Controllers are responsible for handling incoming requests and returning responses to the client. This lesson covers the @Controller() decorator, route decorators, and how controllers should stay thin by delegating work to services.
What Is a Controller?
A controller is a class decorated with @Controller(), optionally given a base path prefix. Each method inside is decorated with an HTTP method decorator (@Get(), @Post(), etc.) that maps it to a specific route.
Controllers should stay thin: parse and validate the incoming request, call the appropriate service method, and shape the response, without containing business logic themselves.
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll() {
return this.catsService.findAll();
}
@Post()
create(@Body() dto: CreateCatDto) {
return this.catsService.create(dto);
}
}
@Controller('cats') prefixes every route in this class with /cats, so @Get() resolves to GET /cats and @Post() to POST /cats.
Controller and Route Decorator Syntax
@Controller('base-path')
export class SomeController {
@Get('optional-subpath')
handlerName() { }
}
@Controller() can take no argument, a string prefix, or an options object with a version.
A handler's return value becomes the JSON response body by default.
Throwing an exception (e.g. NotFoundException) inside a handler sends the matching HTTP error response.
Controller Decorator Cheatsheet
The decorators you'll use most when writing controllers.
Decorator
Purpose
@Controller('path')
Declares a controller and its base route
@Get() / @Post() / @Put() / @Patch() / @Delete()
Maps a method to an HTTP verb
@Param('id')
Extracts a route parameter
@Query('page')
Extracts a query string parameter
@Body()
Extracts and optionally validates the request body
@HttpCode(204)
Overrides the default success status code
Returning Responses
By default, Nest serializes whatever a handler returns (an object, array, string, or promise resolving to one) as the response body, using status 200 for GET requests and 201 for POST requests unless overridden.
@HttpCode() overrides Nest's default status code when the built-in convention (200/201) doesn't match what you want to return.
Keeping Controllers Thin
A well-structured controller mostly contains method signatures with parameter decorators, delegating the actual work to an injected service. This keeps controllers easy to read and keeps business logic reusable and independently testable.
Controllers translate HTTP concerns (params, body, status codes) into service calls.
Services contain the actual business rules and persistence logic.
Thin controllers are easier to unit test, since there's little logic to assert against.
Common Mistakes
Putting database queries or business rules directly inside controller methods.
Forgetting that route order matters, a specific path like /cats/breeds can be shadowed by an earlier /cats/:id.
Not registering the controller in its module's controllers array.
Returning undefined from a handler and being confused why the response body is empty.
Key Takeaways
@Controller() declares a class as a route handler, optionally with a base path.
HTTP method decorators (@Get, @Post, ...) map methods to specific routes.
Returned values are automatically serialized into the response body.
Controllers should stay thin and delegate logic to injected services.
Pro Tip
When two routes could match the same request (like a literal /cats/featured and a parameterized /cats/:id), register the literal path first, Nest matches routes in registration order, just like Express.
You now understand how NestJS controllers handle requests. Next, learn about NestJS Providers, the classes that hold your business logic.