Creating Modular, RESTful Web Services with Express 






Presented by Travis Brown

April 10, 2014  -  Atlanta Node.js Meetup

What is REST?


  • Representational state transfer
  • Defined in 2000 by Roy Fielding
  • Architectural constraints
    • Client–server
    • Stateless
    • Cacheable
    • Layered

REST Basics

  • Objects are known as resources
  • CRUD operations done by HTTP verbs:
    • POST - Create
    • GET - Read
    • PUT, PATCH - Update
    • DELETE - Delete
  • For example, a bookstore inventory might support operations like:
    • POST /book
    • GET /book/12345

The REST Design Spectrum


  • For some REST is a rigidly defined architecture. That's fine!
  • For others REST is a concept used to communicate a style. This is also fine!
  • Be as pragmatic or dogmatic about REST as you want, there are no moral absolutes in software design.

What is Express?


  • A web application framework for node
  • Makes handling HTTP/S requests easy with middleware
  • Lots of built in functionality 
  • If you are handling HTTP requests, use Express.

Getting Started


$ npm install express 

var express = require('express');
var app = express();
app.get('/', function(req, res){
  res.send('Hello World!');
});
app.listen(3000);

$ curl localhost:3000
Hello World! 

Express+REST

var express = require('express');
var app = express();
app.post('/book', function(req, res){
  // Connect to a data source, create a book, assign to variable book
  res.json(201, book);
});
app.get('/book/:id', function(req, res){
  // Connect to a data source, read a book by id, assign to variable book
  res.json(200, book);
});
app.put('/book/:id', function(req, res){
  // Connect to a data source, update a book by id, assign to variable book
  res.json(200, book);
});
app.delete('/book/:id', function(req, res){
  // Connect to a data source, delete a book by id
  res.send(204);
});
app.listen(3000);  

This works, but is not sustainable.

Express+REST


One solution: Import resource methods as module

var express = require('express');
var book = require('./book.js');
var app = express();
app.post('/book', book.create);
app.get('/book/:id', book.fetch);
app.put('/book/:id', book.update);
app.delete('/book/:id', book.destroy);
app.listen(3000); 


Much better, but still cumbersome for multiple services.

Introducing Boss

  • Extensible Web Service Controller
  • Offloads tasks common to services
    • Authentication
    • Authorization
    • Encoding
    • Content Negotiation/Enforcement
    • CORS
    • Routing
  • Each service loaded as a module
  • Scale services separately
  • More granular source code access

Anatomy of a Boss Service Module


  • Boss Service Module requires a boss.json manifest file.
  • Declares resources and where to find resource methods.

{
  "resources": [
    {
      "name" "book",
      "methods": "./book.js"
    },
    {
      "name": "author",
      "methods": "./author.js"
    }
  ]
} 

Anatomy of a Boss Service Module


  • Methods defined for each resource operation

exports.create = function(req, res){
  // Connect to data source, create a book, assign to variable book
  res.json(201, book);
};
exports.fetch = function(req, res){
  // Connect to data source, read a book by id, assign to variable book
  res.json(200, book);
};
exports.update = function(req, res){
  // Connect to data source, update a book by id, assign to variable book
  res.json(200, book);
};
exports.destroy = function(req, res){
  // Connect to data source, delete a book by id
  res.send(204);
}; 

How Boss Works


1. Require Modules
var fs      = require('fs');
var express = require('express');
var config  = require('config');
var auth    = require('./auth.js');

2. Declare app, define middleware
var app = express();
app.use(express.json());
app.use(express.urlencoded()); 


How Boss Works

3. Load services and different versions of services
services = fs.readdirSync(config.serviceDir);
services.forEach(function(service) {
var versions = fs.readdirSync(config.serviceDir+"/"+service);
  versions.forEach(function(version) {
  var serviceConfig = JSON.parse(fs.readFileSync(config.serviceDir+"/"+service+"/"+version+"/"+config.serviceFile))
  serviceConfig.resources.forEach(function(resource) {
    resource.path = "/"+service+"/"+version+"/"+resource.name;
      app.post(resource.path, auth.authenticate, auth.authorize, resource.methods.create);
      app.get(resource.path + '/:id', auth.authenticate, auth.authorize, resource.methods.fetch);
      app.put(resource.path + '/:id', auth.authenticate, auth.authorize, resource.methods.update);
      app.delete(resource.path + '/:id', auth.authenticate, auth.authorize, resource.methods.destroy);
    })
  })
});  
This gives us paths like:
http://api.example.com/service/v1/resource
http://api.example.com/service/v2/resource

How Boss Works


4. Listen
var server = app.listen(config.net.port, function() {
    console.log('Listening on port %d', server.address().port);
}); 



Questions?






Creating Modular, RESTful Web Services Using Express

By Travis Brown

Creating Modular, RESTful Web Services Using Express

This presentation gives a brief overview to the concepts of REST and Express, and describes the structure used at eMeals to deploy services as modules.

  • 1,097