Skip to content

Express Router

express.Router() is the tool Express gives you for organizing routes into separate, mountable modules instead of one giant app.js file. This lesson shows how to structure a growing application around routers.

What Is express.Router()?

express.Router() creates a new, isolated router object that behaves like a mini Express application: it has its own .get(), .post(), .use(), and its own middleware stack, but no .listen(), it must be mounted onto a real app (or another router) to actually receive requests.

This lets you split a large application into focused files, one router per resource, users, orders, products, and wire them together in one place.

// routes/users.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => res.json([{ id: 1, name: 'Ada' }]));
router.get('/:id', (req, res) => res.json({ id: req.params.id }));

module.exports = router;

// app.js
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);

Paths inside the router (/, /:id) are relative to the mount path (/users), so the full routes become GET /users and GET /users/:id.

Creating and Mounting a Router

const router = express.Router();

router.get(path, handler);
router.post(path, handler);
router.use(middleware);

app.use(mountPath, router);
  • express.Router() accepts an options object, e.g. { mergeParams: true } for nested routers.
  • A router supports every method Express supports on app.
  • Mounting the same router at multiple paths is possible but uncommon.
  • Routers can be nested inside other routers for deeply organized route trees.

Express Router Cheatsheet

Common router operations you will use while structuring a real app.

Task Example
Create router const router = express.Router();
Define route router.get('/:id', getUser);
Router middleware router.use(requireAuth);
Mount router app.use('/users', usersRouter);
Nested router express.Router({ mergeParams: true })
Export router module.exports = router;

Why Routers Matter as Apps Grow

A single app.js file with fifty route definitions quickly becomes unreadable and hard to test. Splitting routes by resource into their own router files keeps each file focused, easy to review, and easy to reuse or move without touching unrelated code.

  • One router per resource (users.router.js, orders.router.js, ...).
  • Each router file can be tested in isolation with supertest.
  • app.js becomes a short, readable list of mounted routers.
  • Teams can work on different routers in parallel with fewer merge conflicts.

Typical Project Wiring

A common pattern collects all routers under a single /api prefix, and further versions or groups them from there.

// app.js
const express = require('express');
const usersRouter = require('./routes/users.router');
const ordersRouter = require('./routes/orders.router');

const app = express();
app.use(express.json());

app.use('/api/users', usersRouter);
app.use('/api/orders', ordersRouter);

module.exports = app;

Common Mistakes

  • Defining routes directly on app for every resource instead of splitting them into routers.
  • Forgetting to mount a router with app.use(), its routes will simply never be reachable.
  • Duplicating the mount path inside the router's own route definitions (e.g. /users/:id inside a router already mounted at /users).
  • Not exporting the router with module.exports, leaving other files unable to import it.

Key Takeaways

  • express.Router() creates a modular, mountable set of routes and middleware.
  • Paths defined inside a router are relative to its mount path.
  • Splitting routes by resource keeps a growing app organized and testable.
  • Routers can have their own middleware, independent of the main app.

Pro Tip

As soon as a file has more than two or three routes, extract it into its own router file, waiting until app.js is already a mess makes the refactor far more painful.