Learn NestJS - Controllers & Routing
Episode 4 of 24

Learn NestJS - Controllers & Routing

This episode covers the controller as the layer that handles HTTP requests: creating controllers and route handlers, using the GET POST PUT DELETE PATCH method decorators, reading route parameters, query parameters, and body, plus setting status codes and serialization.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

A controller is the gateway for every request in a NestJS application. In episode 4 we focus on how routes are mapped, how data from the URL and body is read, and how the response is controlled.

A strong understanding of controllers will determine the quality of your REST API. After this episode, you'll be able to build clean, consistent CRUD endpoints.

Creating Controllers and Route Handlers

Creating a Controller with the Nest CLI

The fastest way to create a controller is through the generator:

Generate controller products
nest g controller products

This command creates the products.controller.ts file in the src/products folder and automatically registers it in the module.

The @Controller Decorator

The @Controller decorator accepts a path prefix:

JSController dengan prefix path
import { Controller, Get } from "@nestjs/common";
 
@Controller("products")
export class ProductsController {
  @Get()
  findAll(): string[] {
    return ["Kaos", "Celana", "Sepatu"];
  }
}

With @Controller("products"), every route in this controller is prefixed with /products. The findAll method with @Get() responds to GET /products.

HTTP Method Decorators

NestJS provides a decorator for every HTTP method:

JSSemua metode HTTP di controller
@Controller("products")
export class ProductsController {
  @Get()
  findAll(): string[] {
    return ["Kaos", "Celana"];
  }
 
  @Post()
  create(): string {
    return "Produk dibuat";
  }
 
  @Put(":id")
  update(@Param("id") id: string): string {
    return `Produk ${id} diperbarui`;
  }
 
  @Delete(":id")
  remove(@Param("id") id: string): string {
    return `Produk ${id} dihapus`;
  }
 
  @Patch(":id")
  partialUpdate(@Param("id") id: string): string {
    return `Produk ${id} ditambal`;
  }
}

Each of the @Get, @Post, @Put, @Delete, and @Patch decorators maps an HTTP method to a specific method.

Route Parameters, Query Parameters, and Body

Route Parameters with @Param

Route parameters allow dynamic values in the URL:

JSMembaca route parameter
@Get(":id")
findOne(@Param("id") id: string): string {
  return `Produk dengan id ${id}`;
}

Requesting GET /products/123 will fill id with the value 123. The :id route must be declared in the decorator path.

Query Parameters with @Query

Query parameters appear after the question mark in a URL:

JSMembaca query parameter
@Get()
findAll(@Query("limit") limit: string): string {
  return `Membatasi hasil sebanyak ${limit}`;
}

Requesting GET /products?limit=10 will fill limit with 10. @Query can also accept the entire query object without arguments.

Body Parsing with @Body

For methods that send data in the body, use @Body:

JSMembaca body request
@Post()
create(@Body() body: { name: string; price: number }): string {
  return `Produk ${body.name} dengan harga ${body.price}`;
}

NestJS automatically parses the JSON body and injects it into the parameter. Later on, the body is better validated using a DTO — we'll cover that in episode 7.

Response Handling and Status Codes

Default Status Codes

NestJS provides default status codes: 200 for GET, 201 for POST. You can change them with the @HttpCode decorator:

JSMengatur status code
import { Controller, Post, HttpCode, HttpStatus } from "@nestjs/common";
 
@Controller("products")
export class ProductsController {
  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(): string {
    return "Produk dibuat";
  }
}

@HttpCode(HttpStatus.CREATED) uses the HttpStatus enum from @nestjs/common so the code isn't hard-coded.

Response Serialization

NestJS automatically serializes the value returned by a controller into JSON. You can return an object, array, or string — all are handled automatically. For finer control (for example, hiding fields), we'll cover interceptors and class-transformer in episodes 7 and 18.

Sending Custom Status Codes and Headers

Besides @HttpCode, you can use @Header for custom headers or @Res for full control over the Express response:

JSResponse memakai @Res
import { Controller, Get, Res } from "@nestjs/common";
import { Response } from "express";
 
@Controller("products")
export class ProductsController {
  @Get()
  findAll(@Res() res: Response): void {
    res.status(200).json(["Kaos", "Celana"]);
  }
}

Using @Res gives full control, but you have to manage the response manually. It's recommended to use NestJS's default approach and only use @Res when truly necessary.

Conclusion

Episode 4 equips you with the full controller and routing capabilities: HTTP method decorators, reading parameters from the URL, query, and body, plus controlling status codes and serialization.

Key takeaways:

  • @Controller("path") sets the route prefix for all methods.
  • @Get, @Post, @Put, @Delete, @Patch map HTTP methods.
  • @Param reads values from the path, @Query from the query string.
  • @Body parses JSON from the request body.
  • @HttpCode sets the status code; the default is 200/201.
  • NestJS automatically serializes the value returned by a controller.

In the next episode 5 we'll discuss providers and dependency injection — creating service providers, injecting them into controllers, understanding singleton and request-scoped scopes, custom providers, factory providers, aliases, and how to share providers between modules.

Learn NestJS - Controllers & Routing | Learning NestJS