DEV Community

Cover image for Why Your Controllers Become 600+ Lines Long (And How a Service Layer Fixes It)
vedant kale
vedant kale

Posted on

Why Your Controllers Become 600+ Lines Long (And How a Service Layer Fixes It)

When I first started building APIs with Express.js, I made the same mistake many beginners make:

"If it works, just put it in the controller."

So that's exactly what I did.

Database queries? Controller.
Validation? Controller.
Business logic? Controller.
Error handling? Controller. Everything lived in the same file.

At first, this wasn't a problem. The project was small, the controllers were short, and finding bugs was easy. But as the application grew, so did the controllers. A controller that started with 20 lines slowly became 100 lines. Then 300. Then 600+ lines.

At that point, reading the code became difficult. Finding bugs took longer, testing became harder, and making changes felt risky because every responsibility was mixed together in the same file. That's when I discovered the Service Layer pattern.

What Is a Service Layer?

A service layer is responsible for handling your application's business logic.

Think of it this way:

  • Routes decide which endpoint should be called.

  • Controllers handle HTTP requests and responses.

  • Services handle the actual business logic.

Business logic includes things such as:

  • Database queries

  • Validation rules

  • Permission checks

  • User-related operations

  • Payment processing

  • Business rules specific to your application

Instead of putting all of this inside a controller, you move it into dedicated service files. This keeps your controllers small and focused while making your business logic easier to reuse, test, and maintain.

A Simple Analogy

A controller is like a waiter in a restaurant. The waiter takes your order and brings your food to the table. However, the waiter doesn't cook the food. The kitchen does.

In this analogy:

  • The controller is the waiter.

  • The service layer is the kitchen.

The controller receives the request, passes it to the service layer, and returns the result to the client. The actual work happens inside the service.

Without a Service Layer

A typical controller often starts looking like this:

export const getUser = async (req, res) => {
  const id = req.params.id;

  const user = await User.findById(id);

  if (!user) {
    return res.status(404).json({
      message: "User not found",
    });
  }

  return res.json(user);
};
Enter fullscreen mode Exit fullscreen mode

At first glance, this seems fine.

The problem begins when more logic gets added:

  • Validation

  • Authorization checks

  • Database operations

  • Error handling

  • Business rules

Eventually, the controller becomes responsible for everything.

With a Service Layer

Controller:

export const getUser = async (req, res) => {
  try {
    const user = await getUserById(req.params.id);

    return res.json(user);
  } catch (error) {
    return res.status(404).json({
      message: error.message,
    });
  }
};
Enter fullscreen mode Exit fullscreen mode

Service:

export const getUserById = async (id) => {
  const user = await User.findById(id);

  if (!user) {
    throw new Error("User not found");
  }

  return user;
};
Enter fullscreen mode Exit fullscreen mode

Notice the difference.

The controller no longer knows how users are fetched. Its only responsibility is handling the request and returning a response. The service layer contains the business logic required to retrieve a user. This separation becomes increasingly valuable as your application grows.

Common Mistakes to Avoid

1. Putting all business logic inside controllers

Controllers should not become the place where everything happens. Their primary responsibility is handling the request-response cycle.

2. Turning services into another controller

Moving code to a service file is not enough. Services should contain business logic, not HTTP-specific logic such as req, res, or status codes.

3. Ignoring error handling

Service functions can throw errors. Controllers should properly handle those errors and return appropriate responses.

4. Creating services too early for tiny projects

Not every project needs a large architecture from day one. For small applications, simple controllers may be enough. As complexity grows, introducing services becomes more valuable.

Conclusion

The biggest lesson is simple:
Controllers handle requests and responses. Services handle business logic.

Keeping those responsibilities separate makes your code easier to read, test, maintain, and scale as your application grows.

The goal isn't to follow architecture patterns for the sake of it. The goal is to prevent your controllers from becoming 600-line files that nobody enjoys maintaining.

Top comments (0)