TL;DR: This text is an excerpt (Chapter 9) from Pro Express.js: Master Express.js—The Node.js Framework For Your Web Development. The book will be released next week (December 24, 2014), and we’ll announce a great limited-time offer on it on Sunday, December 28, 2014. So stay tuned… and happy Holidays!!!
Good web applications must have informative error messages to notify clients exactly why their request has failed. Errors might be caused either by the client (e.g., wrong input data) or by the server (e.g., a bug in the code).
The client might be a browser, in which case the application should display an HTML page. For example, a 404 page should display when the requested resource is not found. Or the client might be another application consuming our resources via the REST API. In this case, the application should send the appropriate HTTP status code and the message in the JSON format (or XML or another format that is supported). For these reasons, it’s always the best practice to customize error-handling code when developing a serious application.
In a typical Express.js application, error handlers follow the routes. Error handling deserves its own section of the book because it’s different from other middleware. After the error handlers, we’ll cover the Express.js application methods and ways to start the Express.js app. Therefore, the major topics of this chapter are as follows:
- Error handling
- Running an app
Error Handling
Because of the asynchronous nature of Node.js and callback patterns, it’s not a trivial task to catch and log for future analysis the state in which errors happen. In the Chapter 17 of Pro Express.js, we cover the use of domains with Express.js apps. The use of domains for error handling in Express.js is a more advanced technique and, for most implementations right out of the box, framework’s built-in error handling might prove sufficient (along with custom error handling middleware).
We can start with the basic development error handler from our ch2/cli-app
example in github.com/azat-co/proexpressjs/tree/master/ch2. The error handler spits out the error status (500, Internal Server Error), stack trace, and error message. It is enabled by this code only when the app is in development mode:
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
Tip: app.get('env')
is a convenient method for process.env.NODE_ENV
; in other words, the preceding line can be rewritten with process.env.NODE_ENV === 'development'
This makes sense because error handling is typically used across the whole application. Therefore, it’s best to implement it as middleware.
For custom error-handler implementations, the middleware is the same as any other except that it has one more parameter, error
(or err
for short):
// Main middleware
app.use(function(err, req, res, next) {
// Do logging and user-friendly error message display
console.error(err);
res.status(500).send();
});
// Routes
We can use res.status(500).end()
to achieve a similar result, because we’re not sending any data (e.g., an error message). It’s recommended to send at least a brief error message, because it will help the debugging process when problems occur. In fact, the response can be anything: JSON, text, a redirect to a static page, or something else.
For most front-end and other clients, the preferred format is, of course, JSON:
app.use(function(err, req, res, next) {
// Do logging and user-friendly error message display
console.error(err);
res.status(500).send({status:500, message: 'internal error', type:'internal'});
})
Note: Developers can use the req.xhr
property or check if the Accept
request header has the application/json value
.
The most straightforward way is to just send a text:
app.use(function(err, req, res, next) {
// Do logging and user-friendly error message display
console.error(err);
res.status(500).send('internal server error');
})
To simply render a static error page with the name
500 (template is the file 500.jade
, the engine is Jade) and the default extension, we could use:
app.use(function(err, req, res, next) {
// Do logging and user-friendly error message display
console.error(err);
// Assuming that template engine is plugged in
res.render('500');
})
Or we could use the following, if we want to overwrite the file extension, for a full filename of 500.html
:
app.use(function(err, req, res, next) {
// Do logging and user-friendly error message display
console.error(err);
// Assuming that template engine is plugged in
res.render('500.html');
})
We can also use res.redirect()
:
app.use(function(err, req, res, next) {
// Do logging and user-friendly error message display
res.redirect('/public/500.html')
})
Always using proper HTTP response statuses such as
401, 400, 500, and so on, is recommended. Refer to List 9–1 for a quick reference.
List 9–1. Main HTTP Status Codes
- 200- OK; Standard response for successful HTTP requests
- 201- Created; Request has been fulfilled. New resource created
- 204- No Content; Request processed. No content returned
- 301- Moved Permanently; This and all future requests directed to the given URI
- 304- Not Modified; Resource has not been modified since last requested
- 400- Bad Request; Request cannot be fulfilled due to bad syntax
- 401- Unauthorized; Authentication is possible, but has failed
- 403- Forbidden; Server refuses to respond to request
- 404- Not Found; Requested resource could not be found
- 500- Internal Server Error; Generic error message when server fails
- 501- Not Implemented; Server does not recognize method or lacks ability to fulfill
- 503- Service Unavailable; Server is currently unavailable
Tip: For the complete list of available HTTP methods, please refer to RFC 2616 at www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.
This is how we can send the status 500 (Internal Server Error) without sending back any data:
app.use(function(err, req, res, next) {
// Do logging and user-friendly error message display
res.end(500);
})
To trigger an error from within our request handlers and middleware, we can just call:
app.get('/', function(req, res, next){
next(error);
});
Or, if we want to pass a specific error message, then we create an Error object and pass it to next()
:
app.get('/',function(req,res,next){
next(new Error('Something went wrong :-('));
});
It would be a good idea to use the return
keyword for processing multiple error-prone cases, and combine both of the previous approaches. For example, we pass the database error
to next()
, but an empty query result will not cause a database error (i.e.,error
will be null
), so we check for this condition with !users
:
// A GET route for the user entity
app.get('/users', function(req, res, next) {
// A database query that will get us any users from the collection
db.get('users').find({}, function(error, users) {
if(error)return next(error);
if (!users) return next(new Error('No users found.'))
// Do something, if fail the return next(error);
res.send(users)
});
For complex apps, it’s best to use multiple error handlers. For example, use one for XHR/AJAX requests, one for normal requests, and one for generic catch-everything-else. It’s also a good idea to use named functions (and organize them in modules) instead of anonymous ones.
For an example of this type of advanced error handling, refer to Chapter 22 of Pro Express.js.
Tip: There’s an easy way out in regards to managing error handling that is especially good for development purposes. It’s called errorhandler
(https://www.npmjs.org/package/errorhandler) and it has the default error handlers for Express.js/Connect.js. For more information on errorhandler
, refer to the Chapter 4 of Pro Express.js.
Running an App
The Express.js class provides a few app-wide objects and methods on its object, which is app in our examples. These objects and methods are recommended because they can improve code reuse and maintenance. For example, instead of hard-coding the number 3000 everywhere, we can just assign it once with app.set('PORT', 3000);.
Then, if we need to update it later, we have only one place where it needs to be changed. Therefore, we’ll cover the following properties and methods in this section:
app.locals
app.render()
app.mountpath
app.on('mount', callback)
app.path()
app.listen()
app.locals
The app.locals
object is similar to the res.locals
object (discussed in Chapter 8 of Pro Express.js) in the sense that it exposes data to templates. However, there’s a main difference: app.locals
makes its properties available in all templates rendered by app
, while res.locals
restricts them only to that request. Therefore, developers need to be careful not to reveal any sensitive information via app.locals
. The best use case for this is app-wide settings such as locations, URLs, contact info, and so forth. For example:
app.locals.lang = 'en';
app.locals.appName = 'HackHall';
The app.locals
object can also be invoked like a function:
app.locals([
author: 'Azat Mardan',
email: 'hi@azat.co',
website: 'http://proexpressjs.com'
]);
app.render()
The app.render()
method is invoked either with a view name and a callback or with a view name, data, and a callback. For example, the system might have an e-mail template for a “Thank you for signing up” message and
another for “Reset your password”:
var sendgrid = require('sendgrid')(api_user, api_key);
var sendThankYouEmail = function(userEmail) {
app.render('emails/thank-you', function(err, html){
if (err) return console.error(err);
sendgrid.send({
to: userEmail,
from: app.get('appEmail'),
subject: 'Thank you for signing up',
html: html // The html value is returned by the app.render
}, function(err, json) {
if (err) { return console.error(err); }
console.log(json);
});
});
};
var resetPasswordEmail = function(userEmail) {
app.render('emails/reset-password',{token: generateResetToken()}, function(err, html){
if (err) return console.error(err);
sendgrid.send({
to: userEmail,
from: app.get('appEmail'),
subject: 'Reset your password',
html: html
}, function(err, json) {
if (err) { return console.error(err); }
console.log(json);
});
});
};
Note: The sendgrid module used in the example is available at NPM
and GitHub.
app.mountpath
The app.mountpath
property is used in the mounted/sub apps. Mounted apps are sub-apps that can be used for better code reuse and organization. The app.mountpath
property returns the path on which app is mounted.
For example, in ch9/app-mountpath.js
(github.com/azat-co/proexpressjs/tree/master/ch9) there are two sub applications: post
and comment
. The post is mounted on the /post
path of app, while comment is mounted on /comment
of post. As a result of logs, mountpath
returns values /post
and /comment
:
var express= require('express'),
app = express(),
post = express(),
comment = express();
app.use('/post', post);
post.use('/comment', comment);
console.log(app.mountpath); // ''
console.log(post.mountpath); // '/post'
console.log(comment.mountpath); // '/comment
app.on(‘mount’, function(parent){…})
The mount is triggered when the sub app is mounted on a specific path of a parent/main app. For example, in ch9/app-on-mount.js
, we have two sub apps with on mount event listeners that print parents’ mountpaths. The values of the paths are /
for post
’s parent (app)
and /post
for comment
’s parent (post)
:
var express= require('express'),
app = express(),
post = express(),
comment = express();
post.on('mount', function(parent){
console.log(parent.mountpath); // '/'
})
comment.on('mount', function(parent){
console.log(parent.mountpath); // '/post'
})
app.use('/post', post);
post.use('/comment', comment);
app.path()
The app.path()
method will return the canonical path for the Express.js application. This is useful if you are using multiple Express.js apps mounted to different routes (for better code organization).
For example, you have comments resource (routes related to comments) for posts by the way of mounting the comment
app on the /comment
path of the post
app. But you can still get the “full” path with comment.path()
(from ch9/app-path.js
):
var express= require('express'),
app = express(),
post = express(),
comment = express();
app.use('/post', post);
post.use('/comment', comment);
console.log(app.path()); // ''
console.log(post.path()); // '/post'
console.log(comment.path()); // '/post/comment'
app.listen()
The Express.js app.listen(port, [hostname,] [backlog,] [callback])
method is akin to server.listen()
method is akin to server.listen()
from the core Node.js http
module. This method is one of the ways to start an Express.js app. The port is a port number on which the server should accept incoming requests. The hostname
is the name of the domain. You might need to set it when you deploy your apps to the cloud. The backlog
is the maximum number of queued pending connections. The default is 511
. And the callback
is an asynchronous function that is called when the server is booted.
To spin up the Express.js app directly on a particular port (3000):
var express = require('express');
var app = express();
// ... Configuration
// ... Routes
app.listen(3000);
This approach was created by Express.js Generator in the ch2/hello.js
and ch2/hell-name.js
examples in the Chapter 2 of Pro Express.js. In it, the app.js
file doesn’t start a server, but it exports the object with:
module.exports = app
We don’t run the app.js
file with $ node app.js
either. Instead, we launch a shell script www
with $ ./bin/www
. The shell script has this special string on its first line:
#!/usr/bin/env node
The line above turns the shell script into a Node.js program. This program imports the app
object from the app.js
file, sets the port, and starts the app
server with listen()
and a callback
:
var debug = require('debug')('cli-app');
var app = require('../app');
app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function() {
debug('Express server listening on port ' + server.address().port);
});
Having your server object exported as a module is necessary when another process requires the object, e.g., a testing framework. In the previous example, the main server file (ch2/cli-app/app.js
) exported the object and there is no way of starting the server with $ node app
. If you don’t want to have a separate shell file for launching the server, but still want to export the server when you need to, you can use the following trick. The gist of this approach is to check whether the module is a dependency with require.main === module
condition. If it’s true, then we start the application. If it’s not, then we expose the methods and the app
object.
var server = http.createServer(app);
var boot = function () {
server.listen(app.get('port'), function(){
console.info('Express server listening on port ' + app.get('port'));
});
}
var shutdown = function() {
server.close();
}
if (require.main === module) {
boot();
} else {
console.info('Running app as a
module');
exports.boot = boot;
exports.shutdown = shutdown;
exports.port = app.get('port');
}
Another way to start a server besides app.listen()
is to apply the Express.js app to the core Node.js server function. This is useful for spawning an HTTP server and an HTTPS server with the same code base:
var express = require('express');
var https = require('https');
var http = require('http');
var app = express();
var ops = require('conf/ops');
//... Configuration
//... Routes
http.createServer(app).listen(80);
https.createServer(ops, app).listen(443);
You can create a self-signed SSL certificate (for example, the server.crt file
) to test your HTTPS server locally for development purposes with OpenSSL
by running these commands:
$ sudo ssh-keygen -f host.key
$ sudo openssl req -new -key host.key -out request.csr
$ sudo openssl x509 -req -days 365 -in request.csr -signkey host.key -out server.crt
The OpenSSL is an open-source implementation of Secure Socket Layer (SSL) protocol and a toolkit. You can find more about it at https://www.openssl.org. When you use OpenSSL, Chrome and many other browsers will complain with a warning about self-signed certificates—you can ignore it by clicking Proceed anyway (see Figure 9–1 ).
Tip: To install OpenSSL
on Mac OS X, run $ brew install OpenSSL
. On Windows, download the installer from http://nuwin32.sourceforge.netpackages/openssl.htm. On Ubuntu, run apt-get install OpenSSL
.
After server.crt
is ready, feed it to the https.createServer()
methods like this (the ch9/app.js
file):
var express = require('express');
var https = require('https');
var http = require('http');
var app = express();
var fs = require('fs');
var ops = {
key: fs.readFileSync('host.key'),
cert: fs.readFileSyn('server.crt') ,
passphrase: 'your_secret_passphrase'
};
app.get('/', function(request, response){
response.send('ok');
});
http.createServer(app).listen(80);
https.createServer(ops, app).listen(443);
The passphrase is the one you used during the certificate creation with OpenSSL
. Leave it out if you didn’t put in any passphrase. To start the process, you might have to use sudo, such as $ sudo node app
.
If everything worked well, you should see an okay message as shown in Figure 9–2.
Finally, if your application performs a lot of blocking work, you might want to start multiple processes with cluster
module. This topic is covered in Chapter 13 of Pro Express.js.
Summary
This chapter covered multiple ways to implement error handlers, the app
object interface, and ways to start the Express.js server. This concludes Part 2, “Deep API Reference.” Hopefully, you’ve learned many new properties and methods of the Express.js framework’s objects, such as response, request, and the app itself. If you had any doubts about middleware, then Chapter 4 of Pro Express.js cleared any concerns. Last but not least, we covered routing, error handling and template utilization topics. All these topics built your foundation so you can apply this knowledge to creating amazing and exciting new apps with Express.js.
As the reference part of the book is over, we are moving into more practical and complex topics, which include “how to use X” or “how to do Y” examples. Onward to Part 3, “Solving Common and Abstract Problems.”
PS: If you liked this text, then you might enjoy these Pro Express.js excerpts as well:
- Express.js Security Tips
- LoopBack 101: Express.js on Steroids
- Sails.js 101
- Secret Express.js Settings
PS2: Pro Express.js: Master Express.js—The Node.js Framework For Your Web Development will be released next week, and we’ll announce a great limited-time offer on it soon.
Why is the error handling wrapped in conditional to check the environment? Isn’t the error needed on production as well?
this error handler doensn’t provide a paramenters now,
var express = require(‘express’);
var bodyParser = require(‘body-parser’);
var app = express();
// use body parser so we can get info from POST and/or URL parameters
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json()); // for parsing application/json
app.get(‘/’, function(req, res){
res.send(‘hello world’);
});
app.use(function (err, req, res, next) {
res.status(404).send(‘ㄣ0ㄣ error’);
});
app.listen(3000);