Skip to Content
πŸŽ‰ Welcome to my notes πŸŽ‰
Express.js9. The Controller Pattern (Organizing Your Logic) πŸ—‚οΈ

9. The Controller Pattern (Organizing Your Logic) πŸ—‚οΈ

As you add more routes and logic to your application, your router files can become large and difficult to read. The Controller Pattern is a way to solve this by separating your route handling logic from your route definitions.

  • Router: Its only job is to define the URL paths and HTTP methods and direct incoming requests to the correct function. It’s the β€œtraffic cop.”
  • Controller: A separate module that contains the actual functions (the business logic) that are executed when a route is matched. It handles the work of interacting with models, processing data, and sending the response. It’s the β€œspecialist” who does the work.

Analogy: The Router is like a restaurant’s menu. It lists the available dishes and their prices (the routes). The Controller is the chef in the kitchen who knows the recipe for each dish and actually prepares it when an order comes in.

This separation makes your code cleaner, more organized, easier to test, and more maintainable.

The Workflow: From Route to Controller

  1. A request comes in to your Express app.
  2. It matches a route defined in a router file.
  3. The router file calls the corresponding handler function, which it has imported from a controller file.
  4. The controller function executes all the business logic.
  5. The controller function sends the response back to the client.

Example: Refactoring to Use Controllers

This pattern involves structuring your code across at least three files.

Step 1: Create the Controller File

This file will contain all the logic for handling requests. You define and export each handler function (e.g., getAllProducts, getProductById).

controllers/productController.js
// In a real app, this data would come from a database model. let products = [{ id: 1, name: 'Laptop' }, { id: 2, name: 'Keyboard' }]; // Define and export each handler function. exports.getAllProducts = (req, res) => { res.json(products); }; exports.getProductById = (req, res) => { const product = products.find(p => p.id === parseInt(req.params.id)); if (!product) { return res.status(404).send('Product not found'); } res.json(product); }; exports.createProduct = (req, res) => { const newProduct = { id: products.length + 1, name: req.body.name }; products.push(newProduct); res.status(201).json(newProduct); };

Step 2: Update the Router File

The router file becomes much cleaner. Its only job is to import the controller functions and map the routes to them (e.g., router.get('/', productController.getAllProducts)).

routes/productRoutes.js
const express = require('express'); const router = express.Router(); // 1. Import the controller functions const productController = require('../controllers/productController'); // 2. Map routes to controller functions router.get('/', productController.getAllProducts); router.get('/:id', productController.getProductById); router.post('/', productController.createProduct); module.exports = router;

Step 3: The Main App File

Your main app.js file doesn’t need to change at all. It simply imports and mounts the router, remaining unaware of the controller’s implementation details.

app.js
const express = require('express'); const app = express(); const productRoutes = require('./routes/productRoutes'); app.use(express.json()); // Mount the router app.use('/products', productRoutes); app.listen(3000, () => console.log('Server is running on port 3000'));

✨ Summary

  • The Controller pattern is a way to separate concerns in your Express application.
  • Routers are responsible for defining the URL paths and HTTP methods.
  • Controllers are responsible for the business logic that executes for those routes.
  • This pattern leads to code that is cleaner, more organized, and easier to test and maintain.
  • The typical flow is: app.js β†’ Router File β†’ Controller File.
Last updated on