October 3, 2015 • 16 minute read
Getting to grips with Express.js
Express is a popular Node module that simplifies the development of Web applications. It is comparable to Ruby's Sinatra framework and Python's Flask framework in that it makes it simple to respond to HTTP requests at defined endpoints and serve up responses, typically HTML generated by a view engine or JSON data.
Getting started with Express
Most guides to Express start off by installing express-generator
, an npm module that generates a boilerplate Express app, complete with basic error handling, sample routes and views. Rather than do the same, let's see how to build an Express app from scratch.
Create a new directory for this project called expressapp
. Navigate to this directory in your terminal and run the following command to create a package.json
file.
$ npm init
Next, install express
with the following command. The --save
flag is passed to instruct npm
to include express
as a dependency in the package.json
file. This will allow it to be installed in future by running npm install
.
$ npm install --save express
When the command has finished, Express will be installed in your project's node_modules
subdirectory, and npm
will output a list of its dependencies to the terminal. Next, let's build our first Express app. Create a new file in your project directory named app.js
, and add the code below to it.
app.js
source code
var express = require('express');
var app = express();
app.get('/', function(req, res) {
res.send('Hello, world!');
});
var server = app.listen(3000, function() {
console.log('Express app running on port %s', server.address().port);
});
Run the app using the following command:
$ node app.js
You should see the following message:
Express app running on port 3000
Open your preferred Web browser and navigate to http://localhost:3000. You should see the message Hello, world! displayed on the page. Congratulations, you've just created your first Express app! Let's explore the code to figure out what exactly is going on.
var express = require('express');
var app = express();
First, we load the express
module into our app, and we execute it, assigning the result to the app
variable.
app.get('/', function(req, res) {
res.send('Hello, world!');
});
We then use the get
method to define a route. This method accepts two parameters - the URI endpoint for the route, in this case the root of the app or /
; and a callback function which will be executed when someone accesses this route. Within the callback, we have the req
variable which is the request object, and the res
variable which is a writable stream containing the response that is returned to the user.
In our app, we are using the send
method on the response object to send the message Hello, world! to the user.
var server = app.listen(3000, function() {
console.log('Express app running on port %s', server.address().port);
});
Finally, we use the listen
method to tell Node that we want to listen for incoming connections to our app. The first argument to this method is the port number to listen on, in this case 3000
; and the second argument is a callback function that will be executed when completed. Here, we are logging a message to the terminal to let us know that the app is running on port 3000.
The server will continue to run until you explicitly stop it, typically using Ctrl+C in your terminal window. You'll learn how to run Node apps in the background and how to ensure they stay alive in a future post.
Routing
In the previous section, you created a simple app that listens for HTTP GET requests and returns a response. It is just as simple to listen for requests made using other HTTP methods. For example, to listen for a POST request, you would write:
app.post('/person', function(req, res) {
//Do something
});
The same applies for other HTTP methods, including PUT and DELETE. You can also write code that handles all HTTP methods, that hands down control to a subsequent handler once it's finished. This is particularly useful if you have code that should run regardless of the HTTP method, but also want to do something specific for individual request methods.
Handling multiple HTTP methods
app.all('/person', function(req, res, next) {
console.log('Somebody made a %s request', req.method);
next();
});
app.get('/person', function(req, res) {
res.send('Hello from the GET handler!');
});
app.post('/person', function(req, res) {
res.send('Hello from the POST handler!');
});
In this example, a message will be logged to the terminal regardless of what request method was used. Then, control will be passed down to the next handler for the request using the next
method.
Tip: Testing GET requests is easy, you simply point to a URL with your Web browser and the response will be displayed. To make testing POST (and other methods) requests easy, check out the Postman Chrome extension.
Another approach to defining handlers for multiple HTTP methods is to to use the route
method. This allows you to chain handlers in a single statement. The main benefit of this approach is that it saves you from duplicating the endpoint on each method. The following listing illustrates the previous example, refactored to use the route
method.
Using the route
method to chain HTTP method handlers
app.route('/person')
.all(function(req, res, next) {
console.log('Somebody made a %s request', req.method);
next();
})
.get(function(req, res) {
res.send('Hello from the GET handler!');
})
.post(function(req, res) {
res.send('Hello from the POST handler!');
});
Express routes enable you to define placeholders in the route that map to parameters in the request. This is particularly useful for building a RESTful API, where you accept resource identifiers in the URI.
Accepting parameters in the URI
app.get('/person/:id', function(req, res) {
res.send('Return the resource with the ID ' + req.params.id);
});
In this example, if you navigate to http://localhost:3000/person/1234 you will see the message Return the resource with the ID 1234. You could easily take this ID and use it to search for a record in a database and return the relevant data as JSON.
As your Express apps become larger, it can be difficult to maintain many routes in a single file. Thankfully, Express includes a class named express.Router
that alleviates this problem. This enables you to create modules that define a series of routes. You can then load these modules in your main app and bind the routes within the module to a specific endpoint.
Create a new file named person.js
and save it in the same directory as app.js
. Add the following code to this new file.
person.js
source code
var express = require('express');
var router = express.Router();
router.get('/', function(req, res) {
res.send('Person list');
});
router.get('/new', function(req, res) {
res.send('Add new person');
});
module.exports = router;
Next, change the app.js
file so that its contents match the code below.
app.js
source code
var express = require('express');
var person = require('./person');
var app = express();
app.use('/person', person);
var server = app.listen(3000, function() {
console.log('Express app running on port %s', server.address().port);
});
Run the app again using $ node app.js
. You should be able to navigate to the URLs http://localhost:3000/person and http://localhost:3000/person/new and see the responses Person list and Add new person, respectively.
2.3. JSON Responses
We've already seen how we can use the send
method on the response object to return a string of text to the user when they access a route. The response object has many other options, however.
Express is often used to build APIs. It is common practice in modern APIs to return data in the JavaScript Object Notation (JSON) data interchange format. Express makes it very easy to return JSON from your routes. You simple use the json
method on the response object to take a JavaScript object or array and return it as JSON. Let's tweak the previous example so that the route that previously returned the text Person list now returns JSON representing an array of person objects.
Returning a JSON response
router.get('/', function(req, res) {
var persons = [
{firstName: "Joe", lastName: "Lennon"},
{firstName: "Jill", lastName: "Lennon"}
];
res.json(persons);
});
When you run the app again and navigate to http://localhost:3000/person, you will see a response similar to the following:
[{"firstName":"Joe","lastName":"Lennon"},{"firstName":"Jill","lastName":"Lennon"}]
Warning: Whenever you make changes to the code behind an Express app, you will need to kill the program and run it again to see the changes you have made take effect.
Express automatically encodes the array as JSON and sends the response with the appropriate headers (i.e. it sets the Content-Type to application/json
).
Express also makes it easy to support requests with JSONP callbacks via the jsonp
method of the response object. This works exactly the same way as the json
method, except that it looks for the name of a callback function in the request's query string, and passes the JSON data to this callback function in the response. If you change the previous example to use jsonp
, and then navigate to http://localhost:3000/person?callback=list, you will see the following response:
/**/ typeof list === 'function' && list([{"firstName":"Joe","lastName":"Lennon"},{"firstName":"Jill","lastName":"Lennon"}]);
This makes it easy to support cross-domain API requests without running into Cross-Origin Resource Sharing (CORS) errors.
File Downloads
Express also makes it easy to serve up files that can be downloaded by the user. There are two methods available - sendFile
and download
. The primary difference between these two methods is that the download
method will set a Content-Disposition
header on the response, which will force most Web browsers to initiate a file download, whereas files served with sendFile
will typically be opened by an application associated with the file type if available rather than explicitly downloaded.
The response object in Express is a writable stream, which means that you can pipe a series of readable streams to it to perform file manipulation and send the results in chunks. This allows you to perform intensive processes on large files without causing undue stress on your server.
Let's look at an example of this, where we will read JSON data from a file, transform it to CSV and then send this as a file download to the user with Express. To get this working, we will need to use a few additional third-party Node modules - JSONStream
, sprintf
and event-stream
. Install these with the following command:
$ npm install --save JSONStream sprintf event-stream
Next, create a new file named person.json
and add the following code to it:
person.json
source code
[{"firstName":"Joe","lastName":"Lennon"},{"firstName":"Jill","lastName":"Lennon"}]
Next, open the person.js
file we created earlier in this post and change it so that its contents match the following code.
person.js
source code
var express = require('express'),
fs = require('fs'),
JSONStream = require('JSONStream'),
sprintf = require('sprintf'),
es = require('event-stream');
var router = express.Router();
router.get('/', function(req, res) {
res.attachment('person.csv');
fs.createReadStream('person.json')
.pipe(JSONStream.parse('*'))
.pipe(es.mapSync(function(data) {
return sprintf.sprintf('%(firstName)s,%(lastName)s', data);
}))
.pipe(es.join('\n'))
.pipe(es.wait())
.pipe(es.mapSync(function(data) {
return 'FirstName,LastName\n' + data;
}))
.pipe(res);
});
module.exports = router;
Run your app again with $ node app.js
and navigate to http://localhost:3000/person. A file named person.csv
will be downloaded by your browser, and if you open it, you should see the following contents:
FirstName,LastName
Joe,Lennon
Jill,Lennon
There's quite a bit of new code in this example, so let's take a look at it.
var express = require('express'),
fs = require('fs'),
JSONStream = require('JSONStream'),
sprintf = require('sprintf'),
es = require('event-stream');
Here we are simply loading the newly installed modules JSONStream
, sprintf
and event-stream
.
res.attachment('person.csv');
This uses the attachment
method of the response object to set the Content-Disposition
response header, setting the filename to person.csv
.
fs.createReadStream('person.json')
.pipe(JSONStream.parse('*'))
This code creates a readable stream for the person.json
file and pipes it through to the parse
method of the JSONStream
module. This will take the JSON data and parse it into JavaScript.
.pipe(es.mapSync(function(data) {
return sprintf.sprintf('%(firstName)s,%(lastName)s', data);
}))
.pipe(es.join('\n'))
.pipe(es.wait())
.pipe(es.mapSync(function(data) {
return 'FirstName,LastName\n' + data;
}))
It might look like there's a lot going on here, but it's actually pretty simple. This code uses the the mapSync
method of the event-stream
module along with the sprintf
module to convert JavaScript objects into CSV lines. This is then piped into the join
method, which will join the CSV lines using the \n
newline character. The wait
method will wait until all the chunks of the stream have been processed and concatenate the chunks together. Finally, we wrap the entire thing in a mapSync
that adds the CSV header line.
.pipe(res);
Earlier I mentioned that Express response objects are themselves streams - here we simply pipe the stream to the response object. The data from the stream will be sent to the response, which will be served as a file download due to the attachment
call we made earlier.
Rendering view templates
At this point we have seen how to return responses that are plain text, JSON data and even file downloads. But for many applications, the bulk of the responses you will want to send will be HTML pages. You could construct a HTML response directly in your Express routes, but I don't need to tell you how bad that would be.
Express supports a number of view engines, the most popular of which is Jade. Jade is a short-hand syntax for HTML that relies on indentation for document structure. It is a decent template language with support for conditionals, iteration, extendable layouts and mixins.
To use Jade as the Express view engine, you first need to install it.
$ npm install --save jade
Next, you need to tell Express that you want to use Jade, and where to look for views when rendering templates. Open the app.js
file and below the line var app = express()
, add the following lines:
var jade = require('jade');
app.set('views', './views');
app.set('view engine', 'jade');
Next, let's create a simple Jade template. Create a new subdirectory named views, and in it create a new file named persons.jade
. Add the code below to this file.
views/persons.jade
source code
html
head
title Persons
body
table
thead
tr
th First Name
th Last Name
tbody
for person in persons
tr
td= person.firstName
td= person.lastName
If you have never seen a template language like Jade before, it may look a bit strange at first. Essentially it's HTML without the angle brackets and closing tags, relying on indentation for structure. The most interesting lines in this example are the following:
for person in persons
tr
td= person.firstName
td= person.lastName
This will iterate over an array of objects named persons
, and for each object in the array it will create a new table row with two cells that output the firstName
and lastName
properties of the object. Note that to output a value you use the =
symbol after the element name.
We will explore Jade in much more detail in a future post.
Next, we need to change our route to use this template. Open person.js
in your project's root directory. Change the contents of this file to the code below.
person.js
source code
var express = require('express'),
fs = require('fs');
var router = express.Router();
router.get('/', function(req, res) {
fs.readFile('person.json', function(err, data) {
var json = JSON.parse(data);
res.render('persons', { persons: json });
});
});
module.exports = router;
If you start your app with $ node app.js
and navigate to http://localhost:3000/person, you should see the table in all its glory.
Static content
Template languages like Jade are great for generating dynamic content, but what about serving up static content such as images, stylesheets and client-side scripts? Simply create a new subdirectory (typically named public
or similar) and add your static files here. To tell Express to serve up the contents of this directory as static files, add a line to your app.js
file, below the app.use
statements you added in the previous section.
app.use(express.static('public'));
This will serve any files in the public
directory as static content in your app. Using the declaration above will serve these files from the root of your app, i.e. /
. For example, if you have the following files:
public/images/profile.jpg
public/scripts/app.js
public/styles/app.css
These will be served at the following locations, respectively:
- http://localhost:3000/images/profile.jpg
- http://localhost:3000/scripts/app.js
- http://localhost:3000/styles/app.css
If you'd prefer to add a prefix to the URI for static content, change the line you added to app.js
as follows:
app.use('/public', express.static('public'));
The files will now be served at the following locations:
- http://localhost:3000/public/images/profile.jpg
- http://localhost:3000/public/scripts/app.js
- http://localhost:3000/public/styles/app.css
Which approach to use is a matter of personal preference. I prefer to use the explicit approach so that I don't run into any confusion down the line due to naming conflicts (where I have a view and a static resource with the same name).
Middleware
Express is entirely built on the concept of middleware - pieces of code that have access to the request object, the response object and the next middleware in the chain. Middleware makes it simple to define code that will be run for many requests, for example to perform validation such as verifying that a user is authenticated. Express middleware can change the request and response objects, send a response or pass on to the next middleware in the stack.
All of the router methods you learned about earlier in this post (i.e. get
, post
, etc.) are middleware methods. There is another method, use
, that can handle any HTTP method, and can even run without a route. This allows you to inject common functionality across your app, such as logging, that will execute on every request that is made.
For example, the following code will write a message to the terminal window with a timestamp, URL, request method and IP address every time anyone visits any URI in the app.
app.use(function(req, res, next) {
var dateTime = new Date().toJSON().slice(0, -5);
console.log('[%s] %s %s %s', dateTime, req.url, req.method, req.ip);
next();
});
Another common use for middleware is to include additional functionality in your Express app. The express.static
method used in the previous example is an example of this type of middleware. In fact, this is the only middleware that is included with Express out-of-the-box - all other middleware must be included from third-party modules.
There are some common middleware options that will prove useful in most applications. For example, body-parser is essential for parsing request bodies that are sent as JSON or form-encoded data. If your Express app includes a HTML form, you'll want to use the body-parser middleware. So let's include that now.
First, install the module from npm
.
$ npm install --save body-parser
Next, in your app.js
file, below the line var app = express();
add the following line:
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
Some other popular Express middleware include:
- compression for gzip compression
- cookie-parser for parsing cookie data
- csurf for CSRF protection
- morgan for HTTP request logging
- passport for authentication
- serve-favicon for favicon serving
- vhost for virtual host configuration
- view-helpers for common view engine helper methods
Error handling
Like routing, error handling in Express is simply an implementation of middleware. The only difference with error handling middleware is that it accepts four arguments instead of three. You should put error handling middleware after all other middleware and routes. An example of an error handler is as follows:
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Unexpected server error occurred!');
});
This will log the details of the error to the console, and will send a response to the user with a 500 error status code and an error message. You can put whatever code you like in here - you can render a view, add errors to a log, send an email, etc.
This error handler will only capture application errors - it won't catch typical 404 "Page Not Found" errors. To handle 404 errors, simply add a regular middleware at the end of your app.js
file - this will be executed only if no other middleware or route has already handled the request - indicating that no resource has been found.
app.use(function(req, res, next) {
res.status(404).send('Not Found!');
});
Finally, if your application encounters an error you can return this from your code so all subsequent non-error handling middleware will be ignored, for example:
app.get('/update', function(req, res, next) {
var err = validateRequest(req);
if(err) return next(err);
res.send('Success');
});
You can view the internal logs used by Express by setting the DEBUG
environment variable to express:*
when starting your app.
$ DEBUG=express:* node app.js
Express uses the debug module for logging events. This module can also be very useful for your own application logging purposes, so it's worth checking out.
Summary
Express is a hugely popular module for Node, and it's easy to see why. It is basic enough to provide a high level of flexibility for your project's needs, but powerful enough to handle the low-level complexity that most projects prefer to ignore. In a future post, I'll take a closer look at the Jade template engine.