Controllers handle HTTP concerns, req and res, but the actual business rules of your application, how an order is priced, how a user is validated before creation, deserve their own home. This lesson introduces the service layer.
What Is a Service in Express?
A service is a plain JavaScript module (or class) containing business logic with no knowledge of HTTP at all, no req, no res, no status codes. It takes plain data in and returns plain data (or throws an error) out.
Controllers call services and translate the result into an HTTP response. This separation means the same service function can be reused from a controller, a background job, a CLI script, or a test file, all without any Express dependency.
Because services don't touch Express at all, they can be unit tested by simply calling the function and asserting on the return value or thrown error, no HTTP server or supertest required.
Passing req or res into a service function, coupling business logic to Express unnecessarily.
Duplicating the same business rule in multiple controllers instead of centralizing it in one service.
Skipping the service layer entirely for small apps, then struggling to refactor once the app grows.
Letting services swallow errors silently instead of throwing them for the controller to handle.
Key Takeaways
Services hold business logic and never touch req/res directly.
Controllers translate between HTTP and services, services translate between input data and business rules.
This separation makes business logic reusable and independently unit-testable.
Not every tiny app needs a service layer, but it pays off quickly once logic grows past a few lines per route.
Pro Tip
Start extracting a service as soon as a controller function needs an if statement that isn't purely about HTTP status codes, that's usually the moment business logic has started creeping into the wrong layer.
You now understand the service layer pattern. Next, learn how to standardize your API's JSON responses in the API Response Format lesson.