The de-facto way to construct a web API is to follow the RESTfulapproach (referred to as a REST API).
REST APIs are resource-based interfaces. On the web this means that data resources (typically formatted in JSON, or XML) are represented by URIs (paths) accessed via HTTP.
Actions such as Create, Read, Update, and Delete (commonly referred to as CRUD) are made against these resources using HTTP methods (otherwise referred to as verbs): POST, GET, PUT/PATCH, and DELETE.
Each request made to a REST API is stateless. This means that the server handling your request maintains no context between requests: each request is interpreted equally. Any context required to process the request must be provided with the request itself (for instance, an authorization token).
Good REST APIs maintain a uniform interface. Meaning that the same shape of request which is made against a particular resource can reasonably be expected to act the same way against another resource (for instance, the request modifying an attribute on a Photo resource is uniform to that of modifying a similar attribute on an Album resource).
Another characteristic of a REST API is that server responses are cacheable. This means that based on context returned with the request’s response by the server you can cache the item. The server may return explicit instructions for how long a resource may be cached for (perhaps it instructs not to cache, or provides a recommended length of time to cache before refreshing)
Our sample resources will be in a Postgres database. A sample database file named database.sql can be found in the project root. Simply import the SQL file into a new database, we’ll call ours api. You will need to edit /bin/www with your database connection details. Check out the video above for a walkthrough of this process.
Let’s try running our application for the first time by calling the command below from the project’s root.
./bin/www
If your terminal’s output matches the below, try loading your application in your web browser.
Express.js is great for constructing a REST API because it provides an easy interface to segregate your resources by both type and action.
If you’ve previously built a web application with Express, you’ll find that the steps for creating an API using the same framework are very similar.
Let’s open index.js in your code editor.
This file is as bare bones as you can get when defining an Express application. We have created an application object and exported it for use.
var express = require(‘express’); var app = express();
module.exports = app;
The concept of Routes is used in Express for defining application behaviour to run when a particular request is received.
A route in Express is made up of three main parts:
The HTTP method associated the request we want to capture.
app.get()
The URI (or path) of the request we want to capture.
app.get('/');
A handler function (which accepts a request and response object as args)
app.get('/', function(req, res) { });
This is an example of handling a GET request on / (in other words: what happens when someone loads your app’s index in their browser):
var express = require(‘express’); var app = express();
// Our handler function is passed a request and response object app.get('/', function(req, res) { // We must end the request when we are done handling it res.end(); });
module.exports = app;
After restarting our application, when we load http://localhost:3000 we receive a blank page instead of an error message.
For this tutorial we’ll be interacting with a database containing photos, and albums where an album can have many photos.
We first think about what functionality we want to provide our end-users. Should we wish to allow them to have full CRUD access we need to create routes for each of these actions for each of the resources (photos, albums).
Which HTTP method should we use?
When constructing a REST API each HTTP method corresponds to an action against a resource served by the API.
GET — retrieve a particular resource’s object or list all objects
POST — create a new resource’s object
PATCH — make a partial update to a particular resource’s object
PUT — completely overwrite a particular resource’s object
DELETE — remove a particular resource’s object
We can start laying out our API by specifying the routes for a single resource. This draws from the uniform interface characteristic of REST APIs. We are able to define the routes for one resource, then essentially copy and paste them for additional resources.
To specify our routes we’ll use an Express Router for each resource. We create the router object, specify the routes we wish to reply to, and then attach the router for a particular path.
var express = require(‘express’); var app = express();
// Create the express router object for Photos var photoRouter = express.Router();
// A GET to the root of a resource returns a list of that resource photoRouter.get(‘/’, function(req, res) { });
// A POST to the root of a resource should create a new object photoRouter.post(‘/’, function(req, res) { });
// We specify a param in our path for the GET of a specific object photoRouter.get(‘/:id’, function(req, res) { });
// Similar to the GET on an object, to update it we can PATCH photoRouter.patch(‘/:id’, function(req, res) { });
// Delete a specific object photoRouter.delete('/:id', function(req, res) { });
// Attach the routers for their respective paths app.use(‘/photo’, photoRouter);
module.exports = app;
So now our API is ready to perform actions when we receive any of the following HTTP requests:
GET /photo — Retrieve all photos
GET /photo/:id — Retrieve a photo by its ID
POST /photo — Create a new photo
PATCH /photo/:id — Update properties of a photo by its ID
DELETE /photo/:id — Delete a photo by its ID
This process can be repeated for the album resource.
If you restart your application and try to access /photo or /photo/123 you’ll find that the request hangs and your browser eventually times out. This is because the handler functions we specified do not end the request yet.
Your index.js file should now resemble the following code:
var express = require(‘express’); var app = express();
When we receive a request through a particular route we want to access that resource’s information from the database. This is so that we can return the data to the client performing the request.
Our code is getting verbose now and we want to reduce code duplication. This is where Express Middleware comes into play. Middleware are functions which perform specific actions based on request information, but can be re-used throughout your routers. These functions get passed to the route after the path, and before the handler function, as such:
In our case, an action we are going to be repeating quite frequently is looking up a particular resource object by its ID. So we can create a specific lookup function that is used on multiple routes.
The middleware function looks just like a route handler, but accepts a third argument (typically named next) which is the callback function to proceed to the route’s handler.
function lookupPhoto(req, res, next) { // We access the ID param on the request object var photoId = req.params.id;
// Build an SQL query to select the resource object by ID var sql = ‘SELECT * FROM photo WHERE id = ?’; postgres.client.query(sql, [ photoId ], function(err, results) { if (err) { console.error(err); res.statusCode = 500; return res.json({ errors: [‘Could not retrieve photo’] }); }
// No results returned mean the object is not found if (results.rows.length === 0) { // We are able to set the HTTP status code on the res object res.statusCode = 404; return res.json({ errors: [‘Photo not found’] }); }
// By attaching a Photo property to the request // Its data is now made available in our handler function req.photo = results.rows[0]; next(); }); }
Disclaimer: You want to introduce parameter/input validation before ever publishing such an API to the web.
Here’s a look at the lookup middleware function applied to our photo routes:
Now each time a request comes through for any of those routes, the resource object will be retrieved from the database by its ID and passed via the request object to our handler function.
The same process can be duplicated for the other resources, and also for the handler functions of the GET / routes.
Responding to requests
When we try to list our photos by opening http://localhost:3000/photo in our browser, it still just sits there and doesn’t finish loading.
This is because our handler functions do not respond to the request yet. They’re empty.
When building a RESTful API there are a few concepts we want to be mindful of when responding with data:
HTTP status codes should describe the response
Data returned should be formatted in JSON (preferred) or XML
HTTP status codes
There are a multitude of HTTP status codes, here is just a few of the pertinent ones (which we will be using):
200 — OK, The request was successful
201 — CREATED, A new resource object was successfully created
404 — NOT FOUND, The requested resource could not be found
400 —BAD REQUEST, The request was malformed or invalid
500 — INTERNAL SERVER ERROR, Unknown server error has occurred
To return data with Express we can use the Response object’s json method. This method accepts a JavaScript object and will automatically convert it to JSON and end the request for us.
In the case of our GET /photo/:id route there is no additional processing we need to do in our handler function and we can simply return the data retrieved by our middleware:
In the case of our DELETE /photo/:id route we need to process the removal of the record in our handler function (the middleware verifies that the resource object exists before we continue).
How do we access the POST body?
We want our clients to be able to create records in our database for resources. To do this we need them to supply us with data for that resource object.
The POST body is an object of data attached to the incoming request. Express requires additional middleware to be able to process these incoming values. Enter body-parser.
The body-parser middleware has already been included in the sample project’s package. We need to import and attach the middleware to our Express application.
var express = require('express'); var bodyParser = require('body-parser');
var app = express(); app.use(bodyParser.json({ type: 'application/json' }));
This enables our Express application to parse incoming JSON post bodies.
We have already set up a route at POST /photo for creation of a photo resource object, now we need to write the handler code.
The body-parser middleware that we have loaded automatically adds the bodykey to the Request object. This means we can read any property sent to use by simply doing:
Disclaimer: You always want to validate incoming data before trusting it. For sake of simplicity in the tutorial we are omitting this step
We can now simply write an insert query to run when a request is matched to our photo create route. In this case we return status code 201 (CREATED) and the resulting resource object (which should be identical to the GET of the same resource).
photoRouter.post('/', function(req, res) { var sql = 'INSERT INTO photo (description, filepath, album_id) VALUES ($1,$2,$3) RETURNING id'; // Retrieve the data to insert from the POST body var data = [ req.body.description, req.body.filepath, req.body.album_id ]; postgres.client.query(sql, data, function(err, result) { if (err) { // We shield our clients from internal errors, but log them console.error(err); res.statusCode = 500; return res.json({ errors: ['Failed to create photo'] }); }
var newPhotoId = result.rows[0].id; var sql = 'SELECT * FROM photo WHERE id = $1'; postgres.client.query(sql, [ newPhotoId ], function(err, result) { if (err) { // We shield our clients from internal errors, but log them console.error(err); res.statusCode = 500; return res.json({ errors: ['Could not retrieve photo after create'] }); }
// The request created a new resource object res.statusCode = 201;
// The result of CREATE should be the same as GET res.json(result.rows[0]); }); });
The concepts we have applied for the POST route can also be applied to the PATCH route with basic changes to the SQL query.
Now, how do we test this?
You could us cURL and manually construct the HTTP request from scratch via the Terminal but that’s complicated for beginners. A cool tool for testing API requests is Insomnia REST Client. It’s a Google Chrome app, so you can run it from your Chrome browser.
Testing requests with Insomnia.rest
Once you’ve installed the Chrome app, launch it. You’ll have a screen that looks like this screen below:
We simply enter a name for our first request, which I’ve named Create Photo, and click the Create button.
You’ll now be dropped into the request creation screen. It has a dropdown for the HTTP method/verb, the address and two big boxes. The left box is for your potential request body, the right box is to display the result of the request. To run the request you simply press the arrow on the right of the address bar.
This is a great debugging tool for creating requests arbitrarily for different APIs (you can save them too, for future use).
What about errors?
Errors are just other forms of responses. These can be blank or data responses (depending on error type or API design).
Typical practice for JSON REST APIs is to return an errors array in the body of the response, for example if we were responding to a bad request to create a new photo we might do something like this:
This allows the client to predict how an error message manifests itself (by checking for the errors key in the response, and facilitates returning more than one error message at once.
We saw a very basic error response in our middleware example when the query cannot find the particular row by ID.
You now have the basics
So there we have it. We’ve created the basic functions of a REST API:
Created routes
Looking up resources in the database based on parameters
Create resources in the database base on request body
If you have any questions or comments don’t hesitate to contact me directly via Twitter @jeffandersen, or simply comment on this post.
This tutorial has been built specifically for purpose of exploring creation of a Node.js REST API with UIT Startup immersion students. Check out UIT at http://uitstartup.org or on Twitter @UITStartup