Starting to Implement the FireRating REST API

Let's equip our node.js mongoose-driven mongoDB database web server with a REST API. To be precise, I will:

I already discussed the over-all goal and what to store, namely building project models, door instances living in those models, and each door's fire rating value.

Next, we looked at how to identify projects and maintain the door-project relationships in the mongo database.

Now we will cover a few fundamental aspects and start implementing the REST API to interact with the database:

Before we start implementing the API and adding more functionality to our main server module, let's clean it up a bit by moving out the mongo data model definition into separate modules.

Data Model

To improve the code maintainability, we extract the mongo data model definitions that were initially implemented inside server.js and place them in separate individual JavaScript modules door.js and project.js, respectively, living in their own new model subdirectory. For instance, here is the content of new door.js:

  var mongoose = require( 'mongoose' );

  var Schema = mongoose.Schema,
      ObjectId = Schema.ObjectId;

  var RvtUniqueId = String;

  // use Revit UniqueId for door instances.

  var doorSchema = new Schema(
    { _id          : RvtUniqueId // suppress automatic generation
      , project_id : ObjectId
      , level      : String
      , tag        : String
      , firerating : Number },
    { _id: false } // suppress automatic generation
  );

  mongoose.model( 'Door', doorSchema );

Routes

The REST API that the application responds to is defined by routes. Each route specifies three aspects:

For instance, this trivial route definition handles all GET requests to the root of the site, the home page:

  app.get( '/', function( req, res ) {
    res.send( 'Return plain text, JSON data or HTML view' );
  });

In this case, the handling method is an anonymous function that responds with plain text.

Request Placeholder Pattern

URL paths can be represented as string patterns. They are broken up and translated into regular expressions by express, using the separator characters '/' and ':'.

In our case, we might want to retrieve projects by their document title:

  app.get('/project/:title', function(req, res) {

    // GET /project/rac_basic_sample_project

    console.log(req.params.title);

    // displays hard-coded rac_basic_sample_project
    // replace by live data later

    res.send('{"id": 1,...
      ,"title":"rac_basic_sample_project.rvt",
      ...}');
  });

In this sample, :title represents anything that comes after /project/ in the URL. This parameter is available from the params property on the request object req, keyed using the name of the route path placeholder.

Data can also be passed in using the HTTP request body, a possibility we already explored in some depth in the discussion on implementing node server HTTP POST, and GET vs POST.

Route Implementation

Let's implement routes to add, query and modify projects in the database. They are defined in a new JavaScript module routes.js, which simply hooks up each route with its corresponding controller function:

  module.exports = function(app) {
    var projects = require('./controller/projects');
    app.get('/projects', projects.findAll);
    app.get('/projects/:id', projects.findById);
    app.post('/projects', projects.add);
    app.put('/projects/:id', projects.update);
    app.delete('/projects/:id', projects.delete);
  }

All request event handling methods for projects are specified within one single controller module for projects, projects.js, living in a separate controller subdirectory. The main REST HTTP actions are handled. The URL routes are modelled according to standard REST API conventions, and the handling methods are clearly named.

Controller Implementation

The route implementation displayed above requires the controller module controller/project.js to implement the following functions:

Here is the complete implementation, including one extra test function, populate_rac_basic_sample_project, to insert a sample record used for testing purposes:

var mongoose = require('mongoose'),

Project = mongoose.model('Project');

exports.findAll = function(req, res){
  Project.find({},function(err, results) {
    return res.send(results);
  });
};

exports.findById = function(req, res){
  var id = req.params.id;
  Project.findOne({'_id':id},function(err, result) {
    return res.send(result);
  });
};

exports.add = function(req, res) {
  Project.create(req.body, function (err, project) {
    if (err) return console.log(err);
    return res.send(project);
  });
};

exports.update = function(req, res) {
  var id = req.params.id;
  var updates = req.body;

  Project.update({"_id":id}, req.body,
    function (err, numberAffected) {
      if (err) return console.log(err);
      console.log('Updated %d projects', numberAffected);
      return res.send(202);
  });
};

exports.delete = function(req, res){
  var id = req.params.id;
  Project.remove({'_id':id},function(result) {
    return res.send(result);
  });
};

exports.populate_rac_basic_sample_project = function(req, res){
  Project.create({
    'computername': 'JEREMYTAMMIB1D2',
    'path': 'C:/Program Files/Autodesk/Revit 2016/Samples/rac_basic_sample_project.rvt',
    'centralserverpath': '',
    'title': 'rac_basic_sample_project.rvt',
    'numberofsaves': 271,
    'versionguid': 'f498e8b1-7311-4409-a669-2fd290356bb4',
    'projectinfo_uid': '8764c510-57b7-44c3-bddf-266d86c26380-0000c160' }
  , function (err) {
    if (err) return console.log(err);
    return res.send(202);
  });
};

Door routes and controller

I added the analogue routes and controller functions for doors as well.

The application now consists of the following modules:

  package.json
  routes.js
  server.js
  controller/
    doors.js
    projects.js
  model/
    door.js
    project.js

Download and Test

Now, when I start up the node server, it says 'hello' just like before:

$ node server.js
Firerating server listening at port 3001

Navigating to the root URL localhost:3001 also behaves the same, just saying 'hello' by returning a hard-wired plain text response:

Firerating root URL

The new REST API clicks in when we navigate to localhost:3001/projects, for instance, listing all project records:

Firerating REST API URL to GET projects

Ditto for doors, of course:

Firerating REST API URL to GET doors

This version of the code is captured as release 0.0.4 in the firerating GitHub repository, in case you would like to try it out yourself.

Next

The discussion above addresses the third item in our to-do list; the first two are already done:

Guess what comes next?   – approaching the fun part, real-life testing...