Fullstack CourseLearn by building
Back to week 1

Topic

Controllers

Definition

A Nest controller is a class decorated with @Controller that defines HTTP routes and delegates request handling to injected providers, returning data or throwing an HTTP exception.

In simpler words

A controller matches an incoming URL and method, hands the real work to a service, and sends back whatever that service returns.

Controllers are the HTTP front door of every Nest module. Keep them thin — parsing, delegating, responding — never doing business logic or raw SQL themselves.

After this you can

  • Predict a full route (method + path) from @Controller + @Get/@Post/etc.
  • Read param decorators (@Param, @Query, @Body, @CurrentUser) and know what each extracts
  • Explain why TicketsController has no @UseGuards despite being protected
  • Return data vs throw an HTTP exception correctly

Routing by decoration

Definition

Nest route decorators such as @Controller, @Get, @Post, @Patch, and @Delete attach a URL prefix and HTTP-method-specific path segment that together determine which method handles a request.

In simpler words

The class-level @Controller("tickets") sets the prefix; each method’s @Get/@Post/etc. adds the rest of the path for that HTTP verb.

@Controller("tickets") + @Get() on a method becomes GET /tickets. @Get(":id") on another becomes GET /tickets/:id. @Delete(":id") becomes DELETE /tickets/:id.

Route order inside the class does not matter for matching — Nest matches by method + path pattern, not by declaration order (though more specific static paths should still be declared before broad wildcard ones if both exist).

apps/api TicketsController route table

@Controller('tickets')
export class TicketsController {
  @Get()             list(@Query() q: ListTicketsQueryDto) {}       // GET /tickets
  @Get(':id')        get(@Param('id', ParseUUIDPipe) id: string) {} // GET /tickets/:id
  @Post()            create(@Body() dto: CreateTicketDto, @CurrentUser() u: User) {} // POST /tickets
  @Patch(':id')      update(@Param('id') id: string, @Body() dto: UpdateTicketDto) {} // PATCH /tickets/:id
  @Delete(':id')     @Roles('admin') remove(@Param('id') id: string) {} // DELETE /tickets/:id
}

Five decorated methods, five distinct routes — no manual router.get()/post() wiring anywhere.

Extracting request data with param decorators

Definition

Parameter decorators such as @Param, @Query, @Body, and custom decorators like @CurrentUser instruct Nest to extract a specific piece of the incoming request and pass it as a typed method argument, often after pipes have validated or transformed it.

In simpler words

@Param reads a URL segment, @Query reads the query string, @Body reads the JSON body, and @CurrentUser reads the authenticated user — each becomes a plain typed argument.

@Param("id", ParseUUIDPipe) both extracts :id from the URL and runs ParseUUIDPipe on it before the handler runs — a bad UUID never reaches TicketsService.

@Query() and @Body() bind the whole query string or JSON body to a DTO class (ListTicketsQueryDto, CreateTicketDto), which the global ValidationPipe then validates.

Health controller — the simplest possible controller

@Controller('health')
export class HealthController {
  @Public()
  @Get()
  check() {
    return { status: 'ok' };
  }
}

No params, no service — sometimes a controller method is genuinely this small.

Mistake: parsing the body manually

// Wrong — bypasses DTO validation entirely
create(@Req() req: Request) {
  const title = req.body.title; // unchecked, unvalidated
  return this.ticketsService.create({ title });
}

// Right
create(@Body() dto: CreateTicketDto, @CurrentUser() user: User) {
  return this.ticketsService.create(dto, user.id);
}

@Req() escapes Nest’s pipeline — the global ValidationPipe never runs on manually read fields.

Return values, status codes, and auth without @UseGuards

Definition

Nest serializes a route handler’s return value into a JSON HTTP response with a default status code per HTTP method, and maps thrown Nest HTTP exceptions to an error response through the registered exception filter.

In simpler words

Return an object and Nest sends JSON (201 for POST, 200 for others by default, unless you override it); throw a Nest exception and the filter turns it into a consistent error body.

TicketsController has no @UseGuards(...) anywhere, yet every non-@Public route is protected — because JwtAuthGuard is registered globally as APP_GUARD in AuthModule, so it runs on every route unless that route is marked @Public().

@Roles("admin") on remove() works the same way: RolesGuard is also a global APP_GUARD that reads the @Roles metadata and checks the authenticated user’s role before the handler runs.

@HttpCode(HttpStatus.NO_CONTENT) on remove() overrides the default 200 to 204, matching a delete that returns no body.

Overriding the default status

@Delete(':id')
@Roles('admin')
@HttpCode(HttpStatus.NO_CONTENT)
async remove(@Param('id', ParseUUIDPipe) id: string) {
  await this.ticketsService.remove(id);
}

Without @HttpCode, a DELETE handler that returns undefined would still default to 200 with an empty body.

Keep in mind

  • Name handler methods after the action: list, get, create, update, remove.
  • Prefer the standard return style over @Res() unless you truly need cookies/streams.
  • A controller with SQL, bcrypt, or business rules inline is a sign logic belongs in the service instead.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥ 80% (enforced by the API) to save progress.

Checking your session…

10 questions · concept 3 · syntax 2 · practical 3 · logic 2

Concept1. Which statement best defines Nest controllers?
Syntax2. Which code-level choice matches Nest controllers?
Practical3. A reviewer spots a bug related to Nest controllers. What is the right fix?
Logic4. Which reasoning correctly explains Nest controllers?
Concept5. Which boundary does correct use of Nest controllers preserve?
Practical6. What is the safest next step when applying Nest controllers?
Syntax7. Which implementation direction matches the rule for Nest controllers?
Logic8. Which consequence follows from applying Nest controllers correctly?
Concept9. Which claim about Nest controllers is true in this Nest + Postgres monorepo?
Practical10. Which team practice best demonstrates Nest controllers?