Suggest an editImprove this articleRefine the answer for “What is Express router and how to use it for modular routing?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Express Router** is a standalone routing instance (`express.Router()`) that groups routes into separate files and mounts them at a path prefix in the main app. ```js const router = express.Router(); router.get('/', (req, res) => res.json({ users: [] })); // becomes GET /api/users module.exports = router; // app.js app.use('/api/users', require('./routes/users')); ``` **Key point:** paths inside a router are relative to the mount point, not the server root.Shown above the full answer for quick recall.Answer (EN)Image**Express Router** is a standalone routing instance created with `express.Router()` that lets you define route handlers in separate files and mount them to your main app at a specific path prefix. ## Theory ### TL;DR - Router is a mini app with its own middleware stack, but no `.listen()`. Mount it, never run it standalone. - Routes inside a router are **relative** to the mount point: `router.get('/profile')` mounted at `/api/users` becomes `/api/users/profile`. - Fewer than 10 routes total? Define them directly on `app`. Split across files or teams? Use Router. - `app.use('/users', router)` is the one line that connects the two. ### Quick Example ```js // routes/users.js const express = require('express'); const router = express.Router(); router.get('/', (req, res) => res.json({ users: [] })); // GET /api/users router.get('/:id', (req, res) => res.json({ id: req.params.id })); // GET /api/users/123 module.exports = router; // app.js const userRouter = require('./routes/users'); app.use('/api/users', userRouter); // all routes above become relative to /api/users ``` `router.get('/')` does not mean the server root. It means "whatever path I get mounted at." ### Why Paths Are Relative Without Router, every route lives in `app.js` with absolute paths. One resource is 5 routes. Ten resources is 50 routes in one file. That file hits 1000 lines fast. Router creates a self-contained collection. When you call `app.use('/api/v1/users', userRouter)`, Express prepends `/api/v1/users` to every path in that router during request matching. You write `router.get('/profile')` once. It resolves to `/api/v1/users/profile` automatically. Change the mount point and all routes inside move with it. ### When to Use - **Fewer than 10 routes total** - define them directly on `app`. No extra files needed. - **10+ routes for one resource** - create `routes/users.js`, mount at `/users`. - **Team splits by domain** - one router per area (`auth`, `products`, `admin`), each owned by a different person. - **Versioned API** - `v1Router` and `v2Router`, mounted at `/api/v1` and `/api/v2`. No rewrite when bumping versions. - **Middleware per feature** - put `requireAuth` inside the router so it only guards that resource. ### Router-Level Middleware Middleware attached with `router.use()` applies only to routes in that router. This is different from `app.use()`, which hits every incoming request. ```js // routes/admin.js const router = express.Router(); const { requireAdmin } = require('../middleware/auth'); router.use(requireAdmin); // guards everything below this line router.get('/users', getAllUsers); router.delete('/users/:id', deleteUser); module.exports = router; ``` One rule about order: global middleware like `express.json()` belongs on `app`, not inside each router. If you put body parsing only inside `userRouter`, requests to `productRouter` won't have `req.body`. I've seen this break APIs in production. ### How Express Handles the Mount When you call `express.Router()`, Express creates a new `Router` instance with its own internal `stack` array. Each route you define gets added as a `Layer` object in that stack, storing the HTTP method, path pattern, and handler. At request time, Express walks the main app's stack first. When it hits `app.use('/api/users', userRouter)`, it strips the prefix from the URL and passes what remains to the router's own stack matcher. If a route matches, the handler runs. If nothing matches, control returns to the main app. No magic. Just nested stacks. ### Common Mistakes **Absolute paths inside a router** ```js // WRONG: mounted at /users, this becomes /users/users router.get('/users', (req, res) => res.json({ users: [] })); // RIGHT router.get('/', (req, res) => res.json({ users: [] })); ``` Paths in a router are always relative to the mount point. This is the most common mistake in Express code reviews at the junior level. **Forgetting `module.exports`** ```js // users.js defines everything but forgets this line module.exports = router; // app.js app.use('/users', require('./users')); // TypeError: router is not a function ``` The error looks confusing but the fix is always the same: add the export. **Global middleware placed after the mount** ```js // WRONG app.use('/users', userRouter); app.use(express.json()); // runs after mount, too late // RIGHT app.use(express.json()); // always before routes app.use('/users', userRouter); ``` **Missing `{ mergeParams: true }` in nested routers** If you nest a router under a parameterized path and need the parent param, the child router won't see it by default. ```js // without mergeParams, req.params.userId is undefined here const orderRouter = express.Router({ mergeParams: true }); orderRouter.get('/', (req, res) => { res.json({ userId: req.params.userId, orders: [] }); // works only with mergeParams }); router.use('/:userId/orders', orderRouter); ``` This trips up a lot of mid-level developers. If `req.params` looks empty in a child router, check this option first. ### Real-World Usage - **MERN stack projects** (like fullstackopen.com) - `routes/api/users.js` mounted at `/api` for frontend fetch calls. - **NestJS** - wraps Express Router internally: each `@Controller('users')` compiles down to a Router instance. - **FeathersJS** - every service mounts as a Router at the resource path. - **Express boilerplates** (like hagopj13/node-express-boilerplate on GitHub) - one router per domain, mounted in `app.js`. ### Follow-up Questions **Q:** What is the difference between `app` and `router` objects? **A:** Both have `.use()`, `.get()`, `.post()` and the rest. The key difference: `router` has no `.listen()`. You cannot start a server from a router. It only works when mounted into an app. **Q:** How does `router.use()` scope differ from `app.use()`? **A:** `router.use(fn)` applies only to routes in that specific router. `app.use(fn)` runs for every incoming request before any router gets it. **Q:** What is `router.param()` and when would you use it? **A:** `router.param('id', fn)` runs a callback whenever `:id` appears in a matched route, before the route handler executes. Useful for loading a user from the database once and attaching it to `req.user`, instead of repeating that logic in every handler. **Q:** Can a router have its own error handler? **A:** Yes. Add a four-argument middleware at the end: `router.use((err, req, res, next) => { ... })`. It catches errors thrown inside that router. If it calls `next(err)`, the error bubbles up to the app-level handler. **Q:** How do you version an API with routers? **A:** Create `v1Router` and `v2Router`, mount them at `/api/v1` and `/api/v2`. Old clients keep hitting v1, new clients use v2. No changes to existing handlers. ## Examples ### Basic: One Resource in Its Own File ```js // routes/products.js const express = require('express'); const router = express.Router(); router.get('/', (req, res) => res.json({ products: ['laptop', 'phone'] })); // GET /products router.post('/', (req, res) => res.status(201).json({ id: 1 })); // POST /products router.get('/:id', (req, res) => res.json({ id: req.params.id })); // GET /products/42 module.exports = router; // app.js const express = require('express'); const app = express(); app.use(express.json()); app.use('/products', require('./routes/products')); app.listen(3000); ``` Three routes, one file. `app.js` stays clean regardless of how many handlers you add inside `products.js`. ### Intermediate: Protected User API with Middleware This is closer to what you'd see in a real MERN project. ```js // middleware/auth.js const requireAuth = (req, res, next) => { const token = req.headers.authorization; if (!token) return res.status(401).json({ error: 'Unauthorized' }); // JWT verification would go here next(); }; module.exports = { requireAuth }; // routes/users.js const express = require('express'); const { requireAuth } = require('../middleware/auth'); const router = express.Router(); router.use(requireAuth); // all routes below require auth router.get('/', async (req, res) => { res.json({ users: [{ id: 1, name: 'Alice' }] }); // GET /api/users (protected) }); router.post('/', async (req, res) => { const { name } = req.body; res.status(201).json({ id: 2, name }); // POST /api/users (protected) }); module.exports = router; // app.js app.use(express.json()); app.use('/api/users', require('./routes/users')); ``` The auth check runs once for the whole router. No need to repeat it in every handler. ### Advanced: Nested Routers with `mergeParams` Nested resources like `/users/123/orders` need a specific setup to work. ```js // routes/orders.js const express = require('express'); const orderRouter = express.Router({ mergeParams: true }); // inherit :userId from parent orderRouter.get('/', (req, res) => { // req.params.userId is available because of mergeParams res.json({ userId: req.params.userId, orders: [] }); }); module.exports = orderRouter; // routes/users.js const express = require('express'); const router = express.Router(); const orderRouter = require('./orders'); router.param('userId', (req, res, next, id) => { req.user = { id, name: 'Alice' }; // pre-load user before any handler runs next(); }); router.use('/:userId/orders', orderRouter); // GET /users/123/orders module.exports = router; ``` Without `{ mergeParams: true }`, `req.params.userId` is `undefined` in `orderRouter`. This is a recurring bug in multi-level REST APIs.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.