Before you write your backend code, it is very important to have a clean and organized setup — one that helps you scale, debug, and collaborate better.
There are 6 major layers of it.
- Routes
- Controllers
- Middleware
- Services
- Repositary
- Database
Routes – The Entry Points
Routes define which function should run when a specific request hits your server.
They map HTTP methods and URLs to controllers.
router.post("/signup", userController.signup);
router.get("/profile", authMiddleware, userController.getProfile);
Routes should be thin and clean.
They don’t contain logic.
They only decide where the request goes.
Think of Routes as:
The road map of your backend.
Controllers – The Request Handlers
Controllers handle:
- Reading request data (req.body, req.params, req.query)
- Sending response (res.json, res.status)
- Calling the service layer
They should NOT:
- Contain business logic
- Talk directly to the database
- Hash passwords
- Calculate business rules
Think of Controllers as:
The receptionist. They receive and respond.
Middleware – The Gatekeepers
Middleware runs before the controller.
It is used for:
- Authentication (JWT verification)
- Logging
- Input validation
- Rate limiting
- Error handling
router.get("/profile", authMiddleware, userController.getProfile);
The request must pass through middleware before reaching the controller.
Think of Middleware as:
The security check at the airport.
Services – The Brain of the Application
This is where your real logic lives.
Services handle:
- Business rules
- Data transformations
- Workflow decisions
Orchestration of multiple repositories.
exports.signup = async ({ email, password }) => {
const existingUser = await userRepository.findByEmail(email);
if (existingUser) {
throw new Error("User already exists");
}
const hashedPassword = await bcrypt.hash(password, 10);
return await userRepository.createUser(email, hashedPassword);
};
Think of Services as:
The decision-maker.
Repository – The Data Access Layer
The repository is responsible for:
- Communicating with the database
- Executing queries
- Returning raw data
It does NOT:
- Decide business rules
- Validate logic
- Handle HTTP
exports.findByEmail = async (email) => {
return db("users").where({ email }).first();
};
Repository is:
An abstraction over the database.
If tomorrow you switch from PostgreSQL to MongoDB,
only the repository layer should change.
Think of Repository as:
The translator between your app and the database.
Database – The Storage Engine
This is your:
- PostgreSQL
- MySQL
- MongoDB
- Supabase
Its job is simple:
- Store data
- Retrieve data
- Maintain integrity
It does not care about:
- HTTP
- Business logic
- Application rules
- It only stores information
Full Request Flow
- Let’s say a user signs up.
- Request hits Route
- Middleware validates token/input
- Controller receives request
- Controller calls Service
- Service applies business logic
- Service calls Repository
- Repository talks to Database
- Response travels back up the chain
Database → Repository → Service → Controller → Client
Clean. Predictable. Scalable.
🧠 Simple Mental Model
If you're ever confused where something belongs, ask:
Does it handle HTTP? → Controller
Does it validate/authenticate/log? → Middleware
Is it business logic? → Service
Is it database query? → Repository
Is it storage? → Database
Does it map endpoint to controller? → Route
This structure is inspired by architectural principles popularized in
Clean Architecture by Robert C. Martin — but simplified for practical backend applications.
Top comments (0)