Chapter 6. Hacker News API on Koa.js

In this chapter, we will build an API to power our own Hacker News! While technically this wouldn't be very different from the previous chapters, we will use a different framework altogether, Koa.js (http://koajs.com/).

Koa.js is a new web framework designed by the team behind Express. Why did they create a new framework? Because it is designed from the bottom up, with a minimalistic core for more modularity, and to make use of the new generator syntax, proposed in ECMAScript 6, but already implemented in node 0.11.

Note

The odd version releases of node are considered unstable. At the time of writing, the latest stable release was version 0.10. However, when this book went to print, node 0.12 was finally released and is the latest stable version.

An alternative to node 0.11 would be io.js, which at the time of writing reached version 1.0, and also implements ES6 goodies (forked from Node.js and maintained by a handful of node core contributors). In this chapter, we will stick to node 0.11. (When this book went to print, node 0.12 was finally released and is the latest stable version of node.)

One of the main benefits of the generator syntax is that you can very elegantly avoid callback hell, without the use of complicated promise patterns. You can write your APIs even more cleanly than ever before. We'll go over the subtleties as well as some caveats that come with the bleeding edge.

Some things we will cover in this chapter are as follows:

  • Generator syntax
  • Middleware philosophy
  • Context, versus req,res
  • Centralized error handling
  • Mongoose models in Koa.js
  • Thunkify to use Express modules
  • Testing generator functions with Mocha
  • Parallel HTTP requests using co-mocha
  • Rendering views with koa-render
  • Serve static assets with koa-mount and koa-static

Generator syntax

Generator functions are at the core of Koa.js, so let's dive right into dissecting this beast. Generators allow adept JavaScript users to implement functions in completely new ways. Koa.js makes use of the new syntax to write code in a synchronous-looking fashion while maintaining the performance benefits of an asynchronous flow.

The following defines a simple generator function in src/helloGenerator.js (note the asterisk syntax):

module.exports = function *() {
  return 'hello generator';
};

To use Mocha with Koa.js:

  1. You will need to include co-mocha to add generator support, requiring once at the first line of each test file is the safe way to do it. Now you can pass generator functions to Mocha's it function as follows:
    require('co-mocha'),
    var expect = require('chai').expect;
    var helloGenerator = require('../src/helloGenerator'),
    
    describe('Hello Generator', function() {
      it('should yield to the function and return hello', function *() {
        var ans = yield helloGenerator();
        expect(ans).to.equal('hello generator'),
      });
    });
  2. In order to run this code, you will need to have node 0.11 installed, and use the --harmony-generators flag as you run Mocha:
    ./node_modules/mocha/bin/mocha --harmony-generators
  3. If all is well, congratulations, you have just written your first generator function! Now let's explore the execution flow of generator functions a little more.

    Note

    Note the magic use of the yield keyword. The yield keyword can only be used within a Generator function, and works somewhat similar to return, expecting a single value to be passed, that can also be a generator function (also accepts other yieldables—more on that later), and yields the process to that function.

    When a function* is passed, the execution flow will wait until that function returns before it continues further down. In essence, it would be equivalent to the following callback pattern:

    helloGenerator(function(ans) {
      expect(ans).to.equal('hello generator'),
    });

    Much cleaner, right? Compare the following code:

    var A = yield foo();
    var B = yield bar(A);
    var C = yield baz(A, B);

    With the nasty callback hello if we didn't have generator functions:

    var A, B, C;
    
    foo(function(A) {
      bar(A, function(B) {
        baz(A, B, function(C) {
          return C;
        });
      });
    });

    Another neat advantage is super clean error handling, which we will get into later.

    The preceding example is not too interesting because the helloGenerator() function is a synchronous function anyway, so it would've worked the same, even if we didn't use generator functions!

  4. So let's make it more interesting and change helloGenerator.js to the following:
    module.exports = function *() {
      setTimeout(function(){
        return 'hello generator';
      }, 1000);
    }

    Wait! Your test is failing?! What is going on here? Well, yield should have given the flow to the helloGenerator() function, let it run asynchronously, and wait until it is done before continuing. Yet, ans is undefined. And nobody is lying.

    The reason why it is undefined is because the generator() function returns immediately after calling the setTimeout function, which is set to ans. The message that should have returned from within the setTimeout function is broadcast into the infinite void, nowhere to be seen, ever again.

    Note

    One thing to keep in mind with generator functions is that once you use a generator function, you better commit, and not resort to callbacks down the stack! Recall that we mentioned that yield expects a generator function. The setTimeout function is not a generator function, so what do we do? The yield method can also accept a Promise or a Thunk (more on this later).

  5. The setTimeout() function isn't a Promise, so we have two options left; we can thunkify the function, which basically takes a normal node function with a callback pattern and returns a Thunk, so we can yield to it; alternatively, we use co-sleep, which is basically a minuscule node package that has done it for you as follows:
    module.exports = sleep;
    function sleep(ms) {
      return function (cb) {
        setTimeout(cb, ms);
      };
    }
  6. We will talk about how to thunkify later, so let's use co-sleep. Generally a good idea to reuse what's available is to just do a quick search in the npm registry. There are numerous co packages out there!
    var sleep = require('co-sleep'),
    
    module.exports = function *() {
      yield sleep(1000);
      return 'hello generator';
    }
  7. Now all should be good; your tests should pass after sleeping for 1 second.
  8. Note that the co library is what's under the hood of Koa.js, giving it the generator-based control flow goodies. If you want to use this sort of flow outside Koa.js, you can use something like this:
    var co = require('co'),
    var sleep = require('co-sleep'),
    
    co(function*(){
      console.log('1'),
      yield sleep(10);
      console.log('3'),
    });
    
    console.log('2'),

Middleware philosophy

You should be familiar by now with the middlewares in Express. We used them a lot to dry out code, especially for validation and authentication. In Express, middleware is placed between the server that receives the request and the handler that responds to a request. The request flows one way, until it terminates at res.send or something equivalent.

In Koa.js, everything is a middleware, including the handler itself. As a matter of fact, a Koa.js application is just an object, which contains an array of middleware generator functions! The request flows all the way down the stack of middlewares, and back up again. This is best explained with a simple example:

var koa = require('koa'),
var app = koa();

app.use(function *(next){
  var start = new Date();
  yield next;
  var ms = new Date() - start;
  this.set('X-Response-Time', ms + 'ms'),
});

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

Here we have a Koa.js application with two middlewares. The first middleware adds an X-Response-Time header to the response, whereas the second middleware simply sets the response body to Hello World for each request. The flow is as follows:

  • The request comes in on port 3000.
  • The first middleware receives the execution flow.
  • A new Date object is created and assigned to start.
  • The flow yields to the next middleware on the stack.
  • The second middleware sets body on the Context to Hello World.
  • Since there is no more middleware down the stack to be yielding to, the flow returns back upstream.
  • The first middleware receives the execution flow again and continues down.
  • The response time is calculated and the response header is set.
  • The request has reached the top and the Context is returned.

Note

The Koa.js does not use req, res anymore; they are encapsulated into a single Context.

To run this app, we can use the following command:

node --harmony app.js

Context versus req,res

A Koa.js Context is created for each incoming request. Within each middleware, you can access the Context using the this object. It includes the Request and Response object in this.request and this.response, respectively, although most methods and accessors are directly available from the Context.

The most important property is this.body, which sets the response body. The response status is automatically set to 200 when the response body is set. You may override this by setting this.status manually.

Another very useful syntactic sugar is this.throw, which allows you to return an error response by simply calling this.throw(400), or if you want to override the standard HTTP error message, you may pass a second argument with the error message. We will get to Koa.js slick error handling later in this chapter.

Now that we've got the basics down, let's start building a Hacker News API!

The link model

The following code describes the straightforward link document model in src/models/links.js:

var mongoose = require('mongoose'),

var schema = new mongoose.Schema({
  title: { type: String, require: true },
  URL: { type: String, require: true },
  upvotes: { type: Number, require: true, 'default': 0 },
  timestamp: { type: Date, require: true, 'default': Date.now }
});

schema.statics.upvote = function *(linkId) {
  return yield this.findByIdAndUpdate(linkId, {
    $inc: {
      upvotes: 1
    }
  }).exec();
};

var Links = mongoose.model('links', schema);
module.exports = Links;

Note that this is pretty much identical to how you would define a model in Express, with one exception: the upvotes static method. Since findByIdAndUpdate is an asynchronous I/O operation, we need to make sure that we yield to it, so as to make sure we wait for this operation to complete, before we continue the execution.

Earlier we noted that not only generator functions can be yielded to; it also accepts Promises, which is awesome, because they are quite ubiquitous. Using Mongoose, for example, we can turn Mongoose query instances into Promises by calling the exec() method.

The link routes

With the link model in place, let's set up some routes in src/routes/links.js:

var model = require('../models/links'),

module.exports = function(app) {
  app.get('/links', function *(next) {
    var links = yield model.find({}).sort({upvotes: 'desc'}).exec();
    this.body = links;
  });

  app.post('/links', function *(next) {
    var link = yield model.create({
      title: this.request.body.title,
      URL: this.request.body.URL
    });
    this.body = link;
  });

  app.delete('/links/:id', function *(next) {
    var link = yield model.remove({ _id: this.params.id }).exec();
    this.body = link;
  });

  app.put('/links/:id/upvote', function *(next) {
    var link = yield model.upvote(this.params.id);
    this.body = link;
  });
};

This should start to look familiar. Instead of function handlers with the signature (req, res) that we are used to in Express, we simply use middleware generator functions and set the response body in this.body.

Tying it together

Now that we have our model and routes defined perform the following steps:

  1. Let's tie it together in a Koa.js application in src/app.js:
    var koa = require('koa'),
        app = koa(),
        bodyParser = require('koa-body-parser'),
        router = require('koa-router'),
    
    // Connect to DB
    require('./db'),
    
    app.use(bodyParser());
    app.use(router(app));
    require('./routes/links')(app);
    
    module.exports = app;

    Note

    Note that we use koa-body-parser to parse the request body in this.request.body and koa-router, which allows you to define Express style routes, the kind you saw earlier.

  2. Next, we connect to the database, which isn't different from the previous chapters, so we will omit the code here.
  3. Finally, we define the Koa app, mount the middleware, and load the routes. Then, in the root folder, we have /app.js as given in the following:
    var app = require('./src/app.js'),
    app.listen(3000);
    console.log('Koa app listening on port 3000'),

This just loads the app and starts an HTTP server, which listens on port 3000. Now to start the server, make sure you use the --harmony-generators flag. You should now have a working Koa API to power a Hacker News-like website!

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

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