Creating Your First Entity Controller

Introduction: Understanding Controllers In NestJS

Welcome back! In the previous lesson, you set up your NestJS project and got it ready to serve both static files and an API. Now, you are ready to start building the actual API endpoints that users and other programs can interact with.

In NestJS, a controller is a class that handles incoming requests and returns responses to the client. Controllers serve as the main entry point for client-side HTTP requests. They don’t contain business logic themselves but instead act as a middleman — they receive the request, forward it to the appropriate service, and return the response. This separation of concerns is crucial for maintaining clean and testable code. Think of a controller as the part of your application that listens for specific URLs (like /users) and decides what to do when someone visits them. Controllers are a key part of building APIs, and learning how to create them is an important step in your journey.

By the end of this lesson, you will know how to create a simple controller that responds to a GET request with a list of users.

Quick Recap: Project Setup So Far

Before we dive in, let’s quickly remind ourselves of the current project structure. You already have a basic NestJS project set up, and your main application module looks like this:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';

@Module({
  imports: [UsersModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

This setup ensures that our app is ready to use additional modules, like the UsersModule that we'll build in this lesson.

Note: In the previous lesson, we didn’t include the UsersModule yet. It’s being introduced now so we can start working with user-specific logic. NestJS uses modules to group related functionality, and adding UsersModule here is the first step toward organizing our project into clear, maintainable feature areas.

Building The UsersController: Step-By-Step Example

Let’s create a controller that will handle requests to /users and return a list of users. But why do we need a /users endpoint at all in our reading tracker app? Think of the reading tracker as something that stores which books each user is reading. So before we can track reading progress, we need to have the concept of a user. This endpoint will be the foundation for managing user data (e.g., registering, identifying who’s reading what).

In NestJS applications, it's common to create separate controllers for each main entity. For example, while we now have a /users controller, later we may also create /books or /sessions controllers. This keeps your codebase modular — each entity has its own logic, routes, and controller.

Here’s the code for the controller:

// src/users/users.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('users')
export class UsersController {
  constructor() {}

  @Get('all')
  findAll() {
    return [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ];
  }
}

Let’s break down what’s happening here:

  • @Controller('users'): This decorator tells NestJS that this controller will handle requests that start with /users.
  • UsersController class: This is the main class for your controller. It will contain methods that handle different types of requests.
  • constructor() {}: This sets up the constructor which is empty for now. Later we'll pass our usersService here.
  • @Get('all'): This decorator tells NestJS that the findAll method should handle GET requests to /users/all.
  • findAll(): This method returns a hardcoded list of users. In a real app, you would get this data from a database or another source.

Expected Output:
When you visit /users in your browser or use a tool like Postman, you should see:

[
  { "id": 1, "name": "Alice" },
  { "id": 2, "name": "Bob" }
]

In future lessons, this static array will be replaced with dynamic data — either from a mock in-memory store or a database. This stepwise approach helps you understand the core structure before layering in complexity like data fetching, validation, or error handling.

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