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.
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 );
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.
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.
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.
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); }); };
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
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:
The new REST API clicks in when we navigate to localhost:3001/projects, for instance, listing all project records:
Ditto for doors, of course:
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.
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...