Quick Start

Setting up a server is quick and easy. Here is a barebones echo server:

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);
});

Try hitting that with the following curl commands to get a feel for what restify is going to turn that into:

$ curl -is http://localhost:8080/hello/mark -H 'accept: text/plain'
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 10
Date: Mon, 31 Dec 2012 01:32:44 GMT
Connection: keep-alive

hello mark


$ curl -is http://localhost:8080/hello/mark
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 12
Date: Mon, 31 Dec 2012 01:33:33 GMT
Connection: keep-alive

"hello mark"


$ curl -is http://localhost:8080/hello/mark -X HEAD -H 'connection: close'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 12
Date: Mon, 31 Dec 2012 01:42:07 GMT
Connection: close

Note that by default, curl uses Connection: keep-alive. In order to make the HEAD method return right away, you’ll need to pass Connection: close.

Since curl is often used with REST APIs, restify’s plugins include a plugin to work around this idiosyncrasy in curl. The plugin checks whether the user agent is curl. If it is, it sets the Connection header to “close” and removes the “Content-Length” header.

server.pre(restify.plugins.pre.userAgentConnection());

Sinatra style handler chains

Like many other Node.js based REST frameworks, restify leverages a Sinatra style syntax for defining routes and the function handlers that service those routes:

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();
  }
);

In a restify server, there are three distinct handler chains:

  • pre - a handler chain executed prior to routing
  • use - a handler chain executed post routing
  • {httpVerb} - a handler chain executed specific to a route

All three handler chains accept either a single function, multiple functions, or an array of functions.

Universal pre-handlers: server.pre()

The pre handler chain is executed before routing. That means these handlers will execute for an incoming request even if it’s for a route that you did not register. This can be useful for logging metrics or for cleaning up the incoming request before routing it.

// dedupe slashes in URL before routing
server.pre(restify.plugins.pre.dedupeSlashes());

Universal handlers: server.use()

The use handler chains is executed after a route has been chosen to service the request. Function handlers that are attached via the use() method will be run for all routes. Since restify runs handlers in the order they are registered, make sure that all your use() calls happen before defining any routes.

server.use(function(req, res, next) {
    console.warn('run for all routes!');
    return next();
});

Using next()

Upon completion of each function in the handler chain, you are responsible for calling next(). Calling next() will move to the next function in the chain.

Unlike other REST frameworks, calling res.send() does not trigger next() automatically. In many applications, work can continue to happen after res.send(), so flushing the response is not synonymous with completion of a request.

In the normal case, next() does not typically take any parameters. If for some reason you want to stop processing the request, you can call next(false) to stop processing the request:

server.use([
  function(req, res, next) {
    if (someCondition) {
      res.send('done!');
      return next(false);
    }
    return next();
  },
  function(req, res, next) {
    // if someCondition is true, this handler is never executed
  }
]);

next() also accepts any object for which instanceof Error is true, which will cause restify to send that Error object as a response to the client. The status code for the response will be inferred from the Error object’s statusCode property. If no statusCode is found, it will default to 500. So the snippet below will send a serialized error to the client with an http 500:

server.use(function(req, res, next) {
  return next(new Error('boom!'));
});

And this will send a 404, since the NotFoundError constructor provides a value of 404 for statusCode:

server.use(function(req, res, next) {
  return next(new NotFoundError('not here!'));
});

Calling res.send() with an Error object produces similar results, with this snippet sending an http 500 with a serialized error the client:

server.use(function(req, res, next) {
  res.send(new Error('boom!'));
  return next();
});

The difference between the two is that invoking next() with an Error object allows you to leverage the server’s event emitter. This enables you to handle all occurrences of an error type using a common handler. See the error handling section for more details.

Lastly, you can call next.ifError(err) with an Error object to cause restify to throw, bringing down the process. This can be useful if you an Error is surfaced that cannot be handled, requiring you to kill the process.

Routing

restify routing, in ‘basic’ mode, is pretty much identical to express/sinatra, in that HTTP verbs are used with a parameterized resource to determine what chain of handlers to run. Values associated with named placeholders are available in req.params. Those values will be URL-decoded before being passed to you.

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

server.post('/hello', function create(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 rm(req, res, next) {
  res.send(204);
  return next();
});

You can also pass in a RegExp object and access the capture group with req.params (which will not be interpreted in any way):

server.get(/^\/([a-zA-Z0-9_\.~-]+)\/(.*)/, function(req, res, next) {
  console.log(req.params[0]);
  console.log(req.params[1]);
  res.send(200);
  return next();
});

Here any request like:

$ curl localhost:8080/foo/my/cats/name/is/gandalf

Would result in req.params[0] being foo and req.params[1] being my/cats/name/is/gandalf.

Routes can be specified by any of the following http verbs - del, get, head, opts, post, put, and patch.

server.get(
    '/foo/:id',
    function(req, res, next) {
        console.log('Authenticate');
        return next();
    },
    function(req, res, next) {
        res.send(200);
        return next();
    }
);

Hypermedia

If a parameterized route was defined with a string (not a regex), you can render it from other places in the server. This is useful to have HTTP responses that link to other resources, without having to hardcode URLs throughout the codebase. Both path and query strings parameters get URL encoded appropriately.

server.get({name: 'city', path: '/cities/:slug'}, /* ... */);

// in another route
res.send({
  country: 'Australia',
  // render a URL by specifying the route name and parameters
  capital: server.router.render('city', {slug: 'canberra'}, {details: true})
});

Which returns:

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

Versioned Routes

Most REST APIs tend to need versioning, and restify ships with support for semver versioning in an Accept-Version header, the same way you specify NPM version dependencies:

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);

Try hitting with:

$ curl -s localhost:8080/hello/mark
{"hello":"mark"}
$ curl -s -H 'accept-version: ~1' localhost:8080/hello/mark
"hello: mark"
$ curl -s -H 'accept-version: ~2' localhost:8080/hello/mark
{"hello":"mark"}
$ curl -s -H 'accept-version: ~3' localhost:8080/hello/mark | json
{
  "code": "InvalidVersion",
  "message": "~3 is not supported by GET /hello/mark"
}

In the first case, we didn’t specify an Accept-Version header at all, so restify treats that like sending a *. Much as not sending an Accept header means the client gets the server’s choice. Restify will choose this highest matching route. In the second case, we explicitly asked for for V1, which got us response a response from the version 1 handler function, but then we asked for V2 and got back JSON. Finally, we asked for a version that doesn’t exist and got an error.

You can default the versions on routes by passing in a version field at server creation time. Lastly, you can support multiple versions in the API by using an array:

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

In this case you may need to know more information such as what the original requested version string was, and what the matching version from the routes supported version array was. Two methods make this info available:

server.get('/version/test', 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();
    }
  }
]));

Hitting this route will respond as below:

$ curl -s -H 'accept-version: <2.2.0' localhost:8080/version/test | json
{
  "requestedVersion": "<2.2.0",
  "matchedVersion": "2.1.0"
}

Upgrade Requests

Incoming HTTP requests that contain a Connection: Upgrade header are treated somewhat differently by the node HTTP server. If you want restify to push Upgrade requests through the regular routing chain, you need to enable handleUpgrades when creating the server.

To determine if a request is eligible for Upgrade, check for the existence of res.claimUpgrade(). This method will return an object with two properties: the socket of the underlying connection, and the first received data Buffer as head (may be zero-length).

Once res.claimUpgrade() is called, res itself is marked unusable for further HTTP responses; any later attempt to send() or end(), etc, will throw an Error. Likewise if res has already been used to send at least part of a response to the client, res.claimUpgrade() will throw an Error. Upgrades and regular HTTP Response behaviour are mutually exclusive on any particular connection.

Using the Upgrade mechanism, you can use a library like watershed to negotiate WebSockets connections. For example:

var ws = new Watershed();
server.get('/websocket/attach', function upgradeRoute(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);
});

Responses’ Content Negotiation And Formatting

If you’re using res.send() restify will determine the content-type to respond with by, from highest priority to lowest priority:

  1. using the value of res.contentType if present
  2. otherwise, using the value of the Content-Type response header if set
  3. otherwise, using application/json if the body is an object that is not a Buffer instance
  4. otherwise, negotiating the content-type by matching available formatters with the request’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, restify then determines what formatter to use to format the response’s content.

If no formatter matching the content-type can be found, restify will by default override the response’s content-type to 'application/octet-stream' and then error if no formatter is found for that content-type.

This default behavior can be changed by passing strictFormatters: false (default is false) when creating the restify server instance. In that case, if no formatter is found for the negotiated content-type, the response is flushed without applying any formatter.

Note in the examples above we’ve not defined any formatters, so we’ve been leveraging the fact that restify ships with application/json, text/plain and application/octet-stream formatters. You can add additional formatters to restify by passing in a hash of content-type -> parser at server creation time:

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

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

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

For example, attempting to send a content-type that does not have a defined formatter:

server.get('/foo', function(req, res, next) {
  res.setHeader('content-type', 'text/css');
  res.send('hi');
  return next();
});

Will result in a response with a content-type of application/octet-stream:

$ curl -i localhost:3000/
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 2
Date: Thu, 02 Jun 2016 06:50:54 GMT
Connection: keep-alive

However, if the server instance is created with strictFormatters:false:

var server = restify.createServer({
  strictFormatters: false
});

The response would has a content-type of text/css even though no 'text/css' formatter is present:

$ curl -i localhost:3000/
HTTP/1.1 200 OK
Content-Type: text/css
Content-Length: 2
Date: Thu, 02 Jun 2016 06:50:54 GMT
Connection: keep-alive

As previously noted, restify ships with built-in formatters for json, text, and binary. When you override or append to this, the “priority” might change; to ensure that the priority is set to what you want, you should set a q-value on your formatter definitions, which will ensure sorting happens the way you want:

restify.createServer({
  formatters: {
    'application/foo; q=0.9': function formatFoo(req, res, body) {
      if (body instanceof Error)
        return body.stack;

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

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

Restify ships with the following default formatters, which can be overridden when passing a formatters options to createServer():

  • application/javascript
  • application/json
  • text/plain
  • application/octet-stream

The restify response object retains has all the “raw” methods of a node ServerResponse on it as well.

var body = 'hello world';
res.writeHead(200, {
  'Content-Length': Buffer.byteLength(body),
  'Content-Type': 'text/plain'
});
res.write(body);
res.end();

Error handling

It is common to want to handle an error conditions the same way. As an example, you may want to serve a 500 page on all InternalServerErrors. In this case, you can add a listener for the InternalServer error event that is always fired when this Error is encountered by restify as part of a next(error) statement. This gives you a way to handle all errors of the same class identically across the server. You can also use a generic restifyError event which will catch errors of all types.

An example of sending a 404:

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

server.on('NotFound', function (req, res, err, cb) {
  // do not call res.send! you are now in an error context and are outside
  // of the normal next chain. you can log or do metrics here, and invoke
  // the callback when you're done. restify will automtically render the
  // NotFoundError depending on the content-type header you have set in your
  // response.
  return cb();
});

For customizing the error being sent back to the client:

server.get('/hello/:name', function(req, res, next) {
  // some internal unrecoverable error
  var err = new restify.errors.InternalServerError('oh noes!');
  return next(err);
});

server.on('InternalServer', function (req, res, err, cb) {
  // by default, restify will usually render the Error object as plaintext or
  // JSON depending on content negotiation. the default text formatter and JSON
  // formatter are pretty simple, they just call toString() and toJSON() on the
  // object being passed to res.send, which in this case, is the error object.
  // so to customize what it sent back to the client when this error occurs,
  // you would implement as follows:

  // for any response that is text/plain
  err.toString = function toString() {
    return 'an internal server error occurred!';
  };
  // for any response that is application/json
  err.toJSON = function toJSON() {
    return {
      message: 'an internal server error occurred!',
      code: 'boom!'
    }
  };

  return cb();
});

server.on('restifyError', function (req, res, err, cb) {
  // this listener will fire after both events above!
  // `err` here is the same as the error that was passed to the above
  // error handlers.
  return cb();
});

Here is another example of InternalServerError, but this time with a custom formatter:

const errs = require('restify-errors');

const server = restify.createServer({
  formatters: {
    'text/html': function(req, res, body) {
      if (body instanceof Error) {
        // body here is an instance of InternalServerError
        return '<html><body>' + body.message + '</body></html>';
      }
    }
  }
});

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

restify-errors

A module called restify-errors exposes a suite of error constructors for many common http and REST related errors. These constructors can be used in conjunction with the next(err) pattern to easily leverage the server’s event emitter. The full list of constructors can be viewed over at the restify-errors repository. Here are some examples:

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

server.get('/', function(req, res, next) {
  return next(new errs.ConflictError("I just don't like you"));
});
$ curl -is localhost:3000
HTTP/1.1 409 Conflict
Content-Type: application/json
Content-Length: 53
Date: Fri, 03 Jun 2016 20:29:45 GMT
Connection: keep-alive

{"code":"Conflict","message":"I just don't like you"}

When using restify-errors, you can also directly call res.send(err), and restify will automatically serialize your error for you:

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

server.get('/', function(req, res, next) {
  res.send(new errs.GoneError('gone girl'));
  return next();
});
$ curl -is localhost:8080/
HTTP/1.1 410 Gone
Content-Type: application/json
Content-Length: 37
Date: Fri, 03 Jun 2016 20:17:48 GMT
Connection: keep-alive

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

This automatic serialization happens because the JSON formatter will call JSON.stringify() on the Error object, and all restify-errors have a toJSON method defined. Compare this to a standard Error object which does not have toJSON defined:

server.get('/sendErr', function(req, res, next) {
  res.send(new Error('where is my msg?'));
  return next();
});

server.get('/nextErr', function(req, res, next) {
  return next(new Error('where is my msg?'));
});
$ curl -is localhost:8080/sendErr
HTTP/1.1 410 Gone
Content-Type: application/json
Content-Length: 37
Date: Fri, 03 Jun 2016 20:17:48 GMT
Connection: keep-alive

{}

$ curl -is localhost:8080/nextErr
HTTP/1.1 410 Gone
Content-Type: application/json
Content-Length: 37
Date: Fri, 03 Jun 2016 20:17:48 GMT
Connection: keep-alive

{}

If you want to use custom errors, make sure you have toJSON defined, or use restify-error’s makeConstructor() method to automatically create Errors that are supported with with toJSON.

HttpError

restify-errors provides constructors that inherit from either HttpError or RestError. All HttpErrors have a numeric http statusCode and body properties. The statusCode will automatically set the HTTP response status code, and the body attribute by default will be the message.

All status codes between 400 and 5xx are automatically converted into an HttpError with the name being ‘PascalCase’ and spaces removed. For the complete list, take a look at the node source.

From that code above 418: I'm a teapot would be ImATeapotError, as an example.

RestError

A common problem with REST APIs and HTTP is that they often end up needing to overload 400 and 409 to mean a bunch of different things. There’s no real standard on what to do in these cases, but in general you want machines to be able to (safely) parse these things out, and so restify defines a convention of a RestError. A RestError is a subclass of one of the particular HttpError types, and additionally sets the body attribute to be a JS object with the attributes code and message. For example, here’s a built-in RestError:

var errs = require('restify-errors');
var server = restify.createServer();

server.get('/hello/:name', function(req, res, next) {
  return next(new errs.InvalidArgumentError("I just don't like you"));
});

$ curl -is localhost:8080/hello/mark | json
HTTP/1.1 409 Conflict
Content-Type: application/json
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, Api-Version
Access-Control-Expose-Headers: Api-Version, Request-Id, Response-Time
Connection: close
Content-Length: 60
Content-MD5: MpEcO5EQFUZ2MNeUB2VaZg==
Date: Tue, 03 Jan 2012 00:50:21 GMT
Server: restify
Request-Id: bda456dd-2fe4-478d-809c-7d159d58d579
Response-Time: 3

{
  "code": "InvalidArgument",
  "message": "I just don't like you"
}

The built-in HttpErrors are:

  • BadRequestError (400 Bad Request)
  • UnauthorizedError (401 Unauthorized)
  • PaymentRequiredError (402 Payment Required)
  • ForbiddenError (403 Forbidden)
  • NotFoundError (404 Not Found)
  • MethodNotAllowedError (405 Method Not Allowed)
  • NotAcceptableError (406 Not Acceptable)
  • ProxyAuthenticationRequiredError (407 Proxy Authentication Required)
  • RequestTimeoutError (408 Request Time-out)
  • ConflictError (409 Conflict)
  • GoneError (410 Gone)
  • LengthRequiredError (411 Length Required)
  • PreconditionFailedError (412 Precondition Failed)
  • RequestEntityTooLargeError (413 Request Entity Too Large)
  • RequesturiTooLargeError (414 Request-URI Too Large)
  • UnsupportedMediaTypeError (415 Unsupported Media Type)
  • RequestedRangeNotSatisfiableError (416 Requested Range Not Satisfiable)
  • ExpectationFailedError (417 Expectation Failed)
  • ImATeapotError (418 I’m a teapot)
  • UnprocessableEntityError (422 Unprocessable Entity)
  • LockedError (423 Locked)
  • FailedDependencyError (424 Failed Dependency)
  • UnorderedCollectionError (425 Unordered Collection)
  • UpgradeRequiredError (426 Upgrade Required)
  • PreconditionRequiredError (428 Precondition Required)
  • TooManyRequestsError (429 Too Many Requests)
  • RequestHeaderFieldsTooLargeError (431 Request Header Fields Too Large)
  • InternalServerError (500 Internal Server Error)
  • NotImplementedError (501 Not Implemented)
  • BadGatewayError (502 Bad Gateway)
  • ServiceUnavailableError (503 Service Unavailable)
  • GatewayTimeoutError (504 Gateway Time-out)
  • HttpVersionNotSupportedError (505 HTTP Version Not Supported)
  • VariantAlsoNegotiatesError (506 Variant Also Negotiates)
  • InsufficientStorageError (507 Insufficient Storage)
  • BandwidthLimitExceededError (509 Bandwidth Limit Exceeded)
  • NotExtendedError (510 Not Extended)
  • NetworkAuthenticationRequiredError (511 Network Authentication Required)
  • BadDigestError (400 Bad Request)
  • BadMethodError (405 Method Not Allowed)
  • InternalError (500 Internal Server Error)
  • InvalidArgumentError (409 Conflict)
  • InvalidContentError (400 Bad Request)
  • InvalidCredentialsError (401 Unauthorized)
  • InvalidHeaderError (400 Bad Request)
  • InvalidVersionError (400 Bad Request)
  • MissingParameterError (409 Conflict)
  • NotAuthorizedError (403 Forbidden)
  • RequestExpiredError (400 Bad Request)
  • RequestThrottledError (429 Too Many Requests)
  • ResourceNotFoundError (404 Not Found)
  • WrongAcceptError (406 Not Acceptable)

And the built in RestErrors are:

  • 400 BadDigestError
  • 405 BadMethodError
  • 500 InternalError
  • 409 InvalidArgumentError
  • 400 InvalidContentError
  • 401 InvalidCredentialsError
  • 400 InvalidHeaderError
  • 400 InvalidVersionError
  • 409 MissingParameterError
  • 403 NotAuthorizedError
  • 412 PreconditionFailedError
  • 400 RequestExpiredError
  • 429 RequestThrottledError
  • 404 ResourceNotFoundError
  • 406 WrongAcceptError

You can also create your own subclasses using the makeConstructor method:

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

errs.makeConstructor('ZombieApocalypseError');

var myErr = new errs.ZombieApocalypseError('zomg!');

The constructor takes message, statusCode, restCode, and context options. Please check out the restify-errors repo for more information.

Socket.IO

To use socket.io with restify, just treat your restify server as if it were a “raw” node server:

var server = restify.createServer();
var io = socketio.listen(server.server);

server.get('/', function indexHTML(req, res, next) {
    fs.readFile(__dirname + '/index.html', function (err, data) {
        if (err) {
            next(err);
            return;
        }

        res.setHeader('Content-Type', 'text/html');
        res.writeHead(200);
        res.end(data);
        next();
    });
});


io.sockets.on('connection', function (socket) {
    socket.emit('news', { hello: 'world' });
    socket.on('my other event', function (data) {
            console.log(data);
    });
});

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


Improve this page