It is relatively easy to create a test endpoint for web services using Express, perhaps the most popular Node module for web server development. Today, we create a simple shell using a single file. In practice, server applications will be more complex, and this may not be the best strategy for code organization, but it’s a good starting point.
Start out by creating a file called server.js and require a few modules.
var express = require('express'); var bodyParser = require('body-parser'); var util = require('util');
The first of these modules, express is the basic framework for server development. You will use it whether you are developing traditional web sites or, as is the case here, web services. It is also possible to develop server applications using the core http and https modules, but you should find that using Express makes your code simpler and more modular. Since Express is not a core module, you’ll need to install it
$ npm install --save express
The next module, body-parser simplifies processing of JSON and a few other formats (notably form data). Finally, util provides several utility functions. It’s a core module, so there is no need to install it, but body-parser must be installed in the same manner as express.I
The next step is to create the application object, an instance or express, and set the server port.
var app = express(); app.set('port', process.env.PORT || 3000);
It is somewhat traditional to use port 3000 for node applications. This code actually looks for an environment variable named ‘PORT’ and uses its value as the port on which the application should listen. If it’s not present, the code relies on lazy evaluation of boolean expressions to fall over to 3000. That port value is later used when
//This function starts the server. function startServer() { app.listen(app.get('port'), function () { util.log('Sever started on %s mode in port %d ...', app.get('env'), app.get('port')); }) };
We recommend using a separate function here to start the server (start listening). That way, the function can be exported or invoked directly. If you want to be able to run your server directly using
$ node server.js
you can use code such as the following
//If file is run directly, start the server. Otherwise, export the application (express) object. if (require.main === module) { startServer(); } else { module.exports = startServer; }
In a real application, the code that actually starts the server may reside in another file, and you’ll want to export startServer(). This code handles both scenarios.w0
At this point, we’re ready to start adding routes. A route is essentially a URL, a template, or a pattern that the URL should match, coupled with an HTTP method and a function that will be invoked on a request using the specified URL and method. In Express, the function is called a middleware. This may seem like odd terminology, but it is common for requests to pass through several middlewares, all but the last of which is a link in the chain of actions that are performed by the server to handle the given request.
Now, in our example, we are assuming that the request body (if present) will always be JSON, so we add a middleware (which should be at the top) that is handles parsing of request bodies
app.use(bodyParser.json());
Note that we used app.use here. That’s because the middleware bodyParser.json() should be used for all requests with bodies (and not, say, just PUT or POST). We also don’t specify a URL because this middleware should always be used.
Note, by the way, that middlewares are used (where applicable) in the order they appear ion your source code, so oder matters. This may be counterintuitive to programmers used to functional languages. If you are familiar with the connect module, you will know that the pipelining of middleware is made explicit in connect, but Express doesn’t work this way, and you have to pay attention to the order in which middleware appears in the program source.
Having said that, let’s add another middleware. We want to return our 404 errors in JSON format, and Express doesn’t do that by default. One easy way to do this is to create a wildcard route and put it at the end
Again, we’re using app.use because this route isn’t method specific, but here we have included an explicit wildcard. This is an example of how pattern matching can be used for the URL.
//Add a wildcard route to ensure that a JSON response is sent in other cases. app.use('*', function (req, res) { res.status(400).json({status: 'error', message: 'request not understood'}); });
We also need a route that will handle error conditions. Place this after the preceding one.
//error route app.use(function (err, req, res, next) { console.error(err.stack); res.status(500).json({ status: 'error', message: err.message }); });
So far, we haven’t done anything other than set up a basic framework. We haven’t added any code to handle specific requests. This turns out to be surprisingly easy. Suppose we want to respond to
GET /
with a 200 response with the following JSON as its body
{"status":"ok"}
We can do this by adding a new route
//root path app.get('/', function (req, res) { res.status(200).json({status: 'ok'}); });
Notice that this code uses app.get. The reason for this is that the route is specific to the GET method. Notice also that the URL is the literal string ‘/’. It turns out that the explicit response code of 200 is redundant here because Express defaults to 200, but I’ve made it explicit, anyway. The json() method is a convenience that takes a JavaScript object, formats it as JSON uses it as the response body. (Did you notice that key wasn’t quoted?)
Finally, let’s look at something more complicated
//Update a specific order app.put('/order/:id', function (req, res) { if (!req.body) { return res.status(400).json({status: 'error', message: 'no request body'}); } if (req.is('application/json')) { res.status(200).json({status: 'ok', message: 'update processed'}); } else { res.status(415).json({status: 'error', message: 'media type not supported'}); } });
This route would match a URL such as “/order/10” with the 10 being stored as req.params[‘i] so that we have access to it in the function. We use app.put here because this route will only be used to handle PUT requests. The first thing we do is check to see if a body is present, and if it’s not, we return a 400 error. Next, we look at at the media type, and return 415 (“Unsupported Media Type”) if it’s not “/application/json”. In real life, we might be using a more specific media type, and the code would have to check for supported media types and handle them as appropriate.
This code just returns a reply indicating success regardless of the request body (so long as it’s JSON). In real life, of course, we would either perform a database update and return 200 (“OK”) on success, or perhaps queue it and reply 202 (“Accepted”).
Leave a Reply