193

Suppose I want to have REST endpoints which look roughly like this:

/user/
/user/user_id 

/user/user_id/items/
/user/user_id/items/item_id

CRUD on each if makes sense. For example, /user POST creates a new user, GET fetches all users. /user/user_id GET fetches just that one user.

Items are user specific so I put them under user_id, which is a particular user.

Now to make Express routing modular I made a few router instances. There is a router for user, and a router for the item.

var userRouter = require('express').Router();
userRouter.route('/')
  .get(function() {})
  .post(function() {})
userRouter.route('/:user_id')
  .get(function() {})

var itemRouter = require('express').Router();
itemRouter.route('/')
  .get(function() {})
  .post(function() {})
itemRouter.route('/:item_id')
  .get(function() {})

app.use('/users', userRouter);

// Now how to add the next router?
// app.use('/users/', itemRouter);

URL to item is descendents of the URL hierarchy of the user. Now how do I get URL with /users whatever to userRouter but the more specific route of /user/*user_id*/items/ to the itemRouter? And also, I would like user_id to be accessible to itemRouter as well, if possible.

huggie
  • 17,587
  • 27
  • 82
  • 139
  • 1
    Great answers already on using Express to solve this. You could, however, use Loopback (built on Express) to implement a Swagger-based API and add relations between models to perform the CRUD like you asked. Nice thing is after initial learning curve, it is much faster to assemble. http://loopback.io/ – Mike S. Aug 26 '15 at 21:04

8 Answers8

400

You can nest routers by attaching them as middleware on an other router, with or without params.

You must pass {mergeParams: true} to the child router if you want to access the params from the parent router.

mergeParams was introduced in Express 4.5.0 (Jul 5 2014)

In this example the itemRouter gets attached to the userRouter on the /:userId/items route

This will result in following possible routes:

GET /user -> hello user
GET /user/5 -> hello user 5
GET /user/5/items -> hello items from user 5
GET /user/5/items/6 -> hello item 6 from user 5

var express = require('express');
var app = express();

var userRouter = express.Router();
// you need to set mergeParams: true on the router,
// if you want to access params from the parent router
var itemRouter = express.Router({mergeParams: true});

// you can nest routers by attaching them as middleware:
userRouter.use('/:userId/items', itemRouter);

userRouter.route('/')
    .get(function (req, res) {
        res.status(200)
            .send('hello users');
    });

userRouter.route('/:userId')
    .get(function (req, res) {
        res.status(200)
            .send('hello user ' + req.params.userId);
    });

itemRouter.route('/')
    .get(function (req, res) {
        res.status(200)
            .send('hello items from user ' + req.params.userId);
    });

itemRouter.route('/:itemId')
    .get(function (req, res) {
        res.status(200)
            .send('hello item ' + req.params.itemId + ' from user ' + req.params.userId);
    });

app.use('/user', userRouter);

app.listen(3003);
Willem D'Haeseleer
  • 19,661
  • 9
  • 66
  • 99
  • 3
    Thanks for the answer. The router you uses here is more explicitly nested than the one shared by Jordonias. But does it works the same underneath the hood? I would like to grant you the bounty for comprehensiveness but I cannot do it until a few hours later. – huggie Aug 15 '14 at 05:29
  • Thanks for the answer. Is there a similar way to get from the child route the **query** params of the parent route? – cwarny Sep 23 '14 at 14:40
  • 1
    It would surprise me if they aren't available on any route, as the query param aren't tied to any route in specific... – Willem D'Haeseleer Sep 23 '14 at 14:42
  • Very thorough answer! One question: for the sake of encapsulation and separation of knowledge between the user router and the item router is there a declarative way to specify that a subrouter requires a parameter? In other words, is there an explicit way to write the registration or access calls such that the item router lets us know it expects to be passed a user id? Example situation, the item router is in another file altogether, structurally it isn't clear that it requires a user unless you get into its calls and it's only clear in the user router that it would pass a user id – yo.ian.g Sep 10 '16 at 19:09
  • 1
    This is no more legible that "standard" use of routers, I'm looking for a way to visualize the nesting when viewing the code. – DrewInTheMountains Dec 04 '17 at 21:32
  • for some reason, route.route('/') is not needed, its just route.get / route.post – James Tan Apr 08 '21 at 07:35
181

manageable nested routes...

I wanted a specific example of doing nested routes in a very manageable way in express 4 and this was the top search result for "nested routes in express". Here's an API that would have many routes that would need to be broken up for example.

./index.js:

var app = require('express')();

// anything beginning with "/api" will go into this
app.use('/api', require('./routes/api'));

app.listen(3000);

./routes/api/index.js:

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

// split up route handling
router.use('/products', require('./products'));
router.use('/categories', require('./categories'));
// etc.

module.exports = router;

./routes/api/products.js:

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

// api/products
router.get('/', function(req, res) {
  res.json({ products: [] });
});

// api/products/:id
router.get('/:id', function(req, res) {
  res.json({ id: req.params.id });
});

module.exports = router;

Nesting example in folder structure

I noticed some comments on "nesting folder structure". It is implied in this however not obvious so I added the section below. Here's a specific example of a nested folder structure for routes.

index.js
/api
  index.js
  /admin
    index.js
    /users
      index.js
      list.js
    /permissions
      index.js
      list.js

This is more a general example of how node works. If you use "index.js" in folders similarly to how "index.html" works in web pages for a directory default, this will be easy to scale your organization based off of recursion without changing your entry points to code. "index.js" is the default document accessed when using require in a directory.

contents of index.js

const express = require('express');
const router = express.Router();
router.use('/api', require('./api'));
module.exports = router;

contents of /api/index.js

const express = require('express');
const router = express.Router();
router.use('/admin', require('./admin'));
module.exports = router;

contents of /api/admin/index.js

const express = require('express');
const router = express.Router();
router.use('/users', require('./users'));
router.use('/permissions', require('./permissions'));
module.exports = router;

contents of /api/admin/users/index.js

const express = require('express');
const router = express.Router();
router.get('/', require('./list'));
module.exports = router;

There is some DRY issues here possibly but it does lend itself well to encapsulation of concerns.

FYI, recently I got into actionhero and have found it to be full featured w/sockets and tasks, more like a true framework all-in-one flipping the REST paradigm on its head. You should probably check it out over going naked w/ express.

King Friday
  • 25,132
  • 12
  • 90
  • 84
10
var userRouter = require('express').Router();
var itemRouter = require('express').Router({ mergeParams: true }); 

userRouter.route('/')
  .get(function(req, res) {})
  .post(function(req, res) {})
userRouter.route('/:user_id')
  .get(function() {})

itemRouter.route('/')
  .get(function(req, res) {})
  .post(function(req, res) {})
itemRouter.route('/:item_id')
  .get(function(req, res) {
    return res.send(req.params);
  });

app.use('/user/', userRouter);
app.use('/user/:user_id/item', itemRouter);

The key to the second part of your question is the use of the mergeParams option

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

From /user/jordan/item/cat I get a reponse:

{"user_id":"jordan","item_id":"cat"}
Jordonias
  • 5,778
  • 2
  • 21
  • 32
  • Cool. Both yours and Willem's method works for what I wanted. I'll check his for comprehensiveness but I'll mark you up as well. Thanks a lot. Your method doesn't look nested but it pretty much does what I wanted I think I even prefer yours. Thanks. – huggie Aug 15 '14 at 05:26
  • the mergeParams option is key here! – MrE Feb 07 '16 at 03:01
8

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

Users router

const users = require('express').Router();
const items = require('./items');

//...

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

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

// Note, this route represents /users/:userId/items because our top-level router is already forwarding /users to our Users router!
users.use('/:userId/items', items);

//...

module.exports = users;

Items router

// We need to merge params to make userId available in our Items router
const items = require('express').Router({ mergeParams: true });

//...

// The root router for requests to our items path
items.get('/', function(req, res, next) {
  let userId = req.params.userId; // Here is where mergeParams makes its magic

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

// The route for handling a request to a specific item
items.get('/:itemId', function(req, res, next) {
  let userId = req.params.userId; // <-- mergeParams magic
  let itemId = req.params.itemId;

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

//...

module.exports = items;

Source

svelandiag
  • 4,231
  • 1
  • 36
  • 72
7

Using @Jason Sebring solution, and adapting for Typescript.

server.ts

import Routes from './api/routes';
app.use('/api/', Routes);

/api/routes/index.ts

import { Router } from 'express';
import HomeRoutes from './home';

const router = Router();

router.use('/', HomeRoutes);
// add other routes...

export default router;

/api/routes/home.ts

import { Request, Response, Router } from 'express';

const router = Router();

router.get('/', (req: Request, res: Response) => {
  res.json({
    message: 'Welcome to API',
  });
});

export default router;
Pierre R-A
  • 509
  • 9
  • 13
2

try to add { mergeParams: true } look to simple example which it middleware use it in controller file getUser at the same for postUser

const userRouter = require("express").Router({ mergeParams: true });
export default ()=>{
  userRouter
    .route("/")
    .get(getUser)
    .post(postUser);

  userRouter.route("/:user_id").get(function () {});
}
svelandiag
  • 4,231
  • 1
  • 36
  • 72
Mohammed Al-Reai
  • 2,344
  • 14
  • 18
2

Express router(express.Router()) keeps params seprate so you would explicitly have to tell express to merge these params. eg: express.Router({ mergeParams: true })

//above line is answer to your question.

Omar
  • 31
  • 1
  • 2
-11

You need only one router, and use it like this:

router.get('/users');
router.get('/users/:user_id');

router.get('/users/:user_id/items');
router.get('/users/:user_id/items/:item_id');

app.use('api/v1', router);
eguneys
  • 6,028
  • 7
  • 31
  • 63
  • 1
    Yes but I want to separate the logics between the items and the users, and so I prefer to separate them though. I don't know if it's possible. – huggie Aug 14 '14 at 09:43
  • @huggie `items` belong to `users` right, why do you need to seperate that? you can define them in different files still using the same router if you want that. – eguneys Aug 14 '14 at 09:45
  • It belongs to user but I want to be able to easily plug it in or out without effecting user. And currently I have each router for a different URL endpoints. The style seems to be encouraged by express-generator. If it's not possible, then yeah maybe I should send the router instance to different files? But that's not consistent to the original structures. – huggie Aug 14 '14 at 09:51
  • Is it possible to add one router underneath another? Since Express middleware architecture seems to be handled by router underneath (I'm not entirely sure if it is) I think it could be possible. – huggie Aug 14 '14 at 09:55
  • I don't know i've never seen such a thing. It's interesting though. – eguneys Aug 14 '14 at 09:57
  • 3
    -1 This is not answering the question that is about nested routers – Willem D'Haeseleer Aug 14 '14 at 10:02
  • @facebook It's OK we all learn something new. The other two answers provided by Jordanias and Willem both do what I wanted. – huggie Aug 15 '14 at 05:33
  • This doesn't specifically answer the question however, it suggests an alternative solution to the problem. I like the idea. +1 for that reason. ty. – Sachi Dec 08 '18 at 16:29