This project follows the MSC (Model-Service-Controller) architectural pattern. This is an evolution of standard MVC designed to fix the common problem of "Fat Controllers" by introducing a dedicated Service layer for business logic.
In standard MVC, developers often struggle with where to put business logic (like calculating payments, handling passwords, or processing data), leading to bloated Controllers.
MSC separates concerns into three distinct layers:
- Controller (The Interface)
- Service (The Business Logic)
- Model (The Data)
The Controller is the entry point for a request. It should only handle HTTP-related tasks.
- Responsibilities:
- Receives the API request (
req). - Validates input data (basic validation).
- Calls the appropriate function in the Service layer.
- Sends the final response back to the user (
res).
- Receives the API request (
- Running Rule: A controller should NEVER contain complex business logic.
The Service layer contains the core logic of your application. It acts as the bridge between the Controller and the Model.
- Responsibilities:
- Contains all business rules (e.g., "User cannot register if email exists").
- Performs calculations and data processing.
- Calls the Model layer to query the database.
- Can be reused by different controllers (e.g., a web app and a mobile app can use the same generic
UserService).
- Running Rule: Services should be "framework agnostic" if possible (they shouldn't know they are in an Express.js app).
The Model represents the database schema and structure.
- Responsibilities:
- Defines the shape of data (Schema).
- Handles direct interaction with the database (Create, Read, Update, Delete).
- Running Rule: Models should only care about data integrity and database commands.
Middleware functions run before the Controller. They inspect requests, modify them, or block them if requirements aren't met.
- Responsibilities:
- Authentication & Authorization (e.g., checking JWT tokens).
- Input Validation (e.g., ensuring email format is correct).
- Logging (recording request details).
- Error Handling.
- Running Rule: Middlewares should pass control to the next function (
next()) or send a response (stopping the chain).
To keep the project organized and readable, we use specific naming conventions. This makes it easy for developers to find files and understand their purpose at a glance.
We use the dot-notation pattern: [Entity].[Layer].js
| Layer | Recommended Name | Example |
|---|---|---|
| Controller | name.controller.js |
auth.controller.js, product.controller.js |
| Service | name.service.js |
auth.service.js, payment.service.js |
| Model | name.model.js |
user.model.js, order.model.js |
| Route | name.routes.js |
auth.routes.js |
| Middleware | name.middleware.js |
jwt.middleware.js |
Group files by Type (Architecture-based grouping):
/src
├── /controllers # Handles HTTP requests
│ ├── user.controller.js
│ └── product.controller.js
│
├── /services # Handles business logic
│ ├── user.service.js
│ └── product.service.js
│
├── /models # Database Schemas
│ ├── user.model.js
│ └── product.model.js
│
├── /routes # API Endpoint definitions
│ ├── user.routes.js
│ └── product.routes.js
│
├── /middlewares # Request interceptors
│ ├── auth.middleware.js
│ └── error.middleware.js
│
└── app.js # App entry point
Here is how data flows through the application when a user tries to Sign Up:
- Route: Receives
POST /signup. Forwards request toauth.controller.js. - Controller: Checks if
emailandpasswordare present. CallsAuthService.signup(email, password). - Service:
- Hashes the password.
- Checks if the user already exists.
- Calls
UserModel.create(). - Generates a JWT token.
- Returns the user data to the Controller.
- Model: Saves the new user row/document to the Database.
- Controller: Sends a
201 Createdresponse with the user data back to the client.