Skip to content

NestJS Day 2: What is Controller?

NestJS Day 2

Abstract

This is a concise, summarized approach to learn NestJS. For more in-depth knowledge about NestJS, visit the official NestJS documentation.

Youtube video

Github repo for day 2

HAR file

What is a Controller?

Controllers are responsible for handling incoming HTTP requests and returning responses to the client. The purpose of controllers is to receive specific requests for the application. The routing mechanism controls which controller receives which requests.

Frequently, each controller has more than one route, and different routes can perform different actions. In order to create a basic controller, we use classes and decorators. Decorators associate classes with required metadata and enable Nest to create a routing map.

Controller Basics

Let’s create a basic controller using the NestJS CLI:

Terminal window
nest generate controller cats

This will create:

  • src/cats/cats.controller.ts
  • src/cats/cats.controller.spec.ts

Here’s what a basic controller looks like:

src/cats/cats.controller.ts
import { Controller, Get } from "@nestjs/common";
@Controller("cats")
export class CatsController {
@Get()
findAll(): string {
return "This action returns all cats";
}
}

The @Controller('cats') decorator defines a controller that handles routes starting with cats. The @Get() decorator before the findAll() method tells Nest to create a handler for a specific endpoint for HTTP GET requests. The endpoint path is determined by concatenating the controller prefix (cats) and any path specified in the method decorator (none in this case, so just /cats).

When a request is made to this endpoint, Nest routes the request to our defined handler method, findAll(). The method name is completely arbitrary and doesn’t have any special meaning in the routing decision process.

Routing

Routing in NestJS refers to how the application directs HTTP requests to controller handlers. The routing mechanism determines which controller receives which requests. Each route has a specific path and HTTP method.

Route Paths

Routes can be:

  • String paths: @Get('profile')
  • Pattern paths: @Get('ab*cd')

HTTP Methods

NestJS provides decorators for all standard HTTP methods:

@Get()
@Post()
@Put()
@Delete()
@Patch()
@Options()
@Head()
@All() // Handles all HTTP methods

Example with Multiple Routes

Here’s an example of a controller with multiple routes:

src/cats/cats.controller.ts
import { Controller, Get, Post, Body } from "@nestjs/common";
@Controller("cats")
export class CatsController {
@Post()
create(@Body() createCatDto: any): string {
return "This action adds a new cat";
}
@Get()
findAll(): string {
return "This action returns all cats";
}
@Get(":id")
findOne(@Param("id") id: string): string {
return `This action returns a #${id} cat`;
}
}

Route Parameters

To define routes with parameters, we can add route parameter tokens in the path of the route:

@Get(':id')
findOne(@Param('id') id: string): string {
return `This action returns a #${id} cat`;
}

Route parameters declared in this way can be accessed using the @Param() decorator, which should be added to the method signature. The @Param() decorator is imported from the @nestjs/common package.

Multiple Route Parameters

You can also have multiple route parameters:

@Get(':id/details/:detailId')
findOneDetail(@Param('id') id: string, @Param('detailId') detailId: string): string {
return `This action returns details #${detailId} for cat #${id}`;
}

Access All Parameters

To access all parameters in a route, omit the parameter name in the decorator:

@Get(':id')
findOne(@Param() params: any): string {
return `This action returns a #${params.id} cat`;
}

Request Object

Often, we need to access the client request details. We can access the request object by instructing Nest to inject it by adding the @Req() decorator to the handler’s signature.

import { Controller, Get, Req } from "@nestjs/common";
import { Request } from "express";
@Controller("cats")
export class CatsController {
@Get()
findAll(@Req() request: Request): string {
console.log(request.url); // Access request properties
console.log(request.headers); // Access request headers
return "This action returns all cats";
}
}

Dedicated Decorators for Request Objects

While the request object has a lot of properties, you will often only use a few of them. Nest provides dedicated decorators for common properties to extract specific properties from the request object:

DecoratorExpress Equivalent
@Req()req
@Res()res
@Next()next
@Session()req.session
@Param(key?: string)req.params / req.params[key]
@Body(key?: string)req.body / req.body[key]
@Query(key?: string)req.query / req.query[key]
@Headers(name?: string)req.headers / req.headers[name]
@Ip()req.ip
@HostParam()req.hosts

Here’s an example using the @Body() decorator to access the request body:

@Post()
create(@Body() createCatDto: any): string {
console.log(createCatDto);
return 'This action adds a new cat';
}

Resources (REST API)

Nest provides a way to define an entire resource with all the standard CRUD operations using the @nestjs/cli. To generate a complete resource with CRUD operations, you can use:

Terminal window
nest generate resource cats

This will create:

  • Controller with CRUD operations
  • Service with business logic
  • DTOs (Data Transfer Objects)
  • Entity for database mapping
  • Module to organize everything

Status Code

The response status code is always 200 by default for GET requests and 201 for POST requests. We can easily change this behavior by adding the @HttpCode() decorator at a handler level.

import { Controller, Get, Post, HttpCode } from "@nestjs/common";
@Controller("cats")
export class CatsController {
@Post()
@HttpCode(204)
create(): string {
return "This action adds a new cat";
}
@Get()
findAll(): string {
return "This action returns all cats";
}
}

The @HttpCode() decorator takes an HTTP status code as an argument. It’s imported from the @nestjs/common package.

Headers

To specify a custom response header, you can use the @Header() decorator or a library-specific response object (and call res.header() directly).

import { Controller, Get, Header } from "@nestjs/common";
@Controller("cats")
export class CatsController {
@Get()
@Header("Cache-Control", "none")
findAll(): string {
return "This action returns all cats";
}
}

Redirection

To redirect a response to a specific URL, you can use the @Redirect() decorator or a library-specific response object (and call res.redirect() directly).

@Redirect() takes two arguments: url and statusCode. Both are optional. The default value for statusCode is 302 (Found).

import { Controller, Get, Redirect } from "@nestjs/common";
@Controller("cats")
export class CatsController {
@Get("docs")
@Redirect("https://docs.nestjs.com", 302)
getDocs() {
return; // This is ignored because of @Redirect decorator
}
}

You can also dynamically determine the HTTP status code or redirect URL by returning an object with the shape:

{
"url": string,
"statusCode": number
}

The returned values will override any arguments passed to the @Redirect() decorator. For example:

@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}

Sub-Domain Routing

The @Controller decorator can take a host option to require that the HTTP host of the incoming requests matches some specific value.

@Controller({ host: "admin.example.com" })
export class AdminController {
@Get()
index(): string {
return "Admin page";
}
}

Route Parameters in Host

Similar to route paths, the hosts option can use tokens to capture the dynamic value at that position in the host name. The host parameter tokens declared in this way can be accessed using the @HostParam() decorator.

@Controller({ host: ":account.example.com" })
export class AccountController {
@Get()
getInfo(@HostParam("account") account: string) {
return `Account info for ${account}`;
}
}

Asynchronicity

Modern JavaScript extensively uses asynchronous functions. Nest supports this pattern and works well with async functions that return a Promise.

@Get()
async findAll(): Promise<any[]> {
return [];
}

Alternatively, you can use the RxJS Observable streams:

@Get()
findAll(): Observable<any[]> {
return of([]);
}

Nest will automatically subscribe to the Observable and take the last emitted value once the stream is completed.

Request Payloads

When client needs to send data to the server with a POST, PUT, or PATCH request, we need to define the payload structure. NestJS uses Data Transfer Objects (DTOs) for this purpose.

First, let’s create a class to define the structure of the incoming data:

src/cats/dto/create-cat.dto.ts
export class CreateCatDto {
name: string;
age: number;
breed: string;
}

Now we can use this DTO in our controller:

@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}

Validation with DTOs

DTOs are particularly useful when combined with validation. To add validation to our DTOs, we need to:

  1. Install the required packages:
Terminal window
npm install --save class-validator class-transformer
  1. Enable validation in the main.ts file:
import { ValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
  1. Add validation decorators to our DTO:
import { IsString, IsInt, Min, Max } from "class-validator";
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
@Min(0)
@Max(20)
age: number;
@IsString()
breed: string;
}

Full Resource Example

Here’s a complete example of a controller that implements a basic CRUD (Create, Read, Update, Delete) interface:

import {
Controller,
Get,
Post,
Body,
Param,
Put,
Delete,
HttpStatus,
HttpCode,
} from "@nestjs/common";
import { CreateCatDto } from "./dto/create-cat.dto";
import { UpdateCatDto } from "./dto/update-cat.dto";
import { CatsService } from "./cats.service";
import { Cat } from "./interfaces/cat.interface";
@Controller("cats")
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
return this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@Get(":id")
async findOne(@Param("id") id: string): Promise<Cat> {
return this.catsService.findOne(id);
}
@Put(":id")
async update(
@Param("id") id: string,
@Body() updateCatDto: UpdateCatDto
): Promise<Cat> {
return this.catsService.update(id, updateCatDto);
}
@Delete(":id")
@HttpCode(HttpStatus.NO_CONTENT)
async remove(@Param("id") id: string): Promise<void> {
return this.catsService.remove(id);
}
}

Associated Service

Here’s what the associated service might look like:

import { Injectable } from "@nestjs/common";
import { Cat } from "./interfaces/cat.interface";
import { CreateCatDto } from "./dto/create-cat.dto";
import { UpdateCatDto } from "./dto/update-cat.dto";
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(createCatDto: CreateCatDto): Cat {
const cat = { id: Date.now().toString(), ...createCatDto };
this.cats.push(cat);
return cat;
}
findAll(): Cat[] {
return this.cats;
}
findOne(id: string): Cat {
return this.cats.find((cat) => cat.id === id);
}
update(id: string, updateCatDto: UpdateCatDto): Cat {
const index = this.cats.findIndex((cat) => cat.id === id);
if (index >= 0) {
this.cats[index] = { ...this.cats[index], ...updateCatDto };
return this.cats[index];
}
return null;
}
remove(id: string): void {
const index = this.cats.findIndex((cat) => cat.id === id);
if (index >= 0) {
this.cats.splice(index, 1);
}
}
}

Using Express Response Object

While the standard pattern is to return a value and let Nest handle the response, you can also take full control of the response object:

import { Controller, Get, Res } from "@nestjs/common";
import { Response } from "express";
@Controller("cats")
export class CatsController {
@Get()
findAll(@Res() response: Response) {
response.status(200).json({
data: [],
message: "This action returns all cats",
});
}
}

Library-specific Mode

For compatibility with third-party libraries that require access to the native response object, or to improve performance by bypassing the Nest response handling layer, you can use the passthrough option:

@Get()
findAll(@Res({ passthrough: true }) res: Response) {
res.status(HttpStatus.OK);
return [];
}

This will give you access to the native response object while still allowing Nest to handle the response.

Scopes

In Node.js, each request is handled by a separate thread. Therefore, it’s safe to use mutable request-level state. However, when using GraphQL with subscriptions or WebSockets, Nest controllers can be shared across multiple requests, making request-level state unsafe. To address this, you can use different injection scopes:

  • DEFAULT: A single instance is shared across the entire application (singleton)
  • REQUEST: A new instance is created for each incoming request
  • TRANSIENT: A new instance is created for each consumer (each controller, service, etc.)

Here’s how to specify a scope for a controller:

import { Controller, Get, Scope } from "@nestjs/common";
@Controller({
path: "cats",
scope: Scope.REQUEST,
})
export class CatsController {
constructor(private catsService: CatsService) {
console.log("Controller created");
}
@Get()
findAll() {
return this.catsService.findAll();
}
}

Summary

Controllers are a fundamental part of NestJS applications, responsible for handling incoming requests and returning appropriate responses. In this article, we covered:

  1. Basic controller creation and routing
  2. Working with route parameters and request objects
  3. Setting response status codes and headers
  4. Handling redirections
  5. Using sub-domain routing
  6. Working with asynchronous operations
  7. Processing request payloads with DTOs
  8. Implementing full RESTful resources
  9. Controlling the request/response lifecycle
  10. Understanding controller scopes

In the next article, we will explore Providers and Services, which handle the business logic of your application.

To learn more about controllers, visit the official NestJS documentation on controllers.