I continued the cloud-based FireRating project by equipping my node.js mongoose-driven mongoDB database web server with a REST API. Let's discuss and test the PUT, POST and DELETE implementation.
For the sake of future-proofing, I also added a version prefix to the routing URLs:
So far, we already completed the following steps:
See below for the future steps still to come.
I already mentioned the importance of version management when implementing a REST API.
After all, from the very first moment a public REST API is published on the web, anybody can start using it and publish web pages that depend on it.
If it subsequently changes, all the web pages and other clients that depend on its functionality may fail.
At this stage, adding a version prefix to the routing URLs is trivial, e.g., /api/v1
.
I also renamed the controller modules by appending the suffix _v1
to them.
The updated routes.js
looks like this:
module.exports = function(app) { var projects = require('./controller/projects_v1'); app.get('/api/v1/projects', projects.findAll); app.get('/api/v1/projects/:id', projects.findById); app.post('/api/v1/projects', projects.add); app.put('/api/v1/projects/:id', projects.update); app.delete('/api/v1/projects/:id', projects.delete); var doors = require('./controller/doors_v1'); app.get('/api/v1/doors', doors.findAll); app.get('/api/v1/doors/:id', doors.findById); app.post('/api/v1/doors', doors.add); app.put('/api/v1/doors/:id', doors.update); app.delete('/api/v1/doors/:id', doors.delete); }
We already tested some of the read-only HTTP GET routes by simply typing them into the browser address bar.
The command-line cURL tool comes in handy for testing the read-write PUT, POST and DELETE actions.
The PUT HTTP action in the REST API correlates to an update
method in the controller module.
The route for update uses an :id
parameter to identify the affected element, e.g., /api/v1/projects/:id
handled by projects.update
, implemented like this:
exports.update = function(req, res) { var id = req.params.id; 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); }); };
The mongo model update function takes three arguments:
The data to update is retrieved from the request body, req.body
, which is used to pass in larger chunks of data, often stored as a single JSON object.
In this case, the JSON object passed in corresponds to the mongo database schema defining the project documents and includes only the model properties to modify.
We can use curl like this to update a specific property, e.g., numberofsaves
, in a specific project's data:
$ curl -i -X PUT -H 'Content-Type: application/json' -d '{"numberofsaves": "272"}' http://localhost:3001/api/v1/projects/5593c8792fee421039c0afe6
It sends a PUT request with JSON content to the project update endpoint.
The -d argument specifies the request body or data containing the JSON object with the properties to modify.
The routing URL includes the version number and ends with the mongo database id of the project to update.
Curl prints the following response to this request:
HTTP/1.1 202 Accepted Content-Type: text/plain; charset=utf-8 Content-Length: 8 ETag: W/"8-OCq1IpMWc8EeOY6tG3sWeA" Date: Mon, 06 Jul 2015 07:20:54 GMT Connection: keep-alive
The updated project data can be examined by entering the same URL in the browser, which performs a HTTP GET request:
All the above applies analogously for adding new database records.
routes.js
hooks up the HTTP POST action to the routing URL /api/v1/projects
with the controller function projects.add
:
exports.add = function(req, res) { Project.create(req.body, function (err, project) { if (err) return console.log(err); return res.send(project); }); };
It can be tested from the command line using curl like this:
curl -i -X POST -H 'Content-Type: application/json' -d '{ "projectinfo_uid": "8764c510-57b7-44c3-bddf-266d86c26380-0000c160", "versionguid": "f498e8b1-7311-4409-a669-2fd290356bb4", "numberofsaves": 271, "title": "rac_basic_sample_project.rvt", "centralserverpath": "", "path": "C:/Program Files/Autodesk/Revit 2016/Samples/rac_basic_sample_project.rvt", "computername": "JEREMYTAMMIB1D2"}' http://localhost:3001/api/v1/projects
Curl replies with an OK response and the new database record based on the JSON body data passed in, extended with the mongo generated _id
and __v
identifier and version fields:
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 359 ETag: W/"167-EVjxXWuV17AVWQFqZlC7tA" Date: Mon, 06 Jul 2015 07:47:25 GMT Connection: keep-alive {"__v":0,"projectinfo_uid":"8764c510-57b7-44c3-bddf-266d86c26380-0000c160","versionguid":"f498e8b1-7311-4409-a669-2fd290356bb4","numberofsaves":271,"title":"rac_basic_sample_project.rvt","centralserverpath":"","path":"C:/Program Files/Autodesk/Revit 2016/Samples/rac_basic_sample_project.rvt","computername":"JEREMYTAMMIB1D2","_id":"559a328d8e67197a1c00d6dd"}
Last and least, for the sake of completeness, let's finish off with the HTTP DELETE action, sent to the same base routing URL /api/v1/projects
with the id of the newly added record appended.
The controller function looks like this:
exports.delete = function(req, res){ var id = req.params.id; Project.remove({'_id':id},function(result) { return res.send(result); }); };
We can test it using the following curl command:
$ curl -i -X DELETE http://localhost:3001/api/v1/projects/559a328d8e67197a1c00d6dd HTTP/1.1 200 OK X-Powered-By: Express Content-Length: 0 ETag: W/"0-1B2M2Y8AsgTpgAmY7PhCfg" Date: Mon, 06 Jul 2015 08:03:03 GMT Connection: keep-alive
The application now consists of the following modules:
package.json routes.js server.js controller/ doors_v1.js projects_v1.js model/ door.js project.js
This version of the code is captured as release 0.0.5 in the firerating GitHub repository, in case you would like to try it out yourself.
The discussion above addresses the fourth item in our to-do list; the first three are already done, and two are currently left:
I already started working on the C# .NET FireRatingCloud Revit add-in for live real-life testing from a desktop app, so expect more soon.
More to-do items may well arise in due course. For instance, it strikes me already now that I may want to add as many records as possible, or all at once, instead of submitting individual HTTP requests for each one. That will require an enhancement of our REST API, of course.