Put, Post, Delete and Curl Testing a REST API

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.

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:

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:

Updated project data

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:

  1. Define more complete schema that includes information about the containers, i.e., the Revit RVT BIM or building information model project files.
  2. Manage the relationships between the door family instances to their container projects.
  3. Adding a REST API to populate and query the database programmatically.
  4. Implement and test REST API PUT, POST and DELETE requests.
  5. Implement a Revit add-in exercising the REST API from C# .NET.
  6. 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.


What shall we store in our server database? Let's say that we are interested in CAD models. Within each model, we have recurring instances of certain assets. In the Revit architectural package, for instance, the asset instances can be individual doors or windows in building model. In that case, the door and window geometry is defined in so-called families, and the individual occurences are called insertions or family instances. If we would like to analyse and report on the usage of assets and their instances across a multitude of projects, we would want to: Define and store data about the models, assets and instances List existing data Report and analyse Let's define the following API to achieve : models GET return list of CAD models models/modelid POST define a new CAD model models/modelid GET return specific model data assets GET return list of assets assets/assetid POST define a new asset assets/assetid GET return specific asset data models/modelid/instances GET return list of instances models/modelid/instances/instanceid POST define a new instance models/modelid/instances/instanceid GET return specific instance data Models are containers for instances. Assets are used by instances, but live independent lives from models. In Revit, a building model is represented by and stored in a Revit RVT project file. Assets are families, stored in Revit family definition RFA files. Instances are family instances, placed in the model and referencing a family definition. The situation is a little bit complicated by the fact that Revit families define multiple types, also known as symbols, and these are referenced by the instances, but we can skip that level for the moment. Inventor, used for mechanical models such as machinery, engines, cars, planes, etc. defines similar relationships between assemblies and parts. In both Inventor and Revit, the assets can be nested.