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:
- Add version prefix to routing URLs.
- Test REST API using browser and cURL.
- Put – update a record.
- Post – add a record.
- Delete – delete a record.
- Wrapping up for today.
So far, we already completed the following steps:
- My first mongo database:
- Define the over-all goal and what to store, namely building projects, door instances hosted in them, and each door's fire rating value, based on the venerable old Revit SDK FireRating sample.
- Implementing relationships:
- Define a more complete schema that includes information about the container projects, i.e., the Revit RVT BIM or building information model project files.
- Define and maintain the relationships between the door family instances and their container projects.
- Starting to Implement the FireRating REST API:
- Add a REST API to manage and query the database programmatically.
See below for the future steps still to come.
Add Version Prefix to Routing URLs
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); }
Test REST API using browser and cURL
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.
Put – Update a Record
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:
- query – JSON object of matching properties to identify the document to update
- data – JSON object specifying the properties to update
- callback – function that is called with the number of modified documents
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:
Post – Add a Record
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"}
Delete – Delete a Record
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
Wrapping up for Today
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:
- Define more complete schema that includes information about the containers, i.e., the Revit RVT BIM or building information model project files.
- Manage the relationships between the door family instances to their container projects.
- Adding a REST API to populate and query the database programmatically.
- Implement and test REST API PUT, POST and DELETE requests.
- Implement a Revit add-in exercising the REST API from C# .NET.
- Re-implement the complete cloud-based Revit FireRating SDK sample functionality.
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.