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.