Controllers and Providers

Controllers with @Controller()/@Get()/@Post(), providers with @Injectable(), and constructor-based dependency injection.

Controllers handle incoming requests

A controller is a class responsible for a set of related routes. The @Controller() decorator marks the class as one and optionally sets a path prefix shared by every route inside it; individual methods are then mapped to HTTP verbs with @Get(), @Post(), @Put(), @Patch(), and @Delete().

Typescript
import { Controller, Get, Post, Body, Param } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get()
  findAll() {
    return [{ id: 1, name: 'Ada Lovelace' }];
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return { id, name: 'Ada Lovelace' };
  }

  @Post()
  create(@Body() body: { name: string }) {
    return { id: 2, name: body.name };
  }
}

@Param('id') and @Body() are parameter decorators — they tell Nest exactly which part of the incoming request to hand to that argument, so the method never has to reach into a raw req object. Nest serializes whatever you return to JSON automatically, and uses 201 Created by default for @Post() handlers.

Providers hold the actual logic

A controller should stay thin — parsing input and shaping output — while the real business logic (data access, calculations, calls to other services) lives in a provider, most commonly a service. Any class marked @Injectable() is a provider Nest's DI container can manage and hand out to whatever needs it.

Typescript
import { Injectable } from '@nestjs/common';

interface User {
  id: number;
  name: string;
}

@Injectable()
export class UsersService {
  private users: User[] = [{ id: 1, name: 'Ada Lovelace' }];

  findAll(): User[] {
    return this.users;
  }

  findOne(id: number): User | undefined {
    return this.users.find((user) => user.id === id);
  }

  create(name: string): User {
    const user = { id: this.users.length + 1, name };
    this.users.push(user);
    return user;
  }
}

Constructor-based dependency injection

A controller obtains a provider not by constructing it itself, but by declaring it as a constructor parameter — Nest's DI container instantiates UsersService (once, as a singleton by default) and passes it in automatically:

Typescript
import { Controller, Get, Post, Body, Param, NotFoundException } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    const user = this.usersService.findOne(Number(id));
    if (!user) {
      throw new NotFoundException(`User ${id} not found`);
    }
    return user;
  }

  @Post()
  create(@Body() body: { name: string }) {
    return this.usersService.create(body.name);
  }
}

private readonly usersService: UsersService is TypeScript's parameter property shorthand — it both declares usersService as a field on the class and assigns it, in one line. Nest reads the parameter's type (UsersService) via reflect-metadata to know which provider to inject; this is exactly why the emitDecoratorMetadata compiler option from the previous page is required.

Throwing NotFoundException (or any of Nest's other built-in HTTP exceptions — BadRequestException, ForbiddenException, UnauthorizedException, and so on) is the idiomatic way to return an error response; Nest catches it and turns it into the correct status code and a JSON error body automatically.

Common mistakes

  • Forgetting @Injectable() on a service class — Nest's DI container can only manage classes explicitly marked as providers, and injecting an unmarked class fails at startup with a dependency resolution error.
  • Putting business logic directly in the controller instead of delegating to a service — it works for a toy example, but makes the logic untestable in isolation and unusable from anywhere except that one controller.
  • Forgetting that a provider must also be registered in its module's providers array (covered next) — declaring @Injectable() alone doesn't make Nest aware the class exists.