Express

Lesson 02

Router boundaries

Group related routes behind routers with explicit dependencies.

Good Code

src/reviews/router.ts
import { Router } from "express";
import type { ReviewsService } from "./service";

export function createReviewsRouter(service: ReviewsService) {
  const router = Router();

  router.get("/", async (_req, res) => {
    const reviews = await service.listOpenReviews();
    res.json({ data: reviews });
  });

  router.post("/", async (req, res) => {
    const review = await service.createReview(req.body);
    res.status(201).json({ data: review });
  });

  return router;
}

Bad Code

src/app.ts
import express from "express";
import { reviewService, userService, billingService } from "./services";

const app = express();

app.get("/reviews", async (_req, res) => {
  res.json(await reviewService.listOpenReviews());
});
app.post("/reviews", async (req, res) => {
  res.json(await reviewService.createReview(req.body));
});
app.get("/users", async (_req, res) => {
  res.json(await userService.listUsers());
});
app.post("/billing/retry", async (req, res) => {
  res.json(await billingService.retryCharge(req.body.invoiceId));
});

Review Notes

What to review

Good Code

The good version keeps review routes in a focused router and receives the service dependency explicitly.

Bad Code

The bad version piles unrelated resources into the app entry point and reaches into shared service singletons.

Takeaways

  • Routers should own one resource area and receive services instead of reaching into globals.