Photo by chansu shin on Unsplash

On this page

This chapter follows Modules in NestJS.

A controller is the part of a Nest application that knows about HTTP. It declares which paths and methods it answers, pulls the pieces it needs out of the request, calls a provider, and returns a value. Nest turns that value into the response. Keeping controllers that thin is the point: the logic lives in providers, which can be tested and reused without a request object.

Routes from decorators

@Controller('cats') sets a path prefix, and a method decorator per HTTP verb adds the rest. The method name does not matter to routing.

src/cats/cats.controller.ts
import { Body, Controller, Delete, Get, HttpCode, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll(@Query('breed') breed?: string) {
return this.catsService.findAll({ breed });
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
@Post()
create(@Body() dto: CreateCatDto) {
return this.catsService.create(dto);
}
@Patch(':id')
update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateCatDto) {
return this.catsService.update(id, dto);
}
@Delete(':id')
@HttpCode(204)
remove(@Param('id', ParseIntPipe) id: number) {
return this.catsService.remove(id);
}
}

This gives GET /cats, GET /cats/:id, POST /cats, PATCH /cats/:id, and DELETE /cats/:id. A controller only serves requests once it is listed in the controllers array of a module. nest g resource cats generates this whole shape, including the module, service, and DTOs.

Reading the request

Parameter decorators pull one part of the request into a method argument:

Decorator Reads
@Param('id') A route parameter such as :id
@Query('breed') A query string value
@Body() The parsed request body
@Headers('x-request-id') A request header
@Ip() The client IP address
@Req() The whole platform request object

Every value from the URL arrives as a string. ParseIntPipe in @Param('id', ParseIntPipe) converts it to a number, or rejects the request with 400 Bad Request if it is not numeric. Pipes are covered in Pipes and validation in NestJS.

Request bodies and DTOs

A data transfer object is a class that describes the shape of a body. It is a class, not an interface, because classes still exist at runtime, which lets Nest validate the body against the decorators on it.

src/cats/dto/create-cat.dto.ts
import { IsInt, IsString, Min } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
@Min(0)
age: number;
@IsString()
breed: string;
}

An update DTO usually makes every field optional. PartialType from @nestjs/mapped-types derives it without repeating the fields, and it keeps the validation rules:

src/cats/dto/update-cat.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateCatDto } from './create-cat.dto';
export class UpdateCatDto extends PartialType(CreateCatDto) {}

The DTO describes the HTTP contract. It does not have to match the database document. When the two drift apart, keep a separate type for what the service stores and map between them in the service.

Status codes, headers, and redirects

A handler’s return value becomes the body. Objects and arrays are serialized to JSON, and a returned Promise or RxJS Observable is awaited first. The status code is 200, except for POST, which defaults to 201. Decorators change the defaults:

src/cats/cats.controller.ts
@Post('import')
@HttpCode(202)
@Header('Cache-Control', 'no-store')
startImport() {
return this.catsService.startImport();
}
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
docs() {}

Errors are thrown, not returned. throw new NotFoundException() produces a 404 with a JSON body. Exception filters in NestJS covers the full set.

The library-specific response

@Res() injects the Express (or Fastify) response object. Once you inject it, Nest stops sending the response for you, and the handler must call res.send() or res.json() itself. That also bypasses interceptors that transform the return value.

src/cats/cats.controller.ts
import type { Response } from 'express';
@Get('export')
export(@Res({ passthrough: true }) res: Response) {
res.setHeader('Content-Disposition', 'attachment; filename="cats.json"');
return this.catsService.findAll({});
}

passthrough: true is the middle ground: the handler can set a cookie or a header on the native object and still return a value that Nest serializes. Use the native object only for what the decorators cannot express, such as streaming a file, because it ties the controller to one platform adapter.

Next: Providers and dependency injection in NestJS.

Check your understanding

Question 1 of 5

0 of 5 checked

  1. 1. What is required for a controller's routes to be served?

Search articles

Type a word to search published articles.

Open the search page