Sub-Apps — Routes That Compose
The Router Class
Router lets you build self-contained route modules and mount them under a path prefix.
What you'll learn
- Create a Router
- Mount under a prefix
- Know the differences from `app`
express.Router() is a mini app you can compose. Same methods as
app — get, post, use, param — but it doesn’t bind to a
port. You mount it under a URL prefix.
A Router
// users.js
import { Router } from "express";
const router = Router();
router.get("/", listUsers);
router.post("/", createUser);
router.get("/:id", getUser);
router.delete("/:id", deleteUser);
export default router; Mounting It
// app.js
import express from "express";
import users from "./users.js";
const app = express();
app.use("/api/users", users); Inside the router, paths are relative to the mount point:
router.get("/")→GET /api/usersrouter.get("/:id")→GET /api/users/:id
Per-Router Middleware
const router = Router();
router.use(requireAuth); // applies to every route in router
router.use(express.json());
router.get("/", listUsers); This middleware only runs for requests that hit this router. Great for “auth-required” route groups.
Routers Within Routers
const apiRouter = Router();
const usersRouter = Router();
usersRouter.get("/", listUsers);
apiRouter.use("/users", usersRouter);
app.use("/api/v1", apiRouter);
// → /api/v1/users Compose as deep as you want. Each level can have its own middleware.
Router vs App
app | Router | |
|---|---|---|
Can .listen() | yes | no |
Has app.set() settings | yes | inherits from parent app |
req.app available | yes | yes (points to parent app) |
| Useful in tests | yes | yes (mount in a test app) |
In practice you write almost everything as routers, and the root
app.js is just glue.
mergeParams
If a parent router has :userId, an inner router can’t see it
unless you opt in:
const userPosts = Router({ mergeParams: true });
userPosts.get("/", (req, res) => {
console.log(req.params.userId); // available
});
apiRouter.use("/users/:userId/posts", userPosts); Without mergeParams: true, req.params only shows params
captured inside the inner router.