Good Code
The good version gives routes small helpers for success and error responses, making the API contract consistent.
Lesson 07
Return predictable success and error envelopes across routes.
import type { Response } from "express";
export function ok<T>(res: Response, data: T) {
return res.json({ data });
}
export function created<T>(res: Response, data: T) {
return res.status(201).json({ data });
}
export function fail(res: Response, status: number, message: string) {
return res.status(status).json({ error: { message } });
}router.get("/", async (_req, res) => {
res.json(await service.listOpenReviews());
});
router.post("/", async (req, res) => {
res.status(201).json({ review: await service.createReview(req.body) });
});
router.get("/:id", async (req, res) => {
const review = await service.getReview(req.params.id);
if (!review) {
res.status(404).send("not found");
return;
}
res.json({ data: { review } });
});The good version gives routes small helpers for success and error responses, making the API contract consistent.
The bad version returns arrays, objects, strings, and nested data shapes depending on the route.