Good Code
The good version keeps the route focused on HTTP input and output while the workflow owns approval rules and side effects.
Lesson 10
Keep route handlers thin by delegating business decisions to services.
router.post("/:id/approve", async (req, res) => {
const review = await reviewWorkflow.approve({
reviewId: req.params.id,
approverId: res.locals.user.id,
});
res.json({ data: review });
});router.post("/:id/approve", async (req, res) => {
const review = await db.review.findUnique({ where: { id: req.params.id } });
if (!review || review.status !== "pending") {
res.status(409).json({ error: "Review cannot be approved." });
return;
}
await db.review.update({
where: { id: req.params.id },
data: { status: "approved", approvedBy: res.locals.user.id },
});
await notifications.sendApprovalEmail(review.authorId);
res.json({ ok: true });
});The good version keeps the route focused on HTTP input and output while the workflow owns approval rules and side effects.
The bad version performs database reads, business checks, writes, notification side effects, and response formatting in one route.