As we come in to this chapter we have a MongoDB database set up, but we can only interact with it through the MongoDB shell. During this chapter we’ll build a REST API so that we can interact with our database through HTTP calls and perform the common CRUD functions: create, read, update, and delete.
We’ll mainly be working with Node and Express, using Mongoose to help with the interactions. Figure 6.1 shows where this chapter fits into the overall architecture.
We’ll start off by looking at the rules of a RESTAPI. We’ll discuss the importance of defining the URL structure properly, the different request methods (GET, POST, PUT, and DELETE) that should be used for different actions, and how an API should respond with data and an appropriate HTTP status code. Once we have
This chapter covers
■ Rules of REST APIs
■ API patterns
■ Typical CRUD functions (create, read, update, delete)
■ Using Express and Mongoose to interact with MongoDB
■ Testing API endpoints
161 The rules of a REST API
that knowledge under our belts we’ll move on to building our API for Loc8r, covering all of the typical CRUD operations. As we go, we’ll discuss a lot about Mongoose, and get into some Node programming and more Express routing.
NOTE If you haven’t yet built the application from chapter 5, you can get the code from GitHub on the chapter-05 branch at github.com/simonholmes/
getting-MEAN. In a fresh folder in terminal the following commands will clone it and install the npm module dependencies:
$ git clone -b chapter-05 https://github.com/simonholmes/getting-MEAN.git
$ cd getting-MEAN
$ npm install
6.1 The rules of a REST API
Let’s start with a recap of what a RESTAPI is. From chapter 2 you may remember:
■ REST stands for REpresentational State Transfer, which is an architectural style rather than a strict protocol. REST is stateless—it has no idea of any current user state or history.
■ API is an abbreviation for application program interface, which enables applica-tions to talk to each other.
Express app Encapsulating
Express app
Express Node.js AngularJS Build a REST API using
Express, Node.js, and Mongoose
Angular SPA
AngularJS
Database API
Express Node.js MongoDB
Figure 6.1 This chapter will focus on building the API that will interact with the database, exposing an interface for the applications to talk to.
So a RESTAPI is a stateless interface to your application. In the case of the MEAN stack the RESTAPI is used to create a stateless interface to your database, enabling a way for other applications to work with the data.
REST APIs have an associated set of standards. While you don’t have to stick to these for your own API it’s generally best to, as it means that any API you create will fol-low the same approach. It also means you’re used to doing things in the “right” way if you decide you’re going to make your API public.
In basic terms a RESTAPI takes an incoming HTTP request, does some processing, and always sends back an HTTP response, as shown in figure 6.2.
The standards that we’re going to follow for Loc8r revolve around the requests and the responses.
6.1.1 Request URLs
Request URLs for a REST API have a simple standard. Following this standard will make your API easy to pick up, use, and maintain.
The way to approach this is to start thinking about the collections in your database, as you’ll typically have a set of APIURLs for each collection. You may also have a set of URLs for each set of subdocuments. Each URL in a set will have the same basic path, and some may have additional parameters.
Within a set of URLs you need to cover a number of actions, generally based around the standard CRUD operations. The common actions you’ll likely want are
■ Create a new item
■ Read a list of several items
■ Read a specific item
Application REST API
Request
Response Someone or something sends a request to the API.
1
The API processes the request, talking to a database if necessary.
2 The API always
sends a response back to the requester.
3 Figure 6.2 A REST API takes
incoming HTTP requests, does some processing, and returns HTTP responses.
163 The rules of a REST API
■ Update a specific item
■ Delete a specific item
Using Loc8r as an example, the database has a Locations collection that we want to interact with. Table 6.1 shows how the URLs and parameters might look for this collection.
As you can see from table 6.1, each action has the same URL path, and three of them expect the same parameter to specify a location. This poses a very obvious question:
How do you use the same URL to initiate different actions? The answer lies in request methods.
6.1.2 Request methods
HTTP requests can have different methods that essentially tell the server what type of action to take. The most common type of request is a GET request—this is the method used when you enter a URL into the address bar of your browser. Another common method is POST, often used when submitting form data.
Table 6.2 shows the methods we’ll be using in our API, their typical use cases, and what you’d expect returned.
The four HTTP methods that we’ll be using are POST, GET, PUT, and DELETE. If you look at the first word in the “Use” column you’ll notice that there’s a different method for each of the four CRUD operations.
Table 6.1 URL paths and parameters for an API to the Locations collection; all have the same base path, and several have the same location ID parameter
Action URL path Parameters Example
Create new location /locations http://loc8r.com/api/locations Read list of locations /locations http://loc8r.com/api/locations Read a specific location /locations locationid http://loc8r.com/api/locations/123 Update a specific location /locations locationid http://loc8r.com/api/locations/123 Delete a specific location /locations locationid http://loc8r.com/api/locations/123
Table 6.2 Four request methods used in a REST API
Request method Use Response
POST Create new data in the database New data object as seen in the database GET Read data from the database Data object answering the request
PUT Update a document in the database Updated data object as seen in the database DELETE Delete an object from the database Null
TIP Each of the four CRUD operations uses a different request method.
The method is important, because a well-designed RESTAPI will often have the same URL for different actions. In these cases it’s the method that tells the server which type of operation to perform. We’ll discuss how to build and organize the routes for this in Express later in this chapter.
So if we take the paths and parameters and map across the appropriate request method we can put together a plan for our API, as shown in table 6.3.
Table 6.3 shows the paths and methods we’ll use for the requests to interact with the location data. As there are five actions but only two different URL patterns, we can use the request methods to get the desired results.
Loc8r only has one collection right now, so this is our starting point. But the docu-ments in the Locations collection do have reviews as subdocudocu-ments, so let’s quickly map those out too.
API URLSFORSUBDOCUMENTS
Subdocuments are treated in a similar way, but require an additional parameter. Each request will need to specify the ID of the location, and some will also need to specify the ID of a review. Table 6.4 shows the list of actions and their associated methods, URL paths, and parameters.
Table 6.3 Request method is used to link the URL to the desired action, enabling the API to use the same URL for different actions
Action Method URL path Parameters Example
Create new
Table 6.4 API URL specifications for interacting with subdocuments; each base URL path must contain the ID of the parent document
Action Method URL path Parameters Example
Create new
165 The rules of a REST API
You may have noticed that for the subdocuments we don’t have a “read a list of reviews” action. This is because we’ll be retrieving the list of reviews as part of the main document. The preceding tables should give you an idea of how to create basic API request specifications. The URLs, parameters, and actions will be different from one application to the next, but the approach should remain consistent.
That’s requests covered. The other half of the flow, before we get stuck in some code, is responses.
6.1.3 Responses and status codes
A good API is like a good friend. If you go for a high-five a good friend will not leave you hanging. The same goes for a good API. If you make a request, a good API will always respond and not leave you hanging. Every single API request should return a response. This contrast is shown in figure 6.3.
Read a
Table 6.4 API URL specifications for interacting with subdocuments; each base URL path must contain the ID of the parent document
Action Method URL path Parameters Example
Good API Bad API
Figure 6.3 A good API always returns a response and shouldn’t leave you hanging.
For a successful RESTAPI, standardizing the responses is just as important as standard-izing the request format. There are two key components to a response:
■ The returned data
■ The HTTP status code
Combining the returned data with the appropriate status code correctly should give the requester all of the information required to continue.
RETURNINGDATAFROMAN API
Your API should return a consistent data format. Typical formats for a RESTAPI are XML and JSON. We’ll be using JSON for our API because it’s the natural fit for the MEAN stack, and it’s more compact than XML, so it can help speed up the response times of an API.
Our API will return one of three things for each request:
■ A JSON object containing data answering the request query
■ A JSON object containing error data
■ A null response
During this chapter we’ll discuss how to do all of these things as we build the Loc8r API. As well as responding with data, any RESTAPI should return the correct HTTP sta-tus code.
USING HTTP STATUSCODES
A good REST API should return the correct HTTP status code. The status code most people are familiar with is 404, which is what is returned by a web server when a user requests a page that can’t be found. This is probably the most prevalent error code on the internet, but there are dozens of other codes relating to client errors, server errors, redirections, and successful requests. Table 6.5 shows the 10 most popular HTTP status codes and where they might be useful when building an API.
Table 6.5 Most popular HTTP status codes and how they might be used when sending responses to an API request
Status code Name Use case
200 OK A successful GET or PUT request 201 Created A successful POST request 204 No content A successful DELETE request
400 Bad request An unsuccessful GET, POST, or PUT request, due to invalid content 401 Unauthorized Requesting a restricted URL with incorrect credentials
403 Forbidden Making a request that isn’t allowed
404 Not found Unsuccessful request due to an incorrect parameter in the URL 405 Method not allowed Request method not allowed for the given URL
167 Setting up the API in Express
As we go through this chapter and build the Loc8r API we’ll make use of several of these status codes, while also returning the appropriate data.
6.2 Setting up the API in Express
We’ve already got a good idea about the actions we want our API to perform, and the URL paths needed to do so. As we know from chapter 4, to get Express to do some-thing based on an incoming URL request we need to set up controllers and routes.
The controllers will do the action, and the routes will map the incoming requests to the appropriate controllers.
We have files for routes and controllers already set up in the application, so we could use those. A better option, though, is to keep the API code separate so that we don’t run the risk of confusion and complication in our application. In fact, this is one of the reasons for creating an API in the first place. Also, by keeping the API code sep-arate it makes it easier to strip it out and put it into a sepsep-arate application at a future point, should you choose to do so. We really do want easy decoupling here.
So the first thing we want to do here is create a separate area inside the application for the files that will create the API. At the top level of the application create a new folder called app_api. If you’ve been following along and building up the application as you go, this will sit alongside the app_server folder.
This folder will hold everything specific to the API: routes, controllers, and mod-els. When you’ve got this all set up we’ll have a look at some ways to test these API placeholders.
6.2.1 Creating the routes
Like we did with the routes for the main Express application, we’ll have an index.js file in the app_api/routes folder that will hold all of the routes we’ll use in the API. Let’s start by referencing this file in the main application file app.js.
INCLUDINGTHEROUTESINTHEAPPLICATION
The first step is to tell our application that we’re adding more routes to look out for, and when it should use them. We already have a line in app.js to require the server application routes, which we can simply duplicate and set the path to the API routes as follows:
var routes = require('./app_server/routes/index');
var routesApi = require('./app_api/routes/index');
409 Conflict Unsuccessful POST request when another object already exists with the same data
500 Internal server error Problem with your server or the database server
Table 6.5 Most popular HTTP status codes and how they might be used when sending responses to an API request
Status code Name Use case
Next we need to tell the application when to use the routes. We currently have the fol-lowing line in app.js telling the application to check the server application routes for all incoming requests:
app.use('/', routes);
Notice the '/' as the first parameter. This enables us to specify a subset of URLs for which the routes will apply. For example, we’ll define all of our API routes starting with /api/. By adding the line shown in the following code snippet we can tell the application to use the API routes only when the route starts with /api:
app.use('/', routes);
app.use('/api', routesApi);
Okay, let’s set up these URLs.
SPECIFYINGTHEREQUESTMETHODSINTHEROUTES
Up to now we’ve only used the GET method in the routes, like in the following code snippet from our main application routes:
router.get('/location', ctrlLocations.locationInfo);
Using the other methods of POST, PUT, and DELETE is as simple as switching out the get with the respective keywords of post, put, and delete. The following code snip-pet shows an example using the POST method for creating a new location:
router.post('/locations', ctrlLocations.locationsCreate);
Note that we don’t specify /api at the front of the path. We specify in app.js that these routes should only be used if the path starts with /api, so it’s assumed that all routes specified in this file will be prefixed with /api.
SPECIFYINGREQUIRED URL PARAMETERS
It’s quite common for APIURLs to contain parameters for identifying specific docu-ments or subdocudocu-ments—locations and reviews in the case of Loc8r. Specifying these parameters in routes is really simple; you just prefix the name of the parameter with a colon when defining each route.
Say you’re trying to access a review with the ID abc that belongs to a location with the ID 123; you’d have a URL path like this:
/api/locations/123/reviews/abc
Swapping out the IDs for the parameter names (with a colon prefix) gives you a path like this:
/api/locations/:locationid/reviews/:reviewid
With a path like this Express will only match URLs that match that pattern. So a loca-tion ID must be specified and must be in the URL between locations/ and /reviews,
169 Setting up the API in Express
and a review ID must also be specified at the end of the URL. When a path like this is assigned to a controller the parameters will be available to use in the code, with the names specified in the path, locationid and reviewid in this case.
We’ll review exactly how you get to them in just a moment, but first we need to set up the routes for our Loc8r API.
DEFININGTHE LOC8R API ROUTES
Now we know how to set up routes to accept parameters, and we also know what actions, methods, and paths we want to have in our API. So we can combine all of this to create the route definitions for the Loc8r API.
If you haven’t done so yet, you should create an index.js file in the app_api/routes folder. To keep the size of individual files under control we’ll separate the locations and reviews controllers into different files. The following listing shows how the defined routes should look.
var express = require('express');
var router = express.Router();
var ctrlLocations = require('../controllers/locations');
var ctrlReviews = require('../controllers/reviews');
// locations
router.get('/locations', ctrlLocations.locationsListByDistance);
router.post('/locations', ctrlLocations.locationsCreate);
router.get('/locations/:locationid', ctrlLocations.locationsReadOne);
router.put('/locations/:locationid', ctrlLocations.locationsUpdateOne);
router.delete('/locations/:locationid', ctrlLocations.locationsDeleteOne);
In this router file we need to require the related controller files. We haven’t created these controller files yet, and will do so in just a moment. This is a good way to approach it, because by defining all of the routes and declaring the associated control-ler functions here we develop a high-level view of what controlcontrol-lers are needed.
The application now has two sets of routes: the main Express application routes and the new API routes. The application won’t start at the moment though, because none of the controllers referenced by the API routes exist.
Listing 6.1 Routes defined in app_api/routes/locations.js
Include controller file
6.2.2 Creating the controller placeholders
To enable the application to start we can create placeholder functions for the control-lers. These functions won’t really do anything, but they will stop the application from falling over while we’re building the API functionality.
The first step, of course, is to create the controller files. We know where these
The first step, of course, is to create the controller files. We know where these