Separation of Concerns in Node.js
Build robust and maintainable applications by learning how to properly divide your code into logical, single-purpose layers.
// server.js
app.get('/users/:id', (req, res) => {
// Database logic...
// Business logic...
// Response logic...
});
// server.js
app.get('/users/:id', (req, res) => {
// Database logic...
// Business logic...
// Response logic...
});
Routes: The API's Front Door
The Routes layer is the entry point of your API. Its sole responsibility is to define the URL endpoints (e.g., `/users/:id`) and the HTTP methods (GET, POST, etc.) that can access them. It then delegates the incoming request to the appropriate controller function.
Controllers: The Traffic Directors
The Controllers layer acts as the intermediary. It receives the request from the route, extracts necessary information (like parameters, queries, and body), calls the relevant business logic from the service layer, and then formats and sends the final HTTP response (e.g., a JSON object or a status code).
Services: The Application's Brain
The Services layer (or Business Logic Layer) is the brain of your application. It contains the core logic, calculations, data processing, and business rules. It orchestrates calls to the data layer (models) and is completely independent of the web layer (it doesn't know about requests or responses).
Models: The Database Guardians
The Models layer is responsible for all communication with the database. It defines the data schema (the structure of your data) and provides methods to create, read, update, and delete (CRUD) records. This is the only layer that should contain database queries.
Practice Zone
Interactive Test 1: Match the Responsibility
Match the architectural layer to its primary responsibility.
Arrastra en el orden correspondiente.
Arrastra las opciones:
Completa el código:
Interactive Test 2: Complete the Flow
Rellena los huecos en cada casilla.
// controller.js exports.createUser = async (req, res) => { try { const newUser = await .createUser(req.body); res.status(201).(newUser); } catch (error) { res.status(500).send("Error"); } }; // routes.js const userController = require('./controller'); router.('/users', userController.createUser);
Practice Example: Write a Controller
Complete the controller function to get a user by ID, call the service, and handle the case where the user is not found.
SoC: From Theory to Production Code
Separation of Concerns isn't just an academic concept; it's a practical strategy that directly impacts your daily development workflow.
1. Dramatically Improved Testing
When your business logic (Service) is separate from your database logic (Model) and web logic (Controller), you can test each part in isolation. You can write a unit test for a complex pricing calculation in your service layer without needing to spin up a server or connect to a real database. This makes tests faster, more reliable, and easier to write.
2. Seamless Team Collaboration
In a team environment, a well-defined structure is crucial. A frontend developer can work on a new feature knowing exactly which API route to call. A backend developer can focus on optimizing a database query within a model file without worrying about breaking the request/response handling in the controller. This modularity minimizes merge conflicts and allows for parallel development.
3. Future-Proofing and Refactoring
Imagine you need to switch from a REST API to GraphQL. With a good SoC architecture, this task is much simpler. Your core business logic in the service layer remains unchanged. You only need to create new "resolvers" (the GraphQL equivalent of controllers) that call your existing services. The same applies to changing databases; you can swap out MongoDB for PostgreSQL by rewriting the model layer, leaving the rest of the application intact.
Practical Takeaway: Adopting Separation of Concerns is an investment in your project's future. It leads to code that is not only cleaner but also more adaptable, testable, and easier for teams to manage as it grows in complexity.
Architecture Glossary
- Route
- The part of the application that defines an API endpoint (e.g., `/api/products`). Its only job is to listen for requests at a specific URL and pass them to a controller.
- Controller
- Acts as a manager for incoming requests. It validates input, calls the necessary business logic from a service, and sends back an appropriate HTTP response to the client.
- Service
- Contains the core business logic of the application. It is independent of the web framework and handles data manipulation and orchestration before data is saved or sent back.
- Model
- Represents the structure of the data and is the only layer that communicates directly with the database. It handles all Create, Read, Update, and Delete (CRUD) operations.
- Middleware
- Functions that have access to the request and response objects. They can execute any code, make changes to the request/response, and end the cycle or pass control to the next middleware. Used for concerns like authentication and logging.