Validation and error handling

Error-handling is one of the fortes of Koa.js. Using generator functions we don't need to deal with error handling in every level of the callbacks, avoiding the use of (err, res) signature callbacks popularized by Node.js. We don't even need to use the .error or .catch methods known to Promises. We can use plain old try/catch that ships with JavaScript out of the box.

The implication of this is that we can now have the following centralized error handling middleware:

var logger = console;

module.exports = function *(next) {
  try {
    yield next;
  } catch (err) {
    this.status = err.status || 500;
    this.body = err.message;
    this.app.emit('error', err, this);
  }
};

When we include this as one of the first middlewares on the Koa stack, it will basically wrap the entire stack, which is yielded to downstream, in a giant try/catch clause. Now we don't need to worry about exceptions being thrown into the ether. In fact, you are now encouraged to throw common JavaScript errors, knowing that this middleware will gracefully unpack it for you, and present it to the client.

Now this may not always be exactly what you want though. For instance, if you try to upvote an ID that is not a valid BSON format, Mongoose will throw CastError with the message Cast to ObjectId failed for value xxx at path _id'. While informative for you, it is pretty dirty for the client. So here's how you can override the error by returning a 400 error with a nice, clean message:

app.put('/links/:id/upvote', function *(next) {
  var link;
  try {
    link = yield model.upvote(this.params.id);
  } catch (err) {
    if (err.name === 'CastError') {
      this.throw(404, 'link can not be found'),
    }
  }

  // Check that a link document is returned
  this.assert(link, 404, 'link not found'),

  this.body = link;
});

We basically catch the error where it happens, as opposed to let it bubble up all the way to the error handler. While we could throw a JavaScript error object with the status and message fields set to pass it along to the errorHandler middleware, we can also handle it here directly with the this.throw helper of the Context object.

Now if you pass a valid BSON ID, but the link does not exist, Mongoose will not throw an error. Therefore, you still have to check whether the value of link is not undefined. Here is yet another gorgeous helper of the Context object: this.assert. It basically asserts whether a condition is met, and if not, it will return a 400 error with the message link not found, as passed in the second and third argument.

Here are a few more validations to the submission of links:

app.post('/links', function *(next) {
  this.assert(typeof this.request.body.title === 'string', 400, 'title is required'),
  this.assert(this.request.body.title.length > 0, 400, 'title is required'),

  this.assert(utils.isValidURL(this.request.body.URL), 400, 'URL is invalid'),

  // If the above assertion fails, the following code won't be executed.
  var link = yield model.create({
    title: this.request.body.title,
    URL: this.request.body.URL
  });
  this.body = link;
});

We ensure that a title is being passed, as well as a valid URL, for which we use the following RegEx util:

module.exports = {
  isValidURL: function(url) {
    return /(ftp|http|https)://(w+:{0,1}w*@)?(S+)(:[0-9]+)?(/|/([w#!:.?+=&%@!-/]))?/;
  }
};

Now there are still ways to refactor the validation checks into modular middleware; similar to what we did in Chapter 3, Multiplayer Game API – Connect this is left as an exercise to the reader.

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

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