Using Express Router

1. Creating Router Instance

OptionDefaultPurpose
caseSensitivefalseTreat /Foo/foo
mergeParamsfalseInherit parent route params
strictfalseTreat /foo/foo/

Example: Router with mergeParams

import { Router } from "express";
const router = Router({ mergeParams: true, strict: true });
// In sub-router mounted at /users/:userId — req.params.userId now visible

2. Defining Router-Level Routes

APIDescription
router.get/post/put/patch/delete()HTTP method routes
router.all()All methods
router.use()Middleware (any method)
router.route(path)Chainable verbs

3. Mounting Routers

Example: Versioned mounting

app.use("/api/v1", v1Router);
app.use("/api/v2", v2Router);
app.use("/admin", requireAdmin, adminRouter);
FormBehavior
app.use(router)Mount at root
app.use(prefix, router)Strip prefix from req.url inside router
app.use(prefix, mw, router)Run middleware before router

4. Using Router Middleware

Example: Scoped auth

const router = Router();
router.use(requireAuth);   // applies to ALL routes below
router.get("/me", getProfile);
router.put("/me", updateProfile);
ScopeAPI
App-wideapp.use(mw)
Router-widerouter.use(mw)
Path-specificrouter.use("/admin", mw)
Per-routeInline before handler

5. Implementing Nested Routers

Example: Posts under users

const postsRouter = Router({ mergeParams: true });
postsRouter.get("/", (req, res) => {
  // Access parent param thanks to mergeParams
  res.json({ userId: req.params.userId });
});

const usersRouter = Router();
usersRouter.use("/:userId/posts", postsRouter);
app.use("/users", usersRouter);
// GET /users/42/posts → { userId: "42" }
RequirementDetail
mergeParams: trueRequired to read parent :params
OrderDefine inner router before mounting

6. Using Router Parameters

Example: param() prefetch

router.param("id", async (req, res, next, id) => {
  try {
    const user = await db.users.findById(id);
    if (!user) return res.sendStatus(404);
    req.user = user;
    next();
  } catch (err) { next(err); }
});

router.get("/users/:id", (req, res) => res.json(req.user));
TriggerBehavior
Param appears in URLCallback runs before route handler
Multiple paramsEach param() runs once per request

7. Organizing Routes by Resource

src/
├── routes/
│   ├── index.js           ← mounts all routers
│   ├── users.routes.js
│   ├── posts.routes.js
│   └── auth.routes.js
├── controllers/
│   ├── users.controller.js
│   └── posts.controller.js
└── services/
    └── users.service.js
        
LayerResponsibility
RoutePath + method + middleware → controller
ControllerHTTP-aware: parse req, format res
ServiceBusiness logic, DB access

8. Creating Route Prefixes

Example: API versioning prefix

const apiV1 = Router();
apiV1.use("/users", usersRouter);
apiV1.use("/posts", postsRouter);
app.use("/api/v1", apiV1);
// → /api/v1/users, /api/v1/posts
Prefix ConventionUse
/api/v1REST API versioning
/adminAdmin-only routes
/authAuthentication endpoints
/internalInternal microservice calls

9. Exporting Router Modules

StyleSyntax
ESM defaultexport default router + import router from "..."
ESM namedexport { router }
CommonJSmodule.exports = router
Factoryexport const createRouter = (deps) => { ... }

Example: Dependency-injected router

export function createUsersRouter({ usersService }) {
  const router = Router();
  router.get("/", async (req, res) => res.json(await usersService.list()));
  return router;
}

10. Handling Router-Level Errors

Example: Per-router error handler

const router = Router();
router.get("/", asyncHandler(listUsers));

// Router-scoped error handler — catches only errors from this router
router.use((err, req, res, next) => {
  if (err.code === "USER_NOT_FOUND") return res.status(404).json({ error: err.message });
  next(err);  // delegate to global handler
});
PatternWhen
Global handlerStandard error formatting
Router handlerDomain-specific errors (e.g., 404 per resource)