Categories
Restify

Restify — Error Handling with Restify-Errors

Restify is a simple Node back end framework.

In this article, we’ll look at how to format error messages with Restify.

Formatting Errors

We can format errors with the error handler.

For example, we can write:

var restify = require('restify');
var errors = require('restify-errors');

var server = restify.createServer();

server.get('/hello/:foo', function(req, res, next) {
  var err = new errors.InternalServerError('not found');
  return next(err);
});

server.on('InternalServer', function (req, res, err, cb) {
  err.toString = function() {
    return 'an internal server error occurred!';
  };

  err.toJSON = function() {
    return {
      message: 'an internal server error occurred!',
      code: 'error'
    }
  };

  return cb();
});

server.on('restifyError', function (req, res, err, cb) {
  return cb();
});

server.listen(8080);

We have the InternalServer handler with the toString and toJSON methods to format string and JSON errors.

The restifyError error handler has the error handler.

Also, we can create formatters for other content types.

For example, we can write:

var restify = require('restify');
var errors = require('restify-errors');

const server = restify.createServer({
  formatters: {
    ['text/html'](req, res, body) {
      if (body instanceof Error) {
        return `<html><body>${body.message}</body></html>`;
      }
    }
  }
});

server.get('/', function(req, res, next) {
  res.header('content-type', 'text/html');
  return next(new errors.InternalServerError('error!'));
});

server.listen(8080);

to create a content formatter for the text/html content type.

restify-errors

The restify-errors module exposes a suite of error constrictors for many common HTTP requests and REST related errors.

For example, we can create errors by writing:

var restify = require('restify');
var errors = require('restify-errors');

const server = restify.createServer();

server.get('/', function(req, res, next) {
  return next(new errors.ConflictError("conflict"));
});

server.listen(8080);

Then when we go to http://localhost:8080 , we get:

{"code":"Conflict","message":"conflict"}

We can also pass in Restify errors as an argument of res.send .

For example, we can write:

var restify = require('restify');
var errors = require('restify-errors');

const server = restify.createServer();

server.get('/', function(req, res, next) {
  res.send(new errors.GoneError('gone'));
  return next();
});

server.listen(8080);

We pass in the GoneError instance into the res.send method.

Then when we go to http://localhost:8080 , we get:

{"code":"Gone","message":"gone girl"}

The automatic serialization to JSON is done by calling JSON.stringify on the Error object.

They all have the toJSON method defined.

If the object doesn’t have the toJSON method defined, then we would get an empty object.

HttpError

HttpError is a generic HTTP error that we can return with the statusCode and body properties.

The statusCode will be automatically set with the HTTP status code, and the body will be set to the message by default.

All status codes between 400 error 500s are automatically be converted to an HttpError with the name being in PascalCase and spaces removed.

RestError

Restify provides us with built it errors with the code and message properties/

For example, if we have:

var restify = require('restify');
var errors = require('restify-errors');

const server = restify.createServer();

server.get('/', function(req, res, next) {
  return next(new errors.InvalidArgumentError("error"));
});

server.listen(8080);

Any 400 or 500 series errors can be created with restify-errors .

Conclusion

We can create error objects with restify-errors .

They’ll be automatically be serialized into JSON if we use it in our response.

Categories
Restify

Restify — Content Negotiation and Errors

Restify is a simple Node back end framework.

In this article, we’ll look at how to handle content negotiation and errors with Restify.

Content Negotiation And Formatting

Restify will determine the content-type to respond with by from highest to lowest priority.

It’ll use the res.contentType if it’s present.

The Content-Type response header if it’s set.

application/json will be returned if the body is an object and not a Buffer instance.

Otherwise, it negotiates the content-type by matching the available formatters with the requests’s accept header.

If a content-type can’t be determined then Restify will respond with an error.

If a content-type can be negotiated, then it determines what formatter to use to format the response’s content.

If there’s no formatter matching the content-type can be found, then Restify will override the response’s content-type to 'application/octet-stream' and then error out if no formnatter if found for that content-type .

The default behavior can be changed by passing the strictFormatters: false property when creating the Restify server instance.

If no formatter is found for the content-type , then the response is flushed without applying any formatter.

Restify ships with application/json , text/plain , and application/octet-stream formatters.

We can add additional formatters to Restify by passing a hash of content type and parser when we create the server.

For example, we can write:

var restify = require('restify');
const util = require('util');

function respond(req, res, next) {
  res.send('hello');
  next();
}

var server = restify.createServer({
  formatters: {
    ['application/foo'](req, res, body) {
      if (body instanceof Error)
        return body.stack;

      if (Buffer.isBuffer(body))
        return body.toString('base64');

      return util.inspect(body);
    }
  }
});

server.get('/hello', respond);
server.head('/hello', respond);

server.listen(8080);

We have the formatters object for the application/foo content type.

Then we return a base64 string if the body is a buffer.

We can also add a q-value to our formatter definition to change the priority of the formatter:

var restify = require('restify');
const util = require('util');
function respond(req, res, next) {
  res.send('hello');
  next();
}
var server = restify.createServer({
  formatters: {
    ['application/foo q=0.9'](req, res, body) {
      if (body instanceof Error)
        return body.stack;
      if (Buffer.isBuffer(body))
        return body.toString('base64');
      return util.inspect(body);
    }
  }
});
server.get('/hello', respond);
server.head('/hello', respond);
server.listen(8080);

Restify ships with default formatters.

It can be overridden when passing formatter options with createServer :

var restify = require('restify');

function respond(req, res, next) {
  const body = 'hello world';
  res.writeHead(200, {
    'Content-Length': Buffer.byteLength(body),
    'Content-Type': 'text/plain'
  });
  res.write(body);
  res.end();
}
var server = restify.createServer();
server.get('/hello', respond);
server.head('/hello', respond);
server.listen(8080);

We called the writeHead method with an object with the content-type options we want to return in the header.

Error Handling

We can handle error conditions by passing an error object with the next function.

For example, we can write:

var restify = require('restify');
var errors = require('restify-errors');

var server = restify.createServer();

server.get('/hello/:foo', function(req, res, next) {
  var err = new errors.NotFoundError('not found');
  return next(err);
});

server.on('NotFound', function (req, res, err, cb) {
  return cb();
});

server.listen(8080);

We have the NotFound handler to do logging or collect data.

The NotFoundError constructor is in the restify-errors module.

We shouldn’t call res.send in the NotFound handler since it’s in a different error context.

Conclusion

We can format the our data and do content negotiating the same way.

Also, we create error objects with the restify-errors module.

Categories
Restify

Restify — Versioned Routes and Upgrade Requests

Restify is a simple Node back end framework.

In this article, we’ll look at how to add routes with Restify.

Versioned Routes

Restify routes can be versioned.

The version number should follow semantic versioning.

For example, we can write:

var restify = require('restify');

var server = restify.createServer();

function sendV1(req, res, next) {
  res.send(`hello: ${req.params.name}`);
  return next();
}

function sendV2(req, res, next) {
  res.send({ hello: req.params.name });
  return next();
}

server.get('/hello/:name', restify.plugins.conditionalHandler([
  { version: '1.1.3', handler: sendV1 },
  { version: '2.0.0', handler: sendV2 }
]));

server.listen(8080);

to have different versions of a route.

Then we can make a request with the accept-version header with the version number as a value.

If this header isn’t provided, then the handler for the latest version will be run.

For example, if we run:

curl -s localhost:8080/hello/foo

We get:

{
    "hello": "foo"
}

And if we run:

curl -s -H 'accept-version: ~1' localhost:8080/hello/foo

We get:

"hello: foo"

And if we run:

curl -s -H 'accept-version: ~2' localhost:8080/hello/foo

We get:

{
    "hello": "foo"
}

If we have an invalid version number in our header like:

curl -s -H 'accept-version: ~3' localhost:8080/hello/foo

We get:

{
    "code": "InvalidVersion",
    "message": "~3 is not supported by GET /hello/foo"
}

We can use the same route handlers for multiple versions by passing in an array:

var restify = require('restify');

var server = restify.createServer();

function sendV1(req, res, next) {
  res.send(`hello: ${req.params.name}`);
  return next();
}

function sendV2(req, res, next) {
  res.send({ hello: req.params.name });
  return next();
}

server.get('/hello/:name', restify.plugins.conditionalHandler([
  { version: '1.1.3', handler: sendV1 },
  { version: ['2.0.0', '2.1.0', '2.2.0'], handler: sendV2 }
]));

server.listen(8080);

Also, we can get the original requested version with the req.version method.

And we can get the version that’s matched by Restify with the matchedVersion method.

For example, we can write:

var restify = require('restify');

var server = restify.createServer();

server.get('/hello/:name', restify.plugins.conditionalHandler([
 {
    version: ['2.0.0', '2.1.0', '2.2.0'],
    handler: function (req, res, next) {
      res.send(200, {
        requestedVersion: req.version(),
        matchedVersion: req.matchedVersion()
      });
      return next();
    }
  }
]));

server.listen(8080);

Then if we run:

curl -s -H 'accept-version: ~2' localhost:8080/hello/foo

We get:

{
    "requestedVersion": "~2",
    "matchedVersion": "2.2.0"
}

Upgrade Requests

If an incoming HTTP requests that has the Connection: Upgrade header, then it’s treated differently by the Node server.

It won’t be pushed through to Restify by default.

We can make this pushed through with the handleUpgrades option.

For example, we can write:

var restify = require('restify');
var Watershed = require('Watershed');

var server = restify.createServer();

var ws = new Watershed();
server.get('/websocket/attach', function(req, res, next) {
  if (!res.claimUpgrade) {
    next(new Error('Connection Must Upgrade For WebSockets'));
    return;
  }

var upgrade = res.claimUpgrade();
  var shed = ws.accept(req, upgrade.socket, upgrade.head);
  shed.on('text', function(msg) {
    console.log('Received message from websocket client: ' + msg);
  });
  shed.send('hello there!');

  next(false);
});

server.listen(8080);

We create the Watershed instance to listen for WebSockets connection.

Conclusion

We can version our routes with Restify.

Also, we can use Watershed to handle Webscoket requests.

Categories
Restify

Restify — Routing

Restify is a simple Node back end framework.

In this article, we’ll look at how to add routes with Restify.

The next Function

The next function is called to call the next handler in the chain.

For example, we can write:

var restify = require('restify');

function respond(req, res, next) {
  res.send('hello ' + req.params.name);
  next();
}

var server = restify.createServer();

server.use([
  function(req, res, next) {
    if (Math.random() < 0.5) {
      res.send('done!');
      return next(false);
    }
    return next();
  }
]);

server.get('/hello/:name', respond);
server.head('/hello/:name', respond);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

Then if Math.random returns a number less than 0.5, then we get that the response is 'done' .

Otherwise, then routes are going to be called.

next also accepts an object that’s where instanceof Error is true .

For instance, we can write:

var restify = require('restify');

function respond(req, res, next) {
  res.send('hello ' + req.params.name);
  next();
}

var server = restify.createServer();

server.use([
  function(req, res, next) {
    return next(new Error('error'));
  }
]);

server.get('/hello/:name', respond);
server.head('/hello/:name', respond);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

Then we’ll see the error response since we passed in an Error instance with the message 'error' .

We can also call res.send with an error object.

We can write:

var restify = require('restify');

function respond(req, res, next) {
  res.send(new Error('error'));
  return next();
}

var server = restify.createServer();

server.get('/hello/:name', respond);
server.head('/hello/:name', respond);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

to send an error response.

Routing

The routing logic for Restidy is similar to Express.

HTTP verbs are used with the parameterized resource to determine which handler to run.

The values associated with named placeholders are in req.params .

For example, we can write:

var restify = require('restify');

function send(req, res, next) {
  res.send('hello ' + req.params.name);
  return next();
}

var server = restify.createServer();

server.post('/hello', function(req, res, next) {
  res.send(201, Math.random().toString(36).substr(3, 8));
  return next();
});
server.put('/hello', send);
server.get('/hello/:name', send);
server.head('/hello/:name', send);
server.del('/hello/:name', function(req, res, next) {
  res.send(204);
  return next();
});

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

We have multiple routes in our Restify app.

get lets us handle GET requests.

post lets us handle POST requests.

put lets us handle PUT requests.

del lets us handle DELETE requests.

Other verbs that can be specified include opts and patch .

Hypermedia

We can render results of other routes with the server.route.render method.

For example, we can write:

var restify = require('restify');

function send(req, res, next) {
  res.send('hello ' + req.params.name);
  return next();
}

var server = restify.createServer();

server.get({name: 'city', path: '/cities/:slug'}, function(req, res, next) {
  res.send(req.params.slug);
  return next();
});

server.get('/hello', function(req, res, next) {
  res.send({
    country: 'Australia',
    capital: server.router.render('city', {slug: 'canberra'}, {details: true})
  });
  return next();
});

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

Then when we go to http://localhost:8080/hello , we get:

{
  "country": "Australia",
  "capital": "/cities/canberra?details=true"
}

The path and query string parameters will be URL encoded properly.

Conclusion

We can create routes with various server methods with Restify.

Categories
Restify

Getting Started Back End Development with Restify

Restify is a simple Node back end framework.

In this article, we’ll look at how to create a simple back end app with Restify.

Quick Start

To get started with Restify, we can install the restify package by running:

npm i restify

Then we can create our first app by writing:

var restify = require('restify');

function respond(req, res, next) {
  res.send(`hello ${req.params.name}`);
  next();
}

var server = restify.createServer();
server.get('/hello/:name', respond);
server.head('/hello/:name', respond);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

We import the restify package.

Then we create our respond function with the req , res , and next parameters.

req has the request data.

res lets us create our responses with the methods that it provides.

next is a function to call the next middleware.

Then we created our server with the restify.createServer method.

Then we defined our routes with the get method to define a GET route.

The first argument is the URL.

The :name is the placeholder for the URL parameter.

We can access the parameter value with req.params.name as we did with the respond function.

server.head mounts a chain on the given path against the HTTP verb.

We need it to let us listen to requests.

Now when we go to https://localhost:8000/hello/name, we get:

"hello name"

on the screen.

The URL parameters are automatically mapped according to their position.

Sinatra Style Handler Chains

We can have a chain of route handlers.

For example, we can write:

var restify = require('restify');

function respond(req, res, next) {
  res.send(`hello ${req.params.name}`);
  next();
}

var server = restify.createServer();
server.get('/', function(req, res, next) {
  res.send('home')
  return next();
});

server.post('/foo',
  function(req, res, next) {
    req.someData = 'foo';
    return next();
  },
  function(req, res, next) {
    res.send(req.someData);
    return next();
  }
);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

to add a get and post route.

The POST route has 2 middlewares.

We call next so that the next one can be called.

So when we make a POST request to https://localhost:8080/foo , we see 'foo' is the response.

There are 3 distinct handler chains.

pre is a handler chain run prior to routing.

use is a handler chain run after routing.

And {httpVerb} is a handler chain that’s run specific to a route.

We can run a pre middleware by writing:

var restify = require('restify');

function respond(req, res, next) {
  res.send(`hello ${req.params.name}`);
  next();
}

var server = restify.createServer();
server.pre(restify.plugins.pre.dedupeSlashes());

server.get('/', function(req, res, next) {
  res.send('home')
  return next();
});

server.post('/foo',
  function(req, res, next) {
    req.someData = 'foo';
    return next();
  },
  function(req, res, next) {
    res.send(req.someData);
    return next();
  }
);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

We run the dedupeSlashes middleware before anything else is run to remove extra slashes in our requests.

The server.use handler is run prior to running a route handler.

To use it, we can write:

var restify = require('restify');

function respond(req, res, next) {
  res.send(`hello ${req.params.name}`);
  next();
}

var server = restify.createServer();
server.use(function(req, res, next) {
  console.warn('run for all routes!');
  return next();
});
server.get('/', function(req, res, next) {
  res.send('home')
  return next();
});

server.post('/foo',
  function(req, res, next) {
    req.someData = 'foo';
    return next();
  },
  function(req, res, next) {
    res.send(req.someData);
    return next();
  }
);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

Then the use handler will be run before a route handler is run.

Conclusion

We can create simple back end apps with Restify with ease.