10. Example: Todo List Part 1 (Server-side)

In the first part of this book you learned the ins and outs of CoffeeScript. We covered every last detail of how the language works, how it maps to JavaScript, and how it can help make writing your applications cleaner and more secure. In the last few chapters we’ve looked at a few of the projects that surround the CoffeeScript ecosystem. Now, for the final three chapters of this book, I’d like us to put what we’ve learned into actual use.

Personally, I learn best from seeing things work. Seeing contrived examples of some feature or library goes only so far in actually teaching me what that library is supposed to be doing, but when I see it in action, that’s another story. I can start to see how it fits into my daily development life. In short, it comes alive for me. That’s what we’re, hopefully, going to do in these last few chapters.

The application we are going to build in these last few chapters is the canonical todo list application. We’ve all written them, and we’ve all seen them in action as demo applications. The reason for this is everyone knows what a todo list application is and how it works. It’s big enough in scope to let us exercise the libraries and tools we are talking about, but not too big that it becomes an overwhelming project we can’t tackle in a relatively short span of time.

In this chapter, we are going to build the back end to our application. We need a server that can serve up the todo list page, our assets, and most importantly, serve up the todos themselves and provide a way for us to persist those todos to a data store.

In the two subsequent chapters, we will pick up where we leave off here and build the front end of our application. We will need a way to display the todos on our page, create new todos, update existing todos, and destroy todos.

Finally, at this point I think you’ve seen enough JavaScript, so from here on out, we aren’t going to be looking at the generated JavaScript that CoffeeScript produces. If you really want to see the JavaScript, I recommend either that you compile it yourself or visit the GitHub project for this book.1

Installing and Setting Up Express

The first order of business on the road to building the best todo list application ever is getting a web server/framework in place to handle our backend requests. To do this we are going to use the most popular Node framework out there, Express.2 Express is easy to install and get up and running fairly quickly, and it has a vibrant development community, which makes it a great choice for the task at hand.

In a nice, fresh directory, let’s create a new file, app.coffee, that will be the starting point for our Express backend. Right now, let’s just leave that file blank while we install Express.

Express ships as an NPM,3 Node Package Manager, module. We briefly mentioned NPM in Chapter 9, “Intro to Node.js.” NPM modules are bundles of code that are packaged up and distributed and can then be installed, included, and required into Node applications. If you have Node installed, you should already have NPM installed.

Installing Express is a simple one liner:

> npm install express

You should see output similar to this:

[email protected] ./node_modules/express
—— [email protected]
—— [email protected]
—— [email protected]
—— con [email protected]

You should also now see a new folder called node_modules in the application directory. This is where NPM stores the modules we install. We will be installing more modules in this directory later.


Tip

NPM modules can be installed globally as well by using the -g flag: npm install -g express. I prefer to install the modules into the project so I can check them into SCM. It makes deployment a lot easier and cuts down on potential version conflicts.


With Express now fully installed, let’s add some meat to the bones of that app.coffee file we created earlier.

Example: (source: app.1/app.coffee)


# Setup Express.js:
global.express = require('express')
global.app = app = express.createServer()
require("#{__dirname}/src/configuration")

# Set up a routing for our homepage:
require("#{__dirname}/src/controllers/home_controller")

# Start server:
app.listen(3000)
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env)


So what is going on there? Let’s take a quick look. First we require the express module. Then we call the createServer function. It will do just what its name describes. We then assign that newly created server to two variables: a local variable, app, and a global variable, also called app.


Tip

In Node, the global object references the global scope of the application, similar to the window object in a browser. By setting objects on the global object, we are able to reference them everywhere in our application.


Next we need to configure Express. We are going to do that in a separate file, src/configuration.coffee:

Example: (source: app.1/src/configuration.coffee)


# Configure Express.js:
app.configure ->
  app.use(express.bodyParser())
  app.use(express.methodOverride())
  app.use(express.cookieParser())
  app.use(express.session(secret: 'd19e19fd62f62a216ecf7d7b1de434ad'))
  app.use(app.router)
  app.use(express.static(__dirname + '../public'))
  app.use(express.errorHandler(dumpExceptions: true, showStack: true))


We tell our app to use some pretty basic things, such as sessions, cookies, routing, error handling, and parsing of HTTP bodies. We also tell the app where it can find any static assets we might have, such as images and HTML files.


Tip

In Node the __dirname variable will return the directory from which the calling file resides. This variable is great for building paths to other files.


Next, we need to set up a route for handling our home page. We will do that in a file called src/controllers/home_controller.coffee. The home_controller.coffee file will get pulled into app.coffee using Node’s require function, which will let us load other files into our application.

Example: (source: app.1/src/controllers/home_controller.coffee)


# Set up a routing for our homepage:
app.get '/', (req, res) ->
  res.send "Hello, World!"


On our app object, which was assigned to the global scope of the application, we need to map a routing for when someone comes to the home page of our application. To do that, we can use the get function on the app object. There are functions that map to each of the four HTTP verbs, get, post, put, delete. Because we expect people to make a GET request when they hit the home page, that’s where we are going to map our route of /.

In addition to the URL we want to match, /, the get function is also expecting a callback function that will be executed when that URL is requested. That callback function will be passed the request, req, and the response, res, for us to do with as we please. In this case, we want to send the string Hello, World! back on the response.

With our default route in place for the home page, all that is left to do in app.coffee is to tell the server which port to bind to and to have it start the server when we execute the app.coffee file.

Now let’s start ‘er up!

> coffee app.coffee

Next, navigate to http://localhost:3000 and you should be greeted with the familiar greeting of “Hello, World!”

Congratulations, you now have an Express application up and running!

With the application now up and running, let’s add a proper template engine to it so we don’t have to put all our HTML in the home_controller.coffee file. We can have it in a file all to itself. To do this we need to install the ejs NPM module so we can embed JavaScript into our templates:

> npm install ejs

You should see output similar to this:

[email protected] ./node_modules/ejs


Tip

We could easily serve up plain HTML files with what we have, but having the ejs template engine means we can easily embed dynamic content in our HTML pages. It could, and usually does, prove useful.


Next we have to tell Express where it can find our templates and that it should process those templates with the ejs module. To do that, we need to add two lines to the bottom of the src/configuration.coffee file:

Example: (source: app.2/src/configuration.coffee)


# Configure Express.js:
app.configure ->
  app.use(express.bodyParser())
  app.use(express.methodOverride())
  app.use(express.cookieParser())
  app.use(express.session(secret: 'd19e19fd62f62a216ecf7d7b1de434ad'))
  app.use(app.router)
  app.use(express.static(__dirname + '../public'))
  app.use(express.errorHandler(dumpExceptions: true, showStack: true))
  app.set('views', "#{__dirname}/views")
  app.set('view engine', 'ejs')


Let’s create a new file, src/views/index.ejs. This file will be the landing page for our application when people visit the route we already defined. We’ll make this page fairly simple and have it also say “Hello, World!”—but let’s embed a little bit of dynamic content to make sure it works:

Example: (source: app.2/src/views/index.ejs)


<!DOCTYPE html>
<html>
  <head>
    <title>Todos</title>
  </head>
<body>
  <h1>Hello, World!</h1>
  <h2>The date/time is: <%= new Date() %></h2>
</body>
</html>


Finally, we need to update the home_controller.coffee file to use our new index.ejs file.

Example: (source: app.2/src/controllers/home_controller.coffee)


# Set up a routing for our homepage:
app.get '/', (req, res) ->
  res.render 'index', layout: false


All we had to do was get rid of the send call on the response object and replace it with a call to the render function, passing it the name of the template we wanted to use. We are also telling it to not look for a layout template.


Tip

It is very important to note that any dynamic content you put into an ejs template is to be written in JavaScript, not CoffeeScript. Trust me, that will bite you at some point. It has me! There are quite a few good CoffeeScript-based templating engines out there, but they require a bit more setup work. I recommend checking out Eco4 and CoffeeKup.5


Fire up the server and you should see our new home page, complete with the current date and time.

Let’s move on to the next step, setting up a database.

Setting Up MongoDB Using Mongoose

We need to be able to persist our todos for our application somewhere, and so we are going to use MongoDB.6 MongoDB, also known simply as Mongo, is a popular NoSQL document object store. It’s similar to a relational database in that you can store and retrieve data, but it’s more fluid in that it doesn’t require a traditional schema, meaning we can just start using it without writing a bunch of table creation scripts. That makes it a great choice for us because it means we don’t have to jump through a lot of hoops just building tables and the like. We can simply start throwing todos into the store.

I’m going to make the assumption here that you have MongoDB installed. If you don’t, feel free to go7 to do that now and come back when you’re done.

So what do we need to do to get Mongo up and running with our application? First, we need to install Mongoose,8 a popular object relational mapping (ORM) framework for Node that works with Mongo. Mongoose is conveniently available as an NPM:

> npm install mongoose

[email protected] ./node_modules/mongoose
--- [email protected]
--- [email protected]
--- [email protected]


Tip

Technically, Mongoose isn’t an ORM, because MongoDB isn’t a relational database but rather a document store. However, the term ORM has become a bit overloaded these days to include tools such as Mongoose, so I’m going to run with it.


With Mongoose installed, we need to set it up for the application. This is incredibly easy to do. First create a file, src/models/database.coffee. The src/models directory is where we will put all the code that deals with our database.

Here’s what that file should look like:

Example: (source: app.3/src/models/database.coffee)


# Configure Mongoose (MongoDB):
global.mongoose = require('mongoose')
global.Schema = mongoose.Schema
global.ObjectId = Schema.ObjectId
mongoose.connect("mongodb://localhost:27017/csbook-todos")


In that file we are first requiring Mongoose and then setting a few of its properties to the global object so that we can easily access them later from other parts of our code.

Last, in that file we are telling Mongoose where to find our Mongo server and which database it should use. This line may change on your system depending on how you have things set up.

All that is left to get Mongoose set up in our system is to require the src/models/database.coffee file we just created in our app.coffee file, like so:

Example: (source: app.3/app.coffee)


# Setup Express.js:
global.express = require('express')
global.app = app = express.createServer()
require("#{__dirname}/src/configuration")

# Set up the Database:
require("#{__dirname}/src/models/database")

# Set up a routing for our homepage:
require("#{__dirname}/src/controllers/home_controller")

# Start server:
app.listen(3000)
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env)


With Mongoose up and running, let’s finish up this section by creating a Todo model. The Todo model doesn’t have to be anything fancy. It should have a title, an ID, a state (so we know whether it’s pending or completed), and a date so we know when it was created.

In Mongoose we create new models using the model function and passing it a new Schema object. That Schema object will tell Mongoose and Mongo what type of data we expect our Todo models to have.

Example: (source: app.3/src/models/todo.coffee)


# The Todo Mongoose model:
global.Todo = mongoose.model 'Todo', new Schema
  id: ObjectId
  title:
    type: String
    validate: /.+/
  state:
    type: String
    default: 'pending'
  created_at:
    type: Date
    default: Date.now


The Schema for our Todo model looks pretty much like we wanted it to. It’s very self-describing. For the state and created_at properties, we defined some helpful default attributes. For the title property we set a very simple validation on it, insisting that it has at least one character in it before we save it.


Tip

Mongoose is capable of letting you write some pretty sophisticated validators. Check out the extensive documentation9 for more information.


Finally, we need to update our app.coffee file to require the Todo model we just created.

Example: (source: app.3/app.coffee)


# Setup Express.js:
global.express = require('express')
global.app = app = express.createServer()
require("#{__dirname}/src/configuration")

# Set up the Database:
require("#{__dirname}/src/models/database")

# Set up a routing for our homepage:
require("#{__dirname}/src/controllers/home_controller")

# Start server:
app.listen(3000)
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env)


With that, the database and models are all set up ready to go to work. In the next section you’ll see how we interact with the Todo model when we write the controller to handle the requests for our todos.

Writing the Todo API

All that is left on the server side of our todo application is to write an API to access our todo resources from the client side code we’ll write in the next chapter. Let’s start by adding a file to handle all the requests we expect for our API:

Example: (source: app.4/src/controllers/todos_controller.coffee)


# This 'controller' will handle the server requests
# for the Todo resource

# Get a list of the todos:
app.get '/api/todos', (req, res) ->
  res.json [{}]

# Create a new todo:
app.post '/api/todos', (req, res) ->
  res.json {}

# Get a specific todo:
app.get '/api/todos/:id', (req, res) ->
  res.json {}

# Update a specific todo:
app.put "/api/todos/:id", (req, res) ->
  res.json {}

# Destroy a specific todo:
app.delete '/api/todos/:id', (req, res) ->
  res.json {}


The first thing you should note is that for right now, we have stubbed out all the responses to API by rendering empty JSON10 objects. We will add some substance to those responses in just a minute, but I would like to first talk a little bit about what we have so far and what it’s doing.

Earlier in this chapter I mentioned how Express provides a function to map requests using each of the four HTTP verbs: get, post, put, and delete. In the todos_controller.coffee file we are using all those functions so that our API conforms to the REST11 approach.


Tip

We could use just GET and POST requests, but to me that makes our API more confusing. The REST approach lets us better, and more clearly, define our intent in the API.


We are going to need to perform five actions on our todos.

The first action is getting a list of all of the todos in our database. That is what the first route we defined will be for.

The second action is to create a new todo. We will use the post function to do this.

The third action we will need is the capability to retrieve a specific todo from the database. Our third route mapping will be where we do this. Because we need to know which todo we want to pull from the database, we need to know the ID of that todo. Express lets us create mappings that have placeholders in them. We can then retrieve these placeholders inside of the callback function. Here we are doing that with the :id part of the path we mapped. We will be able to retrieve the value of :id later by using the param function off of the req object to which we have access. We will use this same technique again when we need to update or delete a todo.

The fourth action we need to handle is the updating of a todo. This will use the put function, which maps to the PUT http verb.

Our last mapping and action is to delete a specific todo.

All the mappings and actions are going to render out JSON as the response. Conveniently, Express offers a json method on the response to help us with that. Without the json method we would have to write our responses something like this:

res.send JSON.stringify({})

With stubs in place, you can test that things are working as expected by starting the application and going to http://localhost:3000/api/todos. You should see the following printed to the screen:

[{}]

That means it is hitting our API for listing all the todos successfully.

Querying with Mongoose

All that is left to do for this chapter, and before we move on to putting a nice front end on this bad boy, is flesh out each of the actions we’ve mapped for our API.

Finding All Todos

Let’s start with getting all of the todos in the database:

Example: (source: app.5/src/controllers/todos_controller.coffee)


# This 'controller' will handle the server requests
# for the Todo resource

# Get a list of the todos:
app.get '/api/todos', (req, res) ->
  Todo.find {}, [], {sort: [["created_at", -1]]}, (err, @todos) =>
    if err?
      res.json(err, 500)
    else
      res.json @todos

# Create a new todo:
app.post '/api/todos', (req, res) ->
  res.json {}

# Get a specific todo:
app.get '/api/todos/:id', (req, res) ->
  res.json {}

# Update a specific todo:
app.put "/api/todos/:id", (req, res) ->
  res.json {}

# Destroy a specific todo:
app.delete '/api/todos/:id', (req, res) ->
  res.json {}


Mongoose provides a bunch of helpful and easy-to-use class-level functions for helping us find the exact records we want in the database. In our case, we want all the records in the database.


Tip

Mongoose has some incredible documentation. I highly recommend that you check out the Mongoose pages on finding documents12 and on building complicated queries.13


To find all the records, we could simply call the find function and pass it a callback function that will be executed when the query returns or fails. For our application, we would like to add a sort so that our todos come back with the latest todo first and the oldest last. To do that, we must first pass in an object that contains the details of that sort. This is where it gets a little tricky. We can do this one of two ways: we can either break this into several lines, building up a query as we go and then executing that query, or we can do what we have here, which is pass in a few empty arguments before we pass in the argument containing the details of the sort. The first object would represent any clauses you would want to put on the query. In our case, we don’t want to limit our results using a specific query. The second array is where you would specify any specific fields you want to retrieve. We want all the fields, so we’ll leave this blank.


Tip

Why the nested arrays when defining the sort in our find call? That is actually a MongoDBism hard at work. If we wanted to sort by multiple fields, we would do something like {sort: [["created_at", -1], ["updated_at", 1]]}. The inner arrays are a group representing each sort we want to add. The first element of the array is the attribute name, and the second is the direction, -1 for descending and 1 for ascending. In my opinion it could have been done a little nicer and cleaner, but it is what it is.


As I already mentioned, the callback function we pass into the find function will get executed regardless of whether the query was successful. This means that we must do our own error handling.

The callback function will be called with two arguments. The first argument will be an Error object, if there is an error, or null if there was no error. The second argument will represent the result of our query, in this case an array of Todo objects. If the query fails and there is an error, the second argument will be null.

Inside our callback function we should first check to see if there was an error. We can do so using the existential operator that CoffeeScript provides, aka ?. Using the existential operator, we are checking to see if the err object is not undefined or null. If there was an error, we call the json function, passing it the error and the status code of 500. This will let our front end respond appropriately to the error that occurred.

If there is no error, we can pass the list of todos we received from our query, @todos, into the json function, and Express will take care of the rest.


Tip

You might have noticed that we are using => when defining our callback function. We are doing that because we need to make sure that we have access to the req and res objects. All the Mongoose calls are asynchronous, which means the program could have moved on in execution before the callback function gets called, and it may no longer have access to the scope with the req and res objects.


Creating New Todos

Moving on, let’s flesh out the creating of a new todo.

Example: (source: app.6/src/controllers/todos_controller.coffee)


# This 'controller' will handle the server requests
# for the Todo resource

# Get a list of the todos:
app.get '/api/todos', (req, res) ->
  Todo.find {}, [], {sort: [["created_at", -1]]}, (err, @todos) =>
    if err?
      res.json(err, 500)
    else
      res.json @todos

# Create a new todo:
app.post '/api/todos', (req, res) ->
  @todo = new Todo(req.param('todo'))
  @todo.save (err) =>
    if err?
      res.json(err, 500)
    else
      res.json @todo

# Get a specific todo:
app.get '/api/todos/:id', (req, res) ->
  res.json {}

# Update a specific todo:
app.put "/api/todos/:id", (req, res) ->
  res.json {}

# Destroy a specific todo:
app.delete '/api/todos/:id', (req, res) ->
  res.json {}


The code for creating a new todo is pretty simple. First, we instantiate a new instance of the Todo class and pass in an object that represents the values we want to set on our new todo. Where are we getting those values? We are getting them from the parameters that are sent with the POST request of the action we have mapped.

Express lets us retrieve parameters via its param function on the req object. We pass in the name of the parameter we want, and it will either return the value of that parameter or null if it does not exist.

In our action we are looking for the todo parameter, which we are assuming will be an object containing the key/value pairs necessary to create a new todo, something like this:

{
  todo: {
    title: "My Todo Title",
    state: "pending"
  }
}

To save our new todo, we call the save function that Mongoose provides and pass it a callback function. This function takes one argument that represents an error, should one have occurred. Our callback function has similar logic to the one we already saw when we were getting a list of todos. We first check to see if there is an error. If there is an error, we respond with the error and a status of 500; if not, we send back the JSON of our new created todo.


Tip

Technically, if we were supporting the REST protocol exactly, we should return a status code of 201 (“successfully created”) when we create a new todo. However, Express’s default response status code of 200 (“success”) is good enough for our purposes. I leave it up to you to try to implement the 201 status code.


Getting, Updating, and Destroying a Todo

The three remaining actions we need to have our todo API perform are all very similar in nature, so we can cover them all here fairly quickly.

Example: (source: app.7/src/controllers/todos_controller.coffee)


# This 'controller' will handle the server requests
# for the Todo resource

# Get a list of the todos:
app.get '/api/todos', (req, res) ->
  Todo.find {}, [], {sort: [["created_at", -1]]}, (err, @todos) =>
    if err?
      res.json(err, 500)
    else
      res.json @todos

# Create a new todo:
app.post '/api/todos', (req, res) ->
  @todo = new Todo(req.param('todo'))
  @todo.save (err) =>
    if err?
      res.json(err, 500)
    else
      res.json @todo

# Get a specific todo:
app.get '/api/todos/:id', (req, res) ->
  Todo.findById req.param('id'), (err, @todo) =>
    if err?
      res.json(err, 500)
    else
      res.json @todo

# Update a specific todo:
app.put "/api/todos/:id", (req, res) ->
  Todo.findById req.param('id'), (err, @todo) =>
    if err?
      res.json(err, 500)
    else
      @todo.set(req.param('todo'))
      @todo.save (err) =>
        if err?
          res.json(err, 500)
        else
          res.json @todo

# Destroy a specific todo:
app.delete '/api/todos/:id', (req, res) ->
  Todo.findById req.param('id'), (err, @todo) =>
    if err?
      res.json(err, 500)
    else
      @todo.remove()
      res.json @todo


The first thing we need to do with each of these actions is to find the specific todo we’re looking for using the id parameter we mapped in the paths with the :id keyword. We can do this in Mongoose by using the findById class-level function and passing it the id parameter.

The findById function takes a callback function that receives two arguments. As always, the first argument is an error if one exists, and the second argument is the todo if one was found.

What we do with our found todo depends on which of the three actions we are looking at. For the action where we just want to show the todo, all we have to do is return the JSON of the todo.

For the action where we want to update the action, we can use the set function, passing it any attributes we want to update. This is not too dissimilar from when we created a new todo earlier.

Finally, for the action where we want to destroy the todo, we simply need to call the remove function on the todo to destroy it in the database.

Cleaning Up the Controller

In case you haven’t noticed, our controller is rife with duplicated code. Because this is an example application, we could just leave it, but why not clean it up a bit? It will give us a chance to play with classes a bit more.


Tip

In reality, the clean-up code I’m about to show you might be overkill or could be even further refactored, but I think it’s still fun to see how we could refactor it if we so desired.


To clean up our controller, we are going to create a few classes that will handle each of the actions we have mapped. We’ll also use some inheritance to cut down on duplicated code, especially around the error-handling part of our actions where most of the code duplication is happening.

Let’s start by building a base class, Responder, from which all our other classes will inherit. This class will handle our default and common functionality.

Example: (source: app.8/src/controllers/responders/responder.coffee)


class global.Responder

  respond: (@req, @res) =>
    @complete(null, {})

  complete: (err, result = {}) =>
    if err?
      @res.json(err, 500)
    else
      @res.json result


The first thing you should notice is that we are defining the Responder class on the global object. This will allow us to easily access it throughout the rest of our application.

Next up, we will add a default function called respond. This function will be what is passed to the mappings we define in our todos_controller.coffee file. The respond function will take in two arguments, the request object and the response object. We automatically assign those to the scope of the class, by prefixing them with @, so that we can have access to them in our functions.

The respond function will most likely be overridden by our subclasses, but just in case we will provide a default behavior. The default behavior we give it is to call the complete function.

The complete function is where we are wrapping up all that duplicated error handling and response logic we’ve been writing.

With our super class written, let’s start writing our child classes that will handle each of the actions. Let’s start with the class to handle the action where we get all of the todos.

Example: (source: app.8/src/controllers/responders/index_responder.coffee)


require "#{__dirname}/responder"

class Responder.Index extends Responder

  respond: (@req, @res) =>
    Todo.find {}, [], {sort: [["created_at", -1]]}, @complete


First, we need to require the Responder class we just wrote using the require function Node provides us.

When we define our class we could assign it to the global object, like we did with Responder, but it’s good practice to try to namespace things and not put everything directly on the global object, so that’s what we’ll do here. We also need to make sure our Responder.Index class extends Responder using CoffeeScript’s extend keyword.


Tip

In a real application, we would want to namespace the classes we build better so that it is clear they are dealing with the Todo resource. If we have multiple resources, it could prove problematic or confusing.


All that is left to do is to write a custom respond method. In that method we’ll have the query we saw earlier for finding all of the todos. The big difference, compared to what we had already written, is that for our callback function we will pass in a reference to the complete function we wrote in the Responder super class.

Now let’s build a class to handle the create new todos action.

Example: (source: app.8/src/controllers/responders/create_responder.coffee)


require "#{__dirname}/responder"

class Responder.Create extends Responder

  respond: (@req, @res) =>
    todo = new Todo(@req.param('todo'))
    todo.save(@complete)


The Responder.Create class is very similar to the Responder.Index class we just built. In the respond function, we are creating our new todo and passing the complete function as the callback function to the save call we are making.

Example: (source: app.8/src/controllers/responders/show_responder.coffee)


require "#{__dirname}/responder"

class Responder.Show extends Responder

  respond: (@req, @res) =>
    Todo.findById @req.param('id'), @complete


Again, the Responder.Show class is like the first two classes; we’re just changing out the innards of the respond function. We will use the Responder.Show class as the super class for our final two classes that we will write. Let’s take a look.

Example: (source: app.8/src/controllers/responders/update_responder.coffee)


require "#{__dirname}/show_responder"

class Responder.Update extends Responder.Show

  complete: (err, result = {}) =>
    if err?
      super
    else
      result.set(@req.param('todo'))
      result.save(super)


Because the action to update a todo also needs to find the todo in question, we can extend the Responder.Show class instead of the Responder class itself, because the Responder.Show class has all the functionality we need to find the todo.

In the Responder.Update class we are not going to write a new respond function, because we want to use the one we inherited from Responder.Show. Instead, we are going to write a custom complete function.

The first thing we need to do in our new complete function is to check the existence of an error. If there is an error, we call super, which will call the original complete function from the Responder class, and that will handle the error appropriately for us.

If there is no error, we can proceed with setting the attributes we want to update and then calling the save function. When we call the save function, we are going to pass in super, again the original complete function from the Responder class. This will handle any errors and respond with the appropriate JSON.

The class we need to build for the action to destroy a todo is very similar to the one we just built for updating the todo.

Example: (source: app.8/src/controllers/responders/destroy_responder.coffee)


require "#{__dirname}/show_responder"

class Responder.Destroy extends Responder.Show

  complete: (err, result = {}) =>
    unless err?
      result.remove()
    super


Again we are extending the Responder.Show class, and like the Responder.Update class, we are going to write a new complete function.

First, in the complete function, we need to check for any errors that may have arisen from trying to find the todo. If there are no errors, we call the remove function on the todo, which will destroy it in the database.

Finally, we call the super function to handle any errors and respond correctly.

All that is left now is to update our todos_controller.coffee to use the new classes we have built:

Example: (source: app.8/src/controllers/todos_controller.coffee)


# require all of our responders:
for name in ["index", "create", "show", "update", "destroy"]
  require("#{__dirname}/responders/#{name}_responder")

# This 'controller' will handle the server requests
# for the Todo resource

# Get a list of the todos:
app.get '/api/todos', new Responder.Index().respond

# Create a new todo:
app.post '/api/todos', new Responder.Create().respond

# Get a specific todo:
app.get '/api/todos/:id', new Responder.Show().respond

# Update a specific todo:
app.put "/api/todos/:id", new Responder.Update().respond

# Destroy a specific todo:
app.delete '/api/todos/:id', new Responder.Destroy().respond


At the top of the file, we need to require all the classes we have built.


Tip

We could have just written out the require statements for each file, because there are only a few of them, but I like to create an array and loop through it to build the require statements. It’s cleaner, and to add another require just means typing a few characters into our array instead of copying and pasting a big line of code.


With the classes all required, we can pull out the original callback functions we had mapped for all the actions and replace them with a new instance of the appropriate class and the respond function for that class.

Now our todos_controller.coffee file is much cleaner, and we have extracted out all the common functionality for all our actions. This means that should we ever want to change the way we handle errors, or add other common functionality, we can do it all in one file.

Wrapping Up

In this chapter we built the server-side portion of an application to manage todos. We set up an Express application, added MongoDB support using Mongoose, and refactored it all into a nice clean backend.

In the next chapter we will take what we’ve built here and put a sexy14 front end on it. We’re going to get to play with jQuery to enable us to interact with our beautiful server-side code.

Notes

1. https://github.com/markbates/Programming-In-CoffeeScript

2. http://expressjs.com/

3. http://npmjs.org/

4. https://github.com/sstephenson/eco

5. http://coffeekup.org/

6. http://www.mongodb.org/

7. http://www.mongodb.org/

8. http://mongoosejs.com/

9. http://mongoosejs.com/docs/validation.html

10. http://www.json.org/

11. http://en.wikipedia.org/wiki/REST

12. http://mongoosejs.com/docs/finding-documents.html

13. http://mongoosejs.com/docs/query.html

14. Yes, I’m being a bit sarcastic here as anyone who knows me will tell you “sexy” front ends aren’t my specialty.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.142.194