DEV Community

Cover image for The Backend Setup Every Developer Should Follow
Naweli Verma
Naweli Verma

Posted on

The Backend Setup Every Developer Should Follow

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.

  1. Routes
  2. Controllers
  3. Middleware
  4. Services
  5. Repositary
  6. 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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

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)