nested routes with expressjs

April 12, 2016

Whenever I need to build a quick web application I turn to ExpressJS. It is a fast, minimal, easy to configure web server that puts the E in MEAN). I wanted to build a REST API for a hobby app and found that the docs for how to nest routes are relatively few (see blog, stackoverflow answer). Combining these two resources, I learned a simple method for keeping your routes separate while creating a nested routing structure.

The Plan

I want to create routes that look like these:

  • /foo
  • /foo/bar
  • /foo/42
  • /foo/42/baz
  • /foo/42/baz/123

Using the express-generator and a few simple commands, you can get your server up and running fast:

$ npm install express-generator -g
$ express myapp
$ cd myapp
$ npm install
$ npm start

You will end up with a working directory that looks like this:

├── app.js
├── bin
│   └── www
├── node_modules
│   ├── ...
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.jade
    ├── index.jade
    └── layout.jade

Go ahead and delete the /routes/users.js file and the following lines from app.js

...
var users = require('./routes/users');
...
app.use('/users', users);

Then create a new routes file named /routes/foo.js and and require it from your app server.

...
app.use('/foo', require('./routes/foo'));
...

How to GET /foo

Inside /routes/foo.js, you need to require the router module from ExpressJS, define a route on it, and export it back out.

var express = require('express')
var router = express.Router()

// GET /foo
router.get('/', function (req, res, next) {
  res.send('this is the index for foo')
})

module.exports = router

Everytime you change a route, you need to restart your server. Nevigating to localhost:3000/foo should display the message:

foo

How to GET /foo/bar

Create simple nested routes by requiring the child route file from the parent route file.

var express = require('express')
var router = express.Router()
var bar = require('./bar')

// GET /foo
router.get('/', function (req, res, next) {
  res.send('this is the index for foo')
});

// GET /foo/bar
router.use('/bar', bar) // tell the router to use bar.js for child routes

module.exports = router

Create a new routes file named /routes/bar.js and define a root route the same way you did for /routes/foo.js:

var express = require('express')
var router = express.Router()

// GET /foo/bar
router.get('/', function (req, res, next) {
  res.send('this is the index for bar')
});

module.exports = router

Bounce your server again and navigate to localhost:3000/foo/bar

foobar

How to GET /foo/42

Expecting a parameter? No problem! access URL params directly from the request object: req.params.nameOfParam:

...
// GET /foo/42
router.get('/:number', function (req, res, next) {
  res.send('this is foo #' + req.params.number)
})

module.exports = router

Navigate to localhost:3000/foo/42 to see the result.

fooNumber

How to GET /foo/42/baz

Getting a child route from a parameterized parent is where I was getting confused. It turns out that you need to configure the router a little differently when you are passing params through.

...
var baz = require('./baz')

...
// GET /foo/42/baz
router.use('/:number/baz', baz)

module.exports = router

You need to pass an options object to express.Router to merge the params from any parent route:

var express = require('express')
var router = express.Router({mergeParams: true}) // don't forget the parent params!

// GET /foo/42/baz
router.get('/', function (req, res, next) {
  // the param name is from the parent as well
  res.send('this is the baz for foo#' + req.params.number);
})

module.exports = router

Navigate to localhost:3000/foo/42/baz to enjoy your success.

fooNumberBaz

How to GET /foo/42/baz/123

Parameterizing a child route is the same process as for the parent route:

var express = require('express')
var router = express.Router({mergeParams: true})

// GET /foo/42/baz
router.get('/', function (req, res, next) {
  res.send('this is the baz for foo#' + req.params.number);
})

// GET /foo/42/baz/123
router.get('/:id', function (req, res, next) {
  res.send('baz #' + req.params.id +for foo #’ + req.params.number)
})

module.exports = router

Navigate to localhost:3000/foo/42/baz/123 to enjoy your success.

fooNumberBazNumber

TADA! a model of nested routes that you can apply to any application.


Katie Leonard

Mostly Katie explaining things to herself.

© 2025