Let's perform some tests

In this chapter we omitted the disciplined TDD approach, since it has been covered multiple times in previous chapters. However, testing is slightly different in Koa.js, so let's highlight some of those differences.

We can still use supertest in the neat way that we did before, with one slight adjustment as follows:

var app = require('../src/app').callback();

We need to call the .callback() method to return an object that we can pass to supertest. In fact, the returned object can even be mounted on top of an Express app.

Testing the routes to submit links is pretty straightforward:

var app = require('../src/app').callback(),
    Links = require('../src/models/links'),

describe('Submit a link', function() {

  before(function(done) {
    Links.remove({}, function(err) {
      done();
    });
  });

  it('should successfully submit a link', function (done) {
    request(app).post('/links')
      .send({title: 'google', URL: 'http://google.com'})
      .expect(200, done);
  });

At the start of this test suite, we clear the collection in the DB and submit a link using a post request. Nothing special here; note that we use Mocha's default callbacks for the asynchronous requests, and not co-mocha.

Let's submit a few more links, and check that they are indeed stored in the DB:

  it('should successfully submit another link', function (done) {
    request(app).post('/links')
      .send({title: 'Axiom Zen', URL: 'http://axiomzen.co'})
      .expect(200, done);
  });

  it('should successfully submit a third link', function (done) {
    request(app).post('/links')
      .send({title: 'Hacker News', URL: 'http://news.ycombinator.com'})
      .expect(200, done);
  });

  // To be used in next test
  var linkIDs = [];
  it('should list all links', function (done) {
    request(app).get('/links')
      .expect(200)
      .end(function(err, res) {
        var body = res.body;
        expect(body).to.have.length(3);

        // Store Link ids for next test
        for(var i = 0; i < body.length; i++) {
          linkIDs.push(body[i]._id);
        }
        done();
      });
  });

Notice that we store link IDs in an array for the next test case to demonstrate the final, most awesome bonus feature of Koa.js, parallel asynchronous requests, out of the box!

Parallel requests

The backend of Hacker News should be able to deal with the race condition, that is, it should handle hundreds of concurrent upvote requests without losing data (recall Chapter 4, MMO Word Game on race conditions). So let's write a test case that simulates parallel requests.

Traditionally, you would immediately think of using the extremely powerful and popular async library, which has a lot of very useful tools to deal with complex asynchronous execution flows. One of the most useful tools that async offers is async.parallel, with which you can make asynchronous requests in parallel. It is used to be the go-to solution for parallel requests, but now Koa offers something out of the box and with a much cleaner syntax!

Recall that co is actually what gives Koa the power of generator functions, so refer to the readme page of the co project to read more about all the patterns that it has to offer.

So far we yielded to generator functions, Promises, and thunks. However, that is not all. You can also yield to an array of the preceding which would execute them in parallel! Here's how:

// Add to top of file
require('co-mocha'),
var corequest = require('co-supertest'),

…

  it('should upvote all links in parallel', function *() {
    
    var res = yield linkIDs.map(function(id) {
     return corequest(app)
        .put('/links/' + id + '/upvote')
        .end()
    });
;

    // Assert that all Links have been upvoted
    for(var i = 0; i < res.length; i++) {
      expect(res[i].body.upvotes).to.equal(1);
    }

  });

Firstly, notice how we use a generator function, so be sure that you have require(co-mocha) on top of your test file.

Secondly, supertest does not return a thunk or a promise, which we can yield to, so we require co-supertest for this test case:

npm install co-supertest --save-dev

Thirdly, we build an array of requests to be executed later. We are basically pushing thunks into an array; they could be promises too. Now when we yield the array, it will execute all requests in parallel, and return an array of all the response objects!

Quite mind blowing if you're used to async.parallel for these things, right?

Rendering HTML pages

At this point, we have a simple Koa API that has all the basic functionalities quite well tested. Let's add a simple view layer on top to show how you can serve static files from a Koa app as well. So if the app receives a request from a browser for HTML content, we'll serve a functional web page, where we can see the links submitted, submit a link, as well as upvote a link.

Let's pause here for a quick real-developer-life anecdote to implement the preceding. The tendency for modularity is an empowering force of the open source community. A modern day developer has access to a plethora of well-tested modules. Oftentimes, the majority of the developer's work is simply to compose an app of several such modules. We learn of these modules from prior experience, books, news websites, social media, and so on. So how do we go about choosing the right tools instead of reinventing the wheel?

It is always recommended to do a simple search to see whether a module is already available. In this case, we are interested in rendering views with Koa.js, so let's try the search term koa-render on www.npmjs.com. Two popular packages come up that seem to quite fit our needs, as shown in the following screenshot:

Rendering HTML pages

The koa-views is a template rendering middleware for Koa, supporting many template engines. Sounds promising! koa-render adds a render() method to Koa that allows you to render almost any templating engine. Not bad either. As shown in the following screenshot:

Rendering HTML pages

One of the things we can look at to guide our choice is the number of downloads; both packages have a decent amount of downloads, which shows some credibility. The koa-views has about 5 times more downloads than koa-render per month. While these badges are a minor touch, it does show that the author cared enough and is likely to support it. The number of recent commits is also a of good indicator that can be found on the GitHub page for the project, the number issues that have been resolved, and so on.

At the time of writing, both projects' GitHub links redirect to koa-views, which is unexpected, but good for us! Looking at the GitHub account of the author of koa-render, we cannot find the project anymore, so it's safe to assume it was discontinued; avoid it! When you can, try to avoid using non-maintainable packages as it might pose a threat given the fact that Node.js (and io.js) are rapidly evolving ecosystems.

Back to rendering HTML pages, Koa, unlike Express, has no pre-baked opinion about the rendering of views. However, it does provide us with some mechanisms for content negotiation, some of which we can use to enhance and reuse the routes we already have for our API. Let's see what our /links handler will look like:

  app.get('/links', function *(next) {
    var links = yield model.find({}).sort({upvotes: 'desc'}).exec();
    if( this.accepts('text/html') ){
      yield this.render('index', {links: links});
    } else {
      this.body = links;
    }

Our use case is rather simple; we either serve JSON or HTML. When the request header accepts is set to text/html, something browsers set automatically, we'll render the HTML. For the rendering of dynamic jade views to work as expected, we must not forget to include the koa-views middleware in app.js somewhere before the router middleware:

var views = require('koa-views'),

...

app.use(views('./views', {default: 'jade'}));

The middleware points to a folder with a relative path that will contain the templates. Right now, we just need a single template views/index.jade:

doctype html
html(lang="en")
  head
    title Koa News
  body
    h1 Koa News
    div
      each link in links
        .link-row
          a(href='#', onclick="upvote('#{link._id}', $(this))") ^
          span &nbsp;
          a(href=link.URL)= link.title
          .count= link.upvotes
            |  votes
    h2 Submit your link:
    form(action='/links', method='post')
      label Title:
      input(name='title', placeholder="Title")
      br
      label URL:
      input(name='URL', placeholder="https://")
      br
      br
      button.submit-btn Submit
    script(src="https://code.jquery.com/jquery-2.1.3.min.js")
    script.
      var upvote = function(id, elem) {
        $.ajax({url:'/links/'+id+'/upvote', type:'put' })
        .done(function(data) {
          elem.siblings('.count').text(data.upvotes + ' votes'),
        })
      }

It's a jade file similar to the ones presented before in this book. It loops over every link loaded at the controller, which has a single action to upvote. Links are displayed in the descending order of votes, which only happens when the page is reloaded. There is also a simple form that allows the user to submit new links.

We chose to load jQuery from a CDN simply in order to make the PUT request for upvotes. Notice that our use of inline JavaScript as well as adding a click event using the onclick element is highly discouraged, other than to make this example simple to digest.

Now if you have your app running and you go to localhost:3000/links, here's the result:

Rendering HTML pages

So that's a start from a functional standpoint! Clearly not good enough if we want to add more frontend JavaScript and CSS styling to it; we still need to be able to serve static files.

Serving static assets

Although usually you'd be incentivized to create a separate server for your assets, let's keep things simple and dive straight to the goal. We want to serve any files from a certain folder to a certain base path. For that purpose, we'll need two small middlewares, respectively, koa-static and koa-mount. In src/app.js, we add the following:

var serve = require('koa-static'),
var mount = require('koa-mount'),

// ..

app.use(mount('/public', serve('./public') ));

The function mount() will namespace the request for each middleware that follows, in this particular case being combined with serve, which will serve any file inside the public/ directory. If we decide not to mount to any particular URL, serving files would still work; it just won't have a nice namespace.

Now all you need to do is create a public/ directory in the root folder with filepublic/main.css and it will able to serve a stylesheet.

This method allows to serve all static files you'd expect; CSS, JavaScript, images, and even videos.

To take it even further, there are many build tools and best practices for front-end assets, including ways to set up asset pipelines with Grunt, Gulp, Browserify, SASS, CoffeeScript, and many others tools. Not to mention front-end frameworks such as Angular, Ember, React, and so on. This is only the beginning.

Hope you enjoyed the introduction to Koa.js!

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

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