norest

Web service helpers

NoREST

This module provides a minimal framework for defining web service endpoints on top of the Node http module, or anything using compatible request and response objects. It's designed to be as composable and technology agnostic as possible. It uses promises to achieve a level of composability, but it doesn't require your own code to rely on promises or any other async control mechanism.

var norest = require("norest");
var http = require("http");
 
var services = [
  norest.request("GET", "/", function(reqres) {
    norest.respond(res, 200, "omg hai lol\n", "text/plain");
  });
];
 
http.createServer(norest.run(services)).listen(1337);

The standard Node http module deals with request handlers that are functions taking two arguments: a http.IncomingMessage object, which we refer to as the request object, and a http.ServerResponse object, which we refer to as the response object. This function is expected to handle the request by writing to the response object, and has no return value.

NoREST maintains this pattern throughout, and whenever this documentation refers to a "request handler," we are referring to a function with this signature.

Define a service path using norest.request(method, path, handler). It matches an incoming request's method and path using simple equality: no fancy path argument matching. It will ignore the ? and anything following it, but that's it. Consider your paths functions, and the standard URL parameters their arguments.

The norest.request function returns a promise for a request handler. This handler will examine the request, and if it determines a match, it invokes your provided request handler function (the one you passed in as the handler argument).

The handler argument should be either a request handler, or a promise for one.

The function norest.select(endpoints, ...) takes an array of handlers (or promises thereof) as returned by norest.request and goes through them one by one in order until a match is found. It returns a promise that will yield a boolean value: true if the request was handled, false if there was no match and you should probably respond with a 404.

norest.select will pass any additional arguments through to the handlers being processed, which means that normally you should be calling norest.select(handlers, request, response), but if you're using custom handlers that take different arguments, this function is generalised and works on any list of promises for functions that return promises for boolean values where true means stop processing and false means keep going.

There's a convenience function norest.run(endpoints) which wraps norest.select, responds with a 404 when necessary (or a 500 if a handler crashes), and bundles all this up into a function for you. This function is a request handler, which will handle all your services in one go, and its common use case is as the argument for http.createServer.

If you want to handle your own errors and 404s, use norest.select directly. It returns a standard Promises/A compliant promise, on which you can call .then() to await a result, and .catch() to handle errors. There's a function norest.describeError(response, error) you can use to generate a standard error response, which norest.run also uses.

NoREST provides a function norest.respond(response, status, value, headers) which will generate a response automatically. The response argument should be the response object you are handling. status is the HTTP status code you want to respond with. value is the body of the response. Finally, headers can be either a JS object containing the HTTP headers you want to set in your response, or, if all you want to do is set the Content-Type header, a string containing the appropriate MIME type.

If you don't provide a Content-Type header, norest.respond will run the contents of the value argument through JSON.stringify before writing it to the response, and set the Content-Type header to be application/json. If you provide your own Content-Type (even if it's application/json) the value will be passed through to response.end untouched.

The norest.service(schema, handler) function wraps a request handler with URL argument parsing, validation using a JSON schema, as well as an error handler (identical to the one provided by norest.run at the top level) in case your wrapped request handler crashes. It returns a promise for a standard request handler.

The request's URL arguments will be parsed and added to the request objects as request.params. It will then be validated with the JSON schema you've provided, and if validation fails, a 400 response will be returned and your wrapped handler will not be invoked. If validation succeeds, your handler is invoked as expected, with the request object enriched with the params key containing your validated arguments.

var norest = require("norest");
var http = require("http");
 
var services = [
  norest.request("GET", "/processLols", norest.service({
    type: "object", properties: {
      "lol": { type: "string" }
    }, required: ["lol"]
  }, function(reqres) {
    norest.respond(res, 200, "lol " + req.params.lol + "\n", "text/plain");
  });
];
 
http.createServer(function(reqres) {
  norest.select(services, req, res).then(function(result) {
    if (!result) {
      norest.respond(res, 404, "not found", "text/plain");
    }
  }).catch(function(error) {
    norest.describeError(res, error);
  });
}).listen(1337);

License

Copyright 2014 Future Ad Labs Ltd

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.