Understanding Express Routes

Here’s a comprehensive guide to making the most of Express’s flexibility in building web applications. With lots of screenshots and examples, it’s the perfect step-by-step manual for those with an intermediate knowledge of JavaScript.

(For more resources related to this topic, see here.)

What are Routes?

Routes are URL schema, which describe the interfaces for making requests to your web app. Combining an HTTP request method (a.k.a. HTTP verb) and a path pattern, you define URLs in your app.

Each route has an associated route handler, which does the job of performing any action in the app and sending the HTTP response.

Routes are defined using an HTTP verb and a path pattern. Any request to the server that matches a route definition is routed to the associated route handler.

Route handlers are middleware functions, which can send the HTTP response or pass on the request to the next middleware in line. They may be defined in the app file or loaded via a Node module.

A quick introduction to HTTP verbs

The HTTP protocol recommends various methods of making requests to a Web server. These methods are known as HTTP verbs. You may already be familiar with the GET and the POST methods; there are more of them, about which you will learn in a short while.

Express, by default, supports the following HTTP request methods that allow us to define flexible and powerful routes in the app:

  • GET

  • POST

  • PUT


  • HEAD








GET, POST, PUT, DELETE, HEAD, TRACE, OPTIONS, CONNECT, and PATCH are part of the Hyper Text Transfer Protocol (HTTP) specification as drafted by the Internet Engineering Task Force (IETF) and the World Wide Web Consortium (W3C). M-SEARCH, NOTIFY, SUBSCRIBE, and UNSUBSCRIBE are specified by the UPnP Forum.

There are some obscure HTTP verbs such as LINK, UNLINK, and PURGE, which are currently not supported by Express and the underlying Node HTTP library.

Routes in Express are defined using methods named after the HTTP verbs, on an instance of an Express application: app.get(), app.post(), app.put(), and so on. We will learn more about defining routes in a later section.

Even though a total of 13 HTTP verbs are supported by Express, you need not use all of them in your app. In fact, for a basic website, only GET and POST are likely to be used.

Revisiting the router middleware

This article would be incomplete without revisiting the router middleware.

The router middleware is very special middleware. While other Express middlewares are inherited from Connect, router is implemented by Express itself. This middleware is solely responsible for empowering Express with Sinatra-like routes.

Connect-inherited middlewares are referred to in Express from the express object (express.favicon(), express.bodyParser(), and so on). The router middleware is referred to from the instance of the Express app (app.router)

 To ensure predictability and stability, we should explicitly add router to the middleware stack:


The router middleware is a middleware system of its own. The route definitions form the middlewares in this stack. Meaning, a matching route can respond with an HTTP response and end the request flow, or pass on the request to the next middleware in line. This fact will become clearer as we work with some examples in the upcoming sections.

Though we won't be directly working with the router middleware, it is responsible for running the whole routing show in the background. Without the router middleware, there can be no routes and routing in Express.

Defining routes for the app

we know how routes and route handler callback functions look like. Here is an example to refresh your memory:

app.get('/', function(req, res) { res.send('welcome'); });

Routes in Express are created using methods named after HTTP verbs. For instance, in the previous example, we created a route to handle GET requests to the root of the website. You have a corresponding method on the app object for all the HTTP verbs listed earlier.

Let's create a sample application to see if all the HTTP verbs are actually available as methods in the app object:

var http = require('http'); var express = require('express'); var app = express(); // Include the router middleware app.use(app.router); // GET request to the root URL app.get('/', function(req, res) { res.send('/ GET OK'); }); // POST request to the root URL app.post('/', function(req, res) { res.send('/ POST OK'); }); // PUT request to the root URL app.put('/', function(req, res) { res.send('/ PUT OK'); }); // PATCH request to the root URL app.patch('/', function(req, res) { res.send('/ PATCH OK'); }); // DELETE request to the root URL app.delete('/', function(req, res) { res.send('/ DELETE OK'); }); // OPTIONS request to the root URL app.options('/', function(req, res) { res.send('/ OPTIONS OK'); }); // M-SEARCH request to the root URL app['m-search']('/', function(req, res) { res.send('/ M-SEARCH OK'); }); // NOTIFY request to the root URL app.notify('/', function(req, res) { res.send('/ NOTIFY OK'); }); // SUBSCRIBE request to the root URL app.subscribe('/', function(req, res) { res.send('/ SUBSCRIBE OK'); }); // UNSUBSCRIBE request to the root URL app.unsubscribe('/', function(req, res) { res.send('/ UNSUBSCRIBE OK'); }); // Start the server http.createServer(app).listen(3000, function() { console.log('App started'); });

We did not include the HEAD method in this example, because it is best left for the underlying HTTP API to handle it, which it already does. You can always do it if you want to, but it is not recommended to mess with it, because the protocol will be broken unless you implement it as specified.

The browser address bar isn't capable of making any type of request, except GET requests. To test these routes we will have to use HTML forms or specialized tools. Let's use Postman, a Google Chrome plugin for making customized requests to the server.

We learned that route definition methods are based on HTTP verbs. Actually, that's not completely true, there is a method called app.all() that is not based on an HTTP verb. It is an Express-specific method for listening to requests to a route using any request method:

app.all('/', function(req, res, next) { res.set('X-Catch-All', 'true'); next(); });

Place this route at the top of the route definitions in the previous example. Restart the server and load the home page. Using a browser debugger tool, you can examine the HTTP header response added to all the requests made to the home page, as shown in the following screenshot:

Something similar can be achieved using a middleware. But the app.all() method makes it a lot easier when the requirement is route specified.

Route identifiers

So far we have been dealing exclusively with the root URL (/) of the app. Let's find out how to define routes for other parts of the app.

Routes are defined only for the request path. GET query parameters are not and cannot be included in route definitions.

Route identifiers can be string or regular expression objects.

String-based routes are created by passing a string pattern as the first argument of the routing method. They support a limited pattern matching capability. The following example demonstrates how to create string-based routes:

// Will match /abcd app.get('/abcd', function(req, res) { res.send('abcd'); }); // Will match /acd app.get('/ab?cd', function(req, res) { res.send('ab?cd'); }); // Will match /abbcd app.get('/ab+cd', function(req, res) { res.send('ab+cd'); }); // Will match /abxyzcd app.get('/ab*cd', function(req, res) { res.send('ab*cd'); }); // Will match /abe and /abcde app.get('/ab(cd)?e', function(req, res) { res.send('ab(cd)?e'); });

The characters ?, +, *, and () are subsets of their Regular Expression counterparts.

The hyphen (-) and the dot (.) are interpreted literally by string-based route identifiers.

There is another set of string-based route identifiers, which is used to specify named placeholders in the request path. Take a look at the following example:

app.get('/user/:id', function(req, res) { res.send('user id: ' + req.params.id); }); app.get('/country/:country/state/:state', function(req, res) { res.send(req.params.country + ', ' + req.params.state); }

The value of the named placeholder is available in the req.params object in a property with a similar name.

Named placeholders can also be used with special characters for interesting and useful effects, as shown here:

app.get('/route/:from-:to', function(req, res) { res.send(req.params.from + ' to ' + req.params.to); }); app.get('/file/:name.:ext', function(req, res) { res.send(req.params.name + '.' + req.params.ext.toLowerCase()); });

The pattern-matching capability of routes can also be used with named placeholders. In the following example, we define a route that makes the format parameter optional:

app.get('/feed/:format?', function(req, res) { if (req.params.format) { res.send('format: ' + req.params.format); } else { res.send('default format'); } });

Routes can be defined as regular expressions too. While not being the most straightforward approach, regular expression routes help you create very flexible and powerful route patterns.

Regular expression routes are defined by passing a regular expression object as the first parameter to the routing method.

Do not quote the regular expression object, or else you will get unexpected results.

Using regular expression to create routes can be best understood by taking a look at some examples.

The following route will match pineapple, redapple, redaple, aaple, but not apple, and apples:

app.get(/.+app?le$/, function(req, res) { res.send('/.+ap?le$/'); });

The following route will match anything with an a in the route name:

app.get(/a/, function(req, res) { res.send('/a/'); });

You will mostly be using string-based routes in a general web app. Use regular expression-based routes only when absolutely necessary; while being powerful, they can often be hard to debug and maintain.

Order of route precedence

Like in any middleware system, the route that is defined first takes precedence over other matching routes. So the ordering of routes is crucial to the behavior of an app. Let's review this fact via some examples.

In the following case, http://localhost:3000/abcd will always print "abc*"

, even though the next route also matches the pattern:

app.get('/abcd', function(req, res) { res.send('abcd'); }); app.get('/abc*', function(req, res) { res.send('abc*'); });

Reversing the order will make it print "abc*":

app.get('/abc*', function(req, res) { res.send('abc*'); }); app.get('/abcd', function(req, res) { res.send('abcd'); });

The earlier matching route need not always gobble up the request. We can make it pass on the request to the next handler, if we want to.

In the following example, even though the order remains the same, it will print "abc*" this time, with a little modification in the code.

Route handler functions accept a third parameter, commonly named next, which refers to the next middleware in line. We will learn more about it in the next section:

app.get('/abc*', function(req, res, next) { // If the request path is /abcd, don't handle it if (req.path == '/abcd') { next(); } else { res.send('abc*'); } }); app.get('/abcd', function(req, res) { res.send('abcd'); });

So bear it in mind that the order of route definition is very important in Express. Forgetting this will cause your app behave unpredictably. We will learn more about this behavior in the examples in the next section.

How to handle routes

When a request is made to the server, which matches a route definition, the associated callback functions kick in to process the request and send back a response. These callback functions are responsible for the dynamic behavior of the app; without them routes would simply be dumb interfaces that do nothing at all.

So far, we have been dealing with a single callback function for a route, but a route can have more than one callback function.

As mentioned earlier, the Express routing system is also built around the middleware concept—each route handler has the capability to send a response or pass on the request to the next route-handling middleware in the current or the next matching route.

All of a sudden route handling sounds a little more complicated than what we assumed earlier, doesn't it? Let's find out if it is so.

By now, we are all familiar with how a route definition looks like:

app.get('/', function(req, res) { res.send('welcome'); });

We have been using a single callback function in all our examples so far. So, where do the other callback functions come in and what do they do?

 Route handlers, being middlewares, also have access to the next object, which happens to be the next callback function in the line. To make the next object available to the callback function, pass it along with the req and the res objects to it:

app.get('/', function(req, res, next) { next(); });

If there is no matching callback function after the current callback function, next refers to the built-in 404 error handler, and it will be triggered when you call it.

This is how you specify multiple callbacks for a route:

app.get('/', function(req, res, next) { res.send('one'); }, function(req, res, next) { res.send('two'); }, function(req, res) { res.send('three'); } );

Try guessing what the response will be. Will the server print all of them, or "one" or, "three"?

The server will print just "one". The act of doing a res.send() or send.render() or other similar method terminates the flow of the request then and there; the request is not passed on to any other middleware.

So, how do we specify multiple callbacks for a route, and use them all at the same time? Call the next() function from the callback, without calling any of the methods that terminates the request flow. Here is an example:

app.get('/', function(req, res, next) { res.set('X-One', 'hey!'); next(); }, function(req, res, next) { res.set('X-Two', 'ho!'); next(); }, function(req, res) { res.send("Let's go!"); } );

This route handler stack is composed of three callbacks. The first two add two additional HTTP headers. You can see that the two functions have successfully added the HTTP headers, and the third is printed to the browser:

HTTP headers are protocol-level information sent by an HTTP server in response to a request. They are not displayed by the browser, but can be seen using a traffic analyzer or web development tools such as Firebug and Chrome Developer Tool.

The callback functions can be passed in an array too. The following modification to the code will result in the same response, similar to the one shown in the preceding example, from the server:

var one = function(req, res, next) { res.set('X-One', 'hey!'); next(); }; var two = function(req, res, next) { res.set('X-Two', 'ho!'); next(); }; app.get('/', [one, two], function(req, res) { res.send("Let's go!"); });

You can achieve the same thing again by defining multiple routes for a route path. This is not really recommended, but it will help you to better understand how routes work:

app.get('/', function(req, res, next) { res.set('X-One', 'hey!'); next(); }); app.get('/', function(req, res, next) { res.set('X-Two', 'ho!'); next(); }); app.get('/', function(req, res) { res.send('three'); });

Showing the various ways of assigning callbacks to a route is not a recommendation in any manner; it is just to show you the possibilities. You may most likely stick with the single callback approach, but knowing the fact that you can assign more than one callback to a router in various ways will give you flexibility and and power if the need ever arises.

How to organize routes

So far, our routes and their handlers have been written right in the app file. It might work for small apps, but is not practical for bigger projects. After a certain level of complexity in our app, we will need to organize your routes.

So what is the Express way of organizing routes?

The Express way of organizing routes is—chose what works best for you. Express does not force, recommend, or suggest any form of routing pattern on its developers. However, it provides the capability to implement any sort of routing pattern you may want to implement for your app.

There are three popular ways of organizing routes in an Express app; let's explore them.

Using Node modules

Since route handlers are function, we can modularize our app by using Node nodules to define our route handlers.

Create a directory named routes to keep our route handlers. In the directory, create two basic Node modules: index.js and users.js.

Here is the content for index.js:

exports.index = function(req, res){ res.send('welcome'); };

And, here is the content for users.js:

exports.list = function(req, res){ res.send('Amar, Akbar, Anthony'); };

Now, update the app.js file to include our route handlers:

var express = require('express'); var http = require('http'); var app = express(); // Load the route handlers var routes = require('./routes'); var user = require('./routes/users'); // Add router middleware explicitly app.use(app.router); // Routes app.get('/', routes.index); app.get('/users', user.list); http.createServer(app).listen(3000, function(){ console.log('App started'); });

Start the app and load http://localhost:3000 and http://localhost:3000/users on the browser to see the results.

Now our route handlers reside in separate Node modules, making our app a little more modular. Can the app be made even more modular?

How about moving also the route definitions out of the app.js file?

In our new scheme, the routes directory will be called handlers. So, go ahead and rename routes to handlers. The modules need not be renamed or edited.

Create a new file called routes.js in the app directory. This file will be responsible for loading the route handlers and defining the routes. Here is the content for the file:

// Load the route handlers var routes = require('./handlers'); var user = require('./handlers/users'); module.exports = function(app) { // Define the routes app.get('/', routes.index); app.get('/users', user.list); };

Now modify the app.js file to incorporate the new changes we have made:

var http = require('http'); var express = require('express'); var app = express(); // Explicitly add the router middleware app.use(app.router); // Pass the Express instance to the routes module var routes = require('./routes')(app); http.createServer(app).listen(3000, function() { console.log('App started'); });

Restart the app and reload http://localhost:3000 and http://localhost:3000/users to see the functionality intact.

There you go, an even more modular app.

This method of organizing the routes used the basic module loading capability of Node to introduce modularity in the app. There are other methods of route organization, which introduce a layer of abstraction to create routing patterns. Let's explore two of them: namespaced routing and resourceful routing.

Namespaced routing

Take a look at the following set of route definitions:

app.get('/articles/', function(req, res) { … }); app.get('/articles/new', function(req, res) { … }); app.get('/articles/edit/:id', function(req, res) { … }); app.get('/articles/delete/:id', function(req, res) { … }); app.get('/articles/2013', function(req, res) { … }); app.get('/articles/2013/jan/', function(req, res) { … }); app.get('/articles/2013/jan/nodejs', function(req, res) { … });

As the number of routes and their path segments grow, you will start to wonder if there is any way to organize them better, and flatten the growing pyramid of repeating strings in the path names.

How about the ability to define root paths and defining other routes based on it? That would cut down the repetitive text in path names, right?

Namespaced routing is just about that. You define the routes in your app based on a namespace, which happens to be the root of the path, relative to which other routes are defined.

You will have a better understanding about how namespaced routing works if we re-write the preceding routes using namespaced routing, so let's do that.

Express does not support namespaced routing by default, but it is very easy to enable support by installing a Node module called express-namespace:

$ npm install express-namespace

Now, edit app.js to include express-namespace and redefine the routes using namespaces:

var http = require('http'); var express = require('express'); // express-namespace should be loaded before app is instantiated var namespace = require('express-namespace'); var app = express(); app.use(app.router); app.namespace('/articles', function() { app.get('/', function(req, res) { res.send('index of articles'); }); app.get('/new', function(req, res) { res.send('new article'); }); app.get('/edit/:id', function(req, res) { res.send('edit article ' + req.params.id); }); app.get('/delete/:id', function(req, res) { res.send('delete article ' + req.params.id); }); app.get('/2013', function(req, res) { res.send('articles from 2013'); }); // Namespaces can be nested app.namespace('/2013/jan', function() { app.get('/', function(req, res) { res.send('articles from jan 2013'); }); app.get('/nodejs', function(req, res) { res.send('articles about Node from jan 2013'); }); }); }); http.createServer(app).listen(3000, function() { console.log('App started'); });

Restart the app and load the following URLs in your browser to see namespaced routing in action:

  • http://localhost:3000/articles/

  • http://localhost:3000/articles/edit/4

  • http://localhost:3000/articles/delete/4

  • http://localhost:3000/articles/2013

  • http://localhost:3000/articles/2013/jan

  • http://localhost:3000/articles/2013/jan/nodejs

Namespaces support all the pattern matching and regular expression support we read earlier, so the flexibility and power of defining routes is not compromised by using namespaced routing.

Although we used app.get() for defining all the routes for the sake of simplicity, it is not recommended to actually do so in production. Doing so can leave the resources of your app open to deletion via the most basic and unexpected actions, even by web spiders. Use app.delete() instead, with authentication.

Resourceful routing

Another popular routing pattern is an object-oriented approach called resourceful routing. The idea behind resourceful routing is to create routes based on actions available on objects called resources on the server.

Resources are entities such as users, photos, forums, and so on on the server.

Resourceful routes are defined using a recommended combination of HTTP verbs and path patterns. Corresponding methods are defined in the route handling Node module to perform the necessary actions in the server.

The following table illustrates resourceful routing for a resource called users in the server:



Module Method





Lists users




The form to create a new





Processes new user form





Shows user with ID :id





Form to edit user with

ID :id




Processes user edit form





Deletes user with ID


Resourceful routing is not supported by Express by default. However, enabling it is as easy as installing a Node module named express-resource:

$ npm install express-resource

Next, we need to create a Node module to handle the resourceful routes. Create a file called users.js and implement the resourceful methods in it:

exports.index = function(req, res) { res.send('index of users'); }; exports.new = function(req, res) { res.send('form for new user'); }; exports.create = function(req, res) { res.send('handle form for new user'); }; exports.show = function(req, res) { res.send('show user ' + req.params.user); }; exports.edit = function(req, res) { res.send('form to edit user ' + req.params.user); }; exports.update = function(req, res) { res.send('handle form to edit user ' + req.params.user); }; exports.destroy = function(req, res) { res.send('delete user ' + req.params.user); };

Now modify app.js to use the express-resource module and load the route-handling Node module:

var http = require('http'); var express = require('express'); // Load express-resource BEFORE app is instantiated var resource = require('express-resource'); var app = express(); app.use(app.router); // Load the resourceful route handler app.resource('users', require('./users.js')); http.createServer(app).listen(3000, function() { console.log('App started'); });

Start the app and load the following URLs in your browser to see the resourceful route handlers print the assigned messages:

  • http://localhost:3000/users

  • http://localhost:3000/users/new

  • http://localhost:3000/users/7

  • http://localhost:3000/users/7/edit

For POST, PUT, and DELETE routes, you will have to use a form or a tool such as Postman to see the results:

In a real-world application, POST, PUT, and DELETE methods are called using HTML forms. We are limited to GET examples by the browser address bar which just supports GET requests. 

So you can see, in resourceful routing you just have to specify the resource name and implement the resourceful methods. The task of creating the routes is handled by the underlying express-resource module.

Making a choice

Express aims to be an unopinionated web development framework. Apart from the very basics, it does not impose any software development patterns on the developers. At the same time it is so flexible that you can set up Express to nearly work like Ruby on Rails, plain old PHP, Kohana, Django, or any other web development framework you might have heard about.

The Express ideology of not being opinionated applies to routing too. Routes can be implemented and organized in numerous ways. Some people like to define the routes right in the app file, some like to keep them in a Node module, some like to go the namespaced way, and some prefer the resourceful approach.

There is no one recommended way to implement routes in your app. Each app is different and the needs are different. Resourceful routing in a simple app would be overkill, while not planning the routes for a CMS application would be very bad software development practice.

It is best to be aware about the possible ways of implementing and organizing routes in an Express app, and pick the best according to the need of the application.


In this article, we learned about routes and route handlers in great detail. We can now define flexible routes and route handlers. An important insight we got was the fact that route handers are a middleware system of their own. We also learned the various ways of organizing our routes and route handlers.

Now that we know how to define and handle routes, in the next article let's find out how to send various types of responses from the server.

Resources for Article :

Further resources on this subject:

Books to Consider

comments powered by Disqus