Restful API for Mobile and SPA applications

  • Manikanta Panati


REST stands for REpresentational State Transfer and is a pattern for developing APIs where we expose our business entities to be accessed and manipulated using HTTP requests. Each request can be one of these most often used protocols - GET, POST, PUT, PATCH, and DELETE. Each of these methods correspond to a particular, well-defined behavior that can be leveraged while building the API. Each entity is exposed under a logical grouping that allows for easier maintenance of the API, providing simplicity along with portability.


Error Message REpresentational State Transfer Database Error RESTful APIs MongoDB Database 
These keywords were added by machine and not by the authors. This process is experimental and the keywords may be updated as the learning algorithm improves.

9.1 Introducing Restful APIs

REST stands for REpresentational State Transfer and is a pattern for developing APIs where we expose our business entities to be accessed and manipulated using HTTP requests. Each request can be one of these most often used protocols - GET, POST, PUT, PATCH, and DELETE. Each of these methods correspond to a particular, well-defined behavior that can be leveraged while building the API. Each entity is exposed under a logical grouping that allows for easier maintenance of the API, providing simplicity along with portability.

RESTful APIs are platform independent, and the fact that they are based on simple, well-established standards allows developers to achieve high performance, large scale, and secure communication when using REST APIs. The server is understood to be stateless in the RESTful paradigm, which means there is no notion of multiple requests acting within a single context on the server; this helps to scale out the services better. Content negotiation is also a part of the RESTful paradigm wherein the client can specify what format of data they are expecting back.

The RESTful paradigm should be treated as a useful set of rules to expose our application via endpoints. There may be many ways of implementing RESTful APIs and each developer tends to follow their own conventions as to what RESTful means to them. The simple bottom line should be that the APIs must be designed in such a manner that they serve the purpose of the application in a manner that you see fit and not get bogged down in following what a manual might specify.

9.2 What Are We Building?

Let’s begin by outlining what our API is actually going to look like. To be able to work with the tickets that are stored in our MongoDB database, we are going to support a GET to an API called tickets that will return a list of tickets. We would like to include a parameter named page for paging the results. In addition, the API will allow users to get data about a specific ticket by ID. The API will also support getting a list of tickets for the current logged-in user. The API should also allow users to Post a new ticket and allow them to delete an individual ticket. With these features in mind, let’s see how we can expose a RESTful API for our Ticket Model. We will develop the following routes to allow for data retrieval and manipulation in a REST-based fashion:
  • GET /api/tickets: This gets the list of tickets

  • GET /api/tickets/{id}: This gets the ticket with ID {id}

  • POST /api/tickets: This creates a new ticket

  • PUT /api/tickets/{id}: This updates the ticket with ID {id}

  • DELETE /api/tickets/{id}: This deletes the ticket with ID {id}

With an understanding of what APIs we intend to build, let us begin implementing them in our application. To start off, add the following routes that define the endpoints. The URL for a RESTful API is known as an endpoint .

 1   // API
 2   app.get('/api/tickets', keystone.middleware.api, routes.api.ticket.getTickets);
 3   app.get('/api/tickets/:id', keystone.middleware.api, routes.api.ticket.getTicket\
 4   ById);
 5'/api/tickets', keystone.middleware.api, routes.api.ticket.createTicket\
 6   );
 7   app.put('/api/tickets/:id', keystone.middleware.api, routes.api.ticket.updateTic\
 8   ketById);
 9   app.delete('/api/tickets/:id', keystone.middleware.api, routes.api.ticket.delete\
10   TicketById);

The keystone.middleware.api parameter in the route adds the following shortcut methods for JSON API responses.
  • res.apiResponse(data)

  • res.apiError(key, err, msg, code)

  • res.apiNotFound(err, msg)

  • res.apiNotAllowed(err, msg)

The apiReponse method returns the response data in the JSON format. It can also automatically return data in JSONP if there is a callback specified in the request parameters.

The apiError method is a handy utility to return error messages to the client from our APIs. It returns an object with two keys – error and detail, which contain the exceptions that occurred. By default, it returns a HTTP status of 500 if the code parameter is not passed.

The apiNotFound method provides a quick way to raise a 404 (not found) exception from our APIs.

The apiNotAllowed method provides a quick way to raise a 403 (not allowed) HTTP response from our APIs.

Next, update the keystoneJS file with the code below to indicate to the application that we need to import views that are under the routes/api directory. By default, the code will only look for views under /routes/views directory.

1   // Import Route Controllers
2   var routes = {
3           views: importRoutes('./views'),
4       api: importRoutes('./api')
5   };

After defining the endpoints for the APIs, create the ticket.js view under routes/api directory to handle the responses.


 1   var keystone = require('keystone'),
 2       Ticket = keystone.list('Ticket');
 4   /**
 5    * List Tickets
 6    */
 7    exports.getTickets = function(req, res) {
 8            Ticket.model.find(function(err, items) {
10                    if (err) return res.apiError('database error', err);
12                    res.apiResponse({
13                            tickets: items
14                    });
16            });
17   }
19   /**
20    * Get Ticket by ID
21    */
22   exports.getTicketById = function(req, res) {
23           Ticket.model.findById(, item) {
25                   if (err) return res.apiError('database error', err);
26                   if (!item) return res.apiError('not found');
28                   res.apiResponse({
29                           ticket: item
30                   });
32           });
33   }
36   /**
37    * Create a Ticket
38    */
39   exports.createTicket = function(req, res) {
41           var item = new Ticket.model(),
42                   data = req.body;
44           item.getUpdateHandler(req).process(data, function(err) {
46                   if (err) return res.apiError('error', err);
48                   res.apiResponse({
49                           ticket: item
50                   });
52           });
53   }
55   /**
56    * Update Ticket by ID
57    */
58   exports.updateTicketById = function(req, res) {
59           Ticket.model.findById(, item) {
61                   if (err) return res.apiError('database error', err);
62                   if (!item) return res.apiError('not found');
64                   var data = req.body;
66                   item.getUpdateHandler(req).process(data, function(err) {
68                           if (err) return res.apiError('create error', err);
70                           res.apiResponse({
71                                   ticket: item
72                           });
74                   });
76           });
77   }
79   /**
80    * Delete Ticket by ID
81    */
82   exports.deleteTicketById = function(req, res) {
83           Ticket.model.findById( (err, item) {
85                   if (err) return res.apiError('database error', err);
86                   if (!item) return res.apiError('not found');
88                   item.remove(function (err) {
89                           if (err) return res.apiError('database error', err);
91                           return res.apiResponse({
92                                    success: true
93                           });
94                   });
96           });
97   }

9.3 Tools for Working with Restful APIs

Now that we have implemented our APIs , how do we actually test whether they work? There are a couple of useful tools that make it really simple to issue RESTful requests (GET, POST, PUT, etc.) to our application API endpoints and look at the response. The first simple but often very useful tool is the JSON Formatter Chrome extension. By default, Chrome renders any JSON as plain text, making it hard to understand the structure of the response. The extension helps by converting any JSON responses in the browser into a well-formatted tree display with syntax highlighting and code folding. This can be useful for making a few test calls to APIs from within the browser for experimentation. The second tool is an application named POSTMAN REST client. POSTMAN makes it really easy to craft various kinds of requests to API endpoints. This application can be run as a Google Chrome extension within the browser or as a stand-alone application.

9.4 JSON Formatter Chrome Extension

To install the JSON Formatter Chrome extension , navigate to 1 from Chrome and add the extension to the browser.

Here is the response from pointing to our tickets endpoint that returns JSON before JSON Formatter is installed:
And here is that same URL after JSON Formatter has been installed:


Installing POSTMAN REST Client is simple. Visit 2 and click on the ‘Get the chrome app’ button. This will take you to the Google Chrome Web Store, and provide an option to add the extension to Chrome. After installing the extension, launch it. You should be presented with a screen like the one below:
POSTMAN provides a very intuitive interface to make any kind of requests to any endpoint. To test our API call to get a list of tickets, enter the URL http://localhost:3000/api/tickets in the Enter request URL here field and leave the drop-down next to it as GET since we would like to make a HTTP GET request to our endpoint. Click on the Send button. This will make a HTTP GET similar to a browser and render the response at the bottom half of the application.

A great feature of POSTMAN is that it formats the response from requests to make it readable and easy to work with. In our case, it has prettily printed the JSON list of tickets. It also provides nice code collapsing and search features for finding keywords in our response. The sidebar also maintains a history of requests that we have made that can be saved and replayed to various endpoints.

9.6 Serve Data with GET Requests

Exposing data from our application via a HTTP GET is fairly simple. For example, let us look at our implementation for getting a ticket by id. We use the Mongoose findById method to obtain a single document that matches the id passed via the request object. If there was an error querying the document, we use the api middleware method apiError to return a well-formatted error object with a 404 status. If the query returns a matching document, we send back JSON with the result document assigned to the key – ‘ticket’, using the apiResponse middleware method.

 1   Ticket.model.findById(, item) {
 3                   if (err) return res.apiError('database error', err);
 4                   if (!item) return res.apiError('not found');
 6                   res.apiResponse({
 7                           ticket: item
 8                   });
10           });

The id for the document would be the _id field on the document that would be autogenerated by MongoDB for the tickets that we have created. Let us test the call to this endpoint using POSTMAN:

We can see that our API correctly returns a single instance of the ticket matching the id that was requested.

9.7 Update Data with POST and PUT

The most common HTTP verbs used to able to receive data on the server side are POST and PUT. POST is generally accepted as the verb to be used when we need to insert/save a new document. Let’s take a look at endpoint code that accepts a POST request and inserts a ticket into our collection and returns the new document JSON.

 1   var item = new Ticket.model(),
 2                   data = req.body;
 4           item.getUpdateHandler(req).process(data, function(err) {
 6                   if (err) return res.apiError('error', err);
 8                   res.apiResponse({
 9                           ticket: item
10                   });
12           });

The getUpdateHandler method is added to the underlying Mongoose Model for our Ticket Key-stone.js List when we register the list during creation. The power of KeystoneJS lies in its ability to be able to do most of the heavy lifting for the developer and the update handler is one such example. The method single-handedly validates various criteria that have been specified during model definition. For example, we have specified that the title field is required. This will be properly validated automatically without the developer needing to check for such constraints programmatically at every instance. The method is capable of returning flash error messages (if using a view to display the response) or a collection of validation errors. If fields were marked as uneditable using the no-edit option during the creation of the model, then these fields will be skipped over automatically.

To check the validation in action, perform an empty POST to the /api/tickets endpoint.

The response is a well-formatted error message that clearly indicates the reason (Validation failed) and point of failure (Title is required). The HTTP status is also set to 500.

Next, provide the actual data for a new ticket using the form-data option in POSTMAN and specify each of the fields as a name/value pair with relevant data. The result indicates that our call was successful with a HTTP status 200 and returns the new ticket JSON data.
Reloading the main /api/tickets endpoint in a browser confirms that the new ticket has indeed been added to the tickets collection.

PUT requests to the endpoint work very similarly to how POST works except that the id of the resource we intend to modify is not passed through along with data we intend to change; instead it is passed via the URL. If you recall the route we defined, it included the id parameter. PUT requests are generally not used in scenarios where the id of the resource needs to be changed.

 1   Ticket.model.findById(, item) {
 3                   if (err) return res.apiError('database error', err);
 4                   if (!item) return res.apiError('not found');
 6                   var data = req.body;
 8                   item.getUpdateHandler(req).process(data, function(err) {
10                           if (err) return res.apiError('create error', err);
12                           res.apiResponse({
13                                   ticket: item
14                           });
16                   });
18           });

Looking at the code, we see that we intend to first find the ticket that matches the id that was passed via the URL (req.params collection). If we do not find the document or encounter an error during the query, we return an error object to the client. If we do find the relevant document, we leverage the getUpdateHandler method to make updates to the document and save it. The update handler method will update only fields that we pass in and then leave the rest as is. The following screenshot shows a successful PUT request to update the ticket we created in the previous example. We have updated the ticket’s priority field from Low to Medium. Note that only the priority field was updated and the rest of the ticket data remains the same as the original.

9.8 Removing Data with DELETE

The last route we defined was to delete a ticket. The delete operation is pretty straightforward, and we should call our endpoint with a HTTP DELETE verb along with the id of the ticket we want to be removed.

 1   Ticket.model.findById( (err, item) {
 3                   if (err) return res.apiError('database error', err);
 4                   if (!item) return res.apiError('not found');
 6                   item.remove(function (err) {
 7                           if (err) return res.apiError('database error', err);
 9                           return res.apiResponse({
10                                          success: true
11                           });
12                   });
14           });

We use the Mongoose findById method to retrieve the document we intend to delete. If we do not find the document or encounter exceptions while removing the document, then we return an error to the client. If we do find the document, we remove it and return an object indicating the success status.


All of the endpoints defined in this chapter are extremely simple, that is, they do not enforce strict role validation and authentication rules. They may not be appropriate to be used in production environments as is. However, those rules can be easily added by leveraging the KeystoneJS middleware.

Another tool that can be used to interact with endpoints is cURL. cURL is a command-line tool for crafting requests and receiving responses from servers.

To install cURL on Mac/OSX , follow the steps below:
  1. 1.
  2. 2.

    Open a Terminal and change directory to the folder where the file was downloaded

  3. 3.

    Extract the compressed file with below command:

    1   $tar zxf curl-7.47.1.tar

  4. 4.

    Change directory to the extracted cURL directory

    1   $cd curl-7.47.1

  5. 5.

    Run the make file , as follows and install cURL

    1   $make && sudo make install


cURL on windows is distributed as a stand-alone executable from . Below are the cURL commands illustrating all the interactions we performed using POSTMAN: Get Ticket By Id:

1   curl -X GET -H "Cache-Control: no-cache"
2   "http://localhost:3000/api/tickets/569b9ffeadc009781fe39e23"

Create Ticket using POST :

 1   curl -X POST -H "Cache-Control: no-cache"
 2   -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTr\
 3   Zu0gW"
 4   -F "title=Add social links to blog posts"
 5   -F "description=Add the following:\
 6   l-toolbar/"
 7   -F "priority=Low"
 8   -F "category=Feature"
 9   -F "createdBy=5692e414daa25ac42d792aed"
10   -F "assignedTo=5698e871a3497098256941b4"
11   "http://localhost:3000/api/tickets/"

Update Ticket using PUT:

1   curl -X PUT -H "Cache-Control: no-cache"
2   -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTr\
3   Zu0gW"
4   -F "priority=Medium"
5   "http://localhost:3000/api/tickets/56c1c36077026ff8494d2391"

Delete Ticket By Id:

1   curl -X DELETE -H "Cache-Control: no-cache"
2   "http://localhost:3000/api/tickets/56c1c36077026ff8494d2391"


Copyright information

© Manikanta Panati 2016

Authors and Affiliations

  • Manikanta Panati
    • 1
  1. 1.ApexUSA

Personalised recommendations