Understanding Express Routes

Exclusive offer: get 50% off this eBook here
Express Web Application Development

Express Web Application Development — Save 50%

Learn how to develop web applications with the Express framework from scratch book and ebook

$29.99    $15.00
by Hage Yaapa | July 2013 | Web Development

This article is about routes the request interfaces to your application. We have seen and used some routes in the previous articles, but there is much more to routes than creating one by giving a name and including a callback function to handle the request.

In this article by Hage Yaapa, author of Express Web Application Development, you will get a deeper insight into how routes work and how you can customize them to make your application more flexible and powerful.

You will learn the following in this article.

  • How to define routes

  • How to handle routes

  • How to organize routes

     

(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

  • DELETE

  • HEAD
  • TRACE

  • OPTIONS

  • CONNECT

  • PATCH

  • M-SEARCH

  • NOTIFY

  • SUBSCRIBE

  • UNSUBSCRIBE

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:

app.use(app.router);

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.

Express Web Application Development Learn how to develop web applications with the Express framework from scratch book and ebook
Published: June 2013
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

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:

HTTP Verb

Path

Module Method

Description

GET

/users

index

Lists users

GET

/users/new

new

The form to create a new

user

POST

/users

create

Processes new user form

submission

GET

/users/:id

show

Shows user with ID :id

GET

/users/:id/

edit

edit

Form to edit user with

ID :id

PUT

/users/:id

update

Processes user edit form

submission

DELETE

/users/:id

destroy

Deletes user with ID

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

Summary

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:


Express Web Application Development Learn how to develop web applications with the Express framework from scratch book and ebook
Published: June 2013
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Hage Yaapa

Hage Yaapa is a contributing developer of Express and the head of Web and Node.js development at Sourcebits.

He joined one of the best medical schools in India, JIPMER, to become a doctor, but dropped out to pursue his burning passion for computers and the Internet. He has been creating websites and apps since 1999 using a very wide array of web technologies. He is a self-taught programmer and everything he knows about technology, he learned on his own from the Internet and books.

Yaapa blogs about Node.js, Express, and other web technologies on his website www.hacksparrow.com, as Captain Hack Sparrow.

Books From Packt


Agile Web Application Development with Yii1.1 and PHP5
Agile Web Application Development with Yii1.1 and PHP5

Node Web Development
Node Web Development

PHP and MongoDB Web Development Beginner’s Guide
PHP and MongoDB Web Development Beginner’s Guide

HTML5 Web Application Development By Example
HTML5 Web Application Development By Example

Oracle Application Express 4.0 with Ext JS
Oracle Application Express 4.0 with Ext JS

Instant Lift Web Applications How-to [Instant]
Instant Lift Web Applications How-to [Instant]

Socket.IO Real-time Web Application Development
Socket.IO Real-time Web Application Development

Web Application Development with Yii and PHP
Web Application Development with Yii and PHP


Your rating: None Average: 5 (1 vote)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
V
1
U
d
U
e
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software