Tutorial: Designing A Basic REST API

Managing RoutesI

In API development there are a number of terms floating around but in essence they generally mean the same thing.  An ‘endpoint refers to a location at which a connection may terminate such as the URL endpoint used in this mock API (/hello or /hello/{name}).  Swagger documentation calls this same thing a ‘path’ and RESTify (built on the ExpressJS framework) calls this a ‘route’.

Irrespective of your chosen terminology the role of the route.js file is to provide a small amount of application logic to initialize and map routes in to the API.

Naturally one can imagine that some APIs can become inherently complex and to keep such code in a single file would be impractical.  It would lead to great difficulties during diagnosis and troubleshooting of issues and would quickly become very difficult to maintain.  Therefore it makes good practical sense to separate this application logic in to separate files called controllers.  Slightly similar to the MVC concept the controller files provide the required application logic to manage the routes.

/**
 __   __                _    ____ ___ _____                      _   
 \ \ / /__  _   _ _ __ / \  |  _ \_ _| ____|_  ___ __   ___ _ __| |_ 
  \ V / _ \| | | | '__/ _ \ | |_) | ||  _| \ \/ / '_ \ / _ \ '__| __|
   | | (_) | |_| | | / ___ \|  __/| || |___ >  <| |_) |  __/ |  | |_ 
   |_|\___/ \__,_|_|/_/   \_\_|  |___|_____/_/\_\ .__/ \___|_|   \__|
                                                |_|                  
 @file A basic RESTify API template for use during YourAPIExpert.com tutorials.
 @author YourAPIExpert <yourapiexpert@gmail.com>
 @version 1.0.0
 @module server
*/

// The Essentials
var fs = require('fs');

//-----------------------------------------------------------------------------
// MAIN CODE BLOCK
//-----------------------------------------------------------------------------

// Exports the 'route' function
module.exports = function route(dirname, server) {
  var files = fs.readdirSync(dirname);

  files.forEach(function (file) {
    var filepath = dirname + '/' + file;
    if (fs.statSync(filepath).isDirectory()) {
      route(filepath, server);
    } else {
      var controller = require(filepath);
      controller.route(server);
    }
  });
};

In the above file (route.js) we can see from lines 22-35 that this file exports a function with which to inject routes in to the main application, taking directory name and server as input parameters corresponding to the initialization function in server.js (line 119).  From the make up of the function we can understand that it iterates through each file in dirname (in this case the controllers/ directory) and adds a route map to server (the main RESTify server instance).

A route map takes care of matching incoming HTTP calls to their respective controllers.

Understanding The Controllers
//-----------------------------------------------------------------------------
// MAIN CODE BLOCK
//-----------------------------------------------------------------------------

// Begin be delcaring a class

var Hello = function() {};

// Routing.
// Here we instantiate the new class and assign routes to it.
// Routes, in essence, react to events (GET/POST/etc) by passing the control
// to other functions in the application.

var hello = new Hello();

// In this tutorial I will demonstrate how to extract and respond to data
// in the popular GET and POST requests.
module.exports.route = function(app) {
  app.post('/hello', hello.doPost);
  app.get('/hello/:name', hello.doGet);
};

In the file controllers/hello.js we begin to design the application logic with which to manage our routes.  Between lines 21-28 we declare and initialize a class (Hello) which has an empty function.

Take note of the special magic in lines 33-34 where we notify the calling function (from route.js) to register two routes namely ‘GET /hello/{name}’ and ‘POST /hello’ with respective functions (hello.doGet and hello.doPost) assigned to the routes.  We use ‘/hello/:name’  in NodeJS as the ‘{}’ notation has other reserved purposes denoting an object or dictionary.

Handling POST Requests
// doPost()
// In POST calls the majority of data is sent to the server in the BODY.  
// In the main server.js code we enabled a plugin/middleware to help us work
// with the body.  Conveniently this plugin offers us a variable in which the
// extracted body is contained.
Hello.prototype.doPost = function(req, res, next) {

  // 'req' above is the request object whilst 'res' is the response object.
  // Conveniently thanks to the bodyParser plugin/middleware the submitted
  // body is available in req.body
  //
  // As out content type is application/json the body is a plain object so it
  // is trivial to extract the required information.
  //
  // But first, some sanity checking.
  if(req.body.hasOwnProperty('hello')){
    // We have the required property so let's extract is and return it in a 
    // response to the caller.
    res.send({'result':{'response':'Hello '+req.body.hello}});
  } else {
    // The supplied body does not conform so we need to issue an error.  For 
    // convenience to the calling application and to conform to standards we
    // will issue a JSON parsable response.
    res.send(400, {'error':{'code':400, 'status':'BadRequest','message':'The body does not match the required schema'}})
  }

  // Return control back to the caller
  return next();
};

Between lines 41-69 we implement basic application logic to manage the POST method.  POST is a little bit special in that it allows for the bulk of data to be transmitted in the body of the HTTP request.  As our application is expecting a content type of ‘application/json’ and a particular JSON schema we implement some rudimentary checking (lines 56-64) to ensure validity.  If the expected schema is not matched we format an error (line 64) with a HTTP status code of 400 and supply it with a JSON object.  ‘res.send’ is a low-level function which provides more control over the transmitted data.  You might also want to check up on res.json and experiment with this.

Because of an earlier plugin in the initialization of the RESTify server (called ‘bodyParser’) the RESTify library automatically parses and converts the incoming JSON data in to an object and makes it available to the application as ‘req.body.  Thus by referring to req.body we can access the entire JSON object.  In line 59 we reply to the caller with a formatted JSON object having extracted the input data (req.body.hello).

Handling GET Requests

In this mock API I have chosen to demonstrate a feature of all requests whether GET, POST, PUT, DELETE, UPDATE and MERGE to extract variables from URL paths.

Our specifications called for a request to be sent as ‘GET /hello/{name}’ and our route was established as ‘GET /hello/:name’ essentially interchangeable.  In our hello.js controller we will manage the GET request.

// doGet()
// This example demonstrates how parameters can be extracted from the URL.  In
// the route above we have specified it as '/hello/:name'. RESTify will make
// available any text supplied after /hello in a 'name' variable as
// demonstrated below. 
Hello.prototype.doGet = function(req, res, next) {
  // The 'req' object will contain a special property called 'params'.  Within
  // the 'params' property are the substituted variables extracted from the URL.
  //
  // We will be interested in a property called req.param.name

  res.send({'result':{'response':'Hello '+req.params.name}});

  // Return control back to the caller
  return next();
};

Lines 71-86 show the doGet method referenced by the controller’s exported routes.  Here the RESTify server makes URL parameters available in the request object req.params.  Where our route required ‘/hello/:name’ an input data of ‘/hello/world’ would cause req.params.name to be populated with the value ‘world’.  Therefore path parameter names are mapped 1:1 in to the req.params object for use in the function.

We do nothing revolutionary with this data other than to send it back to the caller as a formatted JSON object in line 82.

With this done you can now start the HTTP server by issuing ‘node server.js’ from the ‘001 Basic’ directory.

Up Next: Testing The API

Pages: 1 2 3 4 5 6

Written by YourAPIExpert