Introduction: The Role of the Service Layer

Welcome back, API Engineer! In the previous lesson, we created a simple UsersController that returned hardcoded user data. While this works for a small example, controllers shouldn’t handle logic like data storage or retrieval. Their job is to respond to requests, not to “know” how to fetch users. That’s where the service layer comes in.

The service layer is responsible for the business logic of your application. It interacts with data sources (like databases or APIs), applies rules, and returns processed data to the controller. By moving logic to services, we keep controllers lean and make our code modular, testable, and easy to extend.

Quick Recap: Current Setup

Here’s what we currently have in users.controller.ts:

This is fine for a small demo, but as we add features like “find a user by ID,” this logic will get messy. Time to delegate these responsibilities to a UsersService.

Building the UsersService

Let’s build the service layer to handle user data and business logic.

First, create a new file called users.service.ts and add the following code:

Explanation:

  • @Injectable() marks this class as a service that NestJS can manage. It tells NestJS that this class can be injected as a dependency.
  • The users array holds the user data.
  • findAll() returns the full list of users.
  • findOne(id: number) looks for a user by their ID. If the user is not found, it throws a NotFoundException. This is a built-in NestJS exception that will return a 404 error to the client.

Example Output:

  • Calling findAll() returns:
  • Calling findOne(1) returns:
  • Calling findOne(99) throws an error:
Connecting Service and Controller

Now, let’s update the controller to use the service instead of handling data directly.

Here’s the updated users.controller.ts:

New Concepts Introduced

  • @Param('id'): Extracts the id value from the route GET /users/:id. For example, calling /users/2 will set id = '2'.
  • +id: Converts the string parameter into a number (because URL parameters are always strings).
  • Dynamic :id routes allow us to fetch resources individually (e.g., /users/1 for Alice).
  • The controller now receives an instance of UsersService through its constructor. This is called dependency injection.
  • When a request comes in, the controller calls the appropriate method on the service.

Testing Our Endpoints:

  • GET /users/all → Returns all users.
  • GET /users/1 → Returns .
Optional UI Preview (public/index.html)

We’ve added buttons to fetch both /users/all and /users/:id:

You are encouraged to test the same routes directly in your browser or with curl. The static HTML preview is just a helper.

Code Flow Summary: From Request to Response
  1. User calls GET /users/1.
  2. NestJS finds UsersController.findOne() due to @Get(':id').
  3. @Param('id') extracts the ID from the URL.
  4. findOne(+id) calls UsersService.findOne() to look for a match in the mock array.
  5. The result (or 404 error) is sent back to the client.
Summary and Practice Preview

In this lesson, you:

  • Created the UsersService to handle data and logic.
  • Updated UsersController to delegate to the service.
  • Learned how to use @Param() and dynamic :id routes.
  • Tested your endpoints with both /users/all and /users/:id.

Next, we’ll explore services in more depth — including adding new users and connecting to a mock database. Keep pushing forward — you’re building real backend skills here! 🚀

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal