Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djom202/17762b88006b2b4efe4f89761a0dd4fa to your computer and use it in GitHub Desktop.
Save djom202/17762b88006b2b4efe4f89761a0dd4fa to your computer and use it in GitHub Desktop.
Child Routers in Express

Nested Routers in Express.js

Express makes it easy to nest routes in your routers. But I always had trouble accessing the request object's .params when you had a long URI with multiple parameters and nested routes.

Let's say you're building routes for a website www.music.com. Music is organized into albums with multiple tracks. Users can click to see a track list. Then they can select a single track and see a sub-page about that specific track.

At our application level, we could first have a Router to handle any requests to our albums.

const express = require('express');
const app = express();
const albumsRouter = require('./routers/albums');

//...

app.use('/albums', albumsRouter); // Forwards any requests to the /albums URI to our albums Router

//...

Inside our albums.js file...

const albums = require('express').Router();

//...

// Our app.use in the last file forwards a request to our albums router.
// So this route actually handles `/albums` because it's the root route when a request to /albums is forwarded to our router.
albums.get('/', function(req, res, next) {
  // res.send() our response here
});


// A route to handle requests to any individual album, identified by an album id
albums.get('/:albumId', function(req, res, next) {
  let myAlbumId = req.params.albumId;
  // get album data from server and res.send() a response here
});

//...

module.exports = albums;

So far, so good!

But what about when we add tracks to our routes? Since tracks are associated with a particular album, our routes will end up looking something like this:

www.music.com/albums/[AlbumIdHere]/tracks/[TrackIdHere]

In the spirit of Express' modular routers, we should have a separate router for tracks. That router isn't part of our top level application logic. We can nest it in our albums router instead.

First let's set up the proper forwarding in our albums router.

const albums = require('express').Router();
const tracks = require('./tracks').Router();

//...

// Our root route to /albums
albums.get('/', function(req, res, next) {
  // res.send() our response here
});

// A route to handle requests to any individual album, identified by an album id
albums.get('/:albumId', function(req, res, next) {
  let albumId = req.params.albumId;
  // retrieve album from database using albumId
  // res.send() response with album data
});

// Note, this route represents /albums/:albumId/tracks because our top-level router is already forwarding /albums to our Albums router!
albums.use('/:albumId/tracks', tracks);

//...

module.exports = albums;

Now let's make our tracks router in a track.js file.

const tracks = require('express').Router();

//...

// The root router for requests to our tracks path
track.get('/', function(req, res, next) {
  let albumId = req.params.albumId; // Our problem line

  // retrieve album's track data and render track list page
});

// The route for handling a request to a specific track
track.get('/:trackId', function(req, res, next) {
  let albumId = req.params.albumId; // <-- How do we get this?
  let trackId = req.params.trackId;

  // retrieve individual track data and render on single track page
});

//...

module.exports = tracks;

This seems like it should work. But wait a minute. To retrieve an album's track data, we'll need to know the album's id using the req.params.albumId that we set up in our albums route.

If you use the code above, you'll discover that our albumId is undefined. Our req.params does not have an albumId property inside our tracks router. Our router is broken because we can't figure out which album the user requested when our tracks router takes over.

Solve It

Express provides an elegant solution. Back in our albums router we can refactor the route that forwards requests to our nested tracks router.

Previously, we had:

albums.use('/:albumId/tracks', tracks);

Our refactoring will attach our req.params.albumdId to our req object directly. We'll next() the request, which tells Express to send the request to the next applicable route. But instead of allowing Express to choose the route, we'll include our optional third parameter to .use(), the router that we want Express to send the request to.

albums.use('/:albumId/tracks', function(req, res, next) {
  req.albumId = req.params.albumId;
  next()
}, tracks);

We've saved our param on our req object and told Express to send it directly to our tracks router.

Here's what our albums router will look like now.

const albums = require('express').Router();
const tracks = require('./tracks').Router();

//...

albums.get('/', function(req, res, next) {
  // send our response here
});

albums.get('/:albumId', function(req, res, next) {
  let albumId = req.params.albumId;
  // retrieve album from database using albumId
  // send response with album data
});

// The fix for our parameters problem
albums.use('/:albumId/tracks', function(req, res, next) {
  req.albumId = req.params.albumId;
  next()
}, tracks);

//...

module.exports = albums;

Now in tracks we can refactor to access the req.albumId property we set.

const tracks = require('express').Router();

//...

// The root router for requests to our tracks path
track.get('/', function(req, res, next) {
  let albumId = req.albumId; // Refactored to access our albumId property

  // retrieve album's track data and render track list page
});

// The route for handling a request to a specific track
track.get('/:trackId', function(req, res, next) {
  let albumId = req.albumId; // Refactored to access our albumId property!
  let trackId = req.params.trackId;

  // retrieve individual track data and render on single track page
});

//...

module.exports = tracks;

With everything refactored, we can access all the data we need.

Any time that we need to access a parameter from a parent router in its child, we can follow the same process.

  1. Attach the data we need from the parent router to our req object directly.
  2. next() in our parent router's .use() method.
  3. Set the optional third parameter in our parent's .use() as the child router that we want the request send to.

Happy routing!


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment