Understanding Express Routes

10 min read

(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.


Please enter your comment!
Please enter your name here