Advanced Controllers & Logic
As your API grows, putting logic directly inside route files becomes a nightmare. Master the art of Decoupling.
1. The Route vs The Controller
The route file should only define where requests go. The controller handles what happens.
// routes/userRoutes.js
router.get('/:id', userController.getUser);
// controllers/userController.js
exports.getUser = async (req, res, next) => {
const user = await UserService.findById(req.params.id);
res.json({ data: { user } });
};
2. Higher-Order Controllers
For standard CRUD, don't repeat yourself. Use a generic handler factory.
const deleteOne = (Model) => async (req, res) => {
await Model.findByIdAndDelete(req.params.id);
res.status(204).json({ data: null });
};
// Usage
exports.deleteUser = deleteOne(User);
exports.deleteProduct = deleteOne(Product);
3. Business Logic vs DB Logic
Don't put complex business rules in your controllers. Move them to aService Layer. This makes your API logic testable without a database.
Scalability Tip: By keeping your controllers "thin" (only handling input and output), you can switch from one framework (e.g., Express) to another (e.g., Fastify) with minimal changes to your core logic.