Chapter 03 — Controllers (Handling Requests)
Chapter 03 — Controllers (Handling Requests)
Hey everyone! Welcome back to Namaste NestJS! 🙏
Today we meet the receptionist of your app — the Controller. Its only job is to answer the door: "Which URL was requested? Which HTTP method? What data came along?" — and then hand the work to someone else. Controllers are the easiest building block to love, so let's dive in.
What we will cover:
- What a controller is (and what it should NOT do)
- Routing with
@Controllerand HTTP method decorators - Reading route params with
@Param - Reading query strings with
@Query - Reading the request body with
@Body - Status codes, headers, and redirects
- Sub-routes and route wildcards
- Common mistakes beginners make
- Interview Questions
1. What is a Controller?
A controller is a class whose methods handle incoming HTTP requests and return a response. Nothing more.
┌─────────────────────────────────────────────────────────────┐ │ WHAT A CONTROLLER DOES │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ✅ Decide WHICH method runs for a URL + HTTP verb │ │ ✅ Pull data out of the request (params, query, body) │ │ ✅ Call a SERVICE to do the real work │ │ ✅ Return a result (Nest turns it into a response) │ │ │ │ ❌ It should NOT contain business logic │ │ ❌ It should NOT talk to the database directly │ │ │ └─────────────────────────────────────────────────────────────┘
Think of it like this: a receptionist takes your request and routes you to the right department. A good receptionist never does your paperwork themselves — they hand it to the chef (the service). Keep controllers thin.
2. Routing — @Controller and HTTP Methods
The @Controller('cats') decorator gives every route in the class a common prefix. Then each method gets an HTTP-method decorator.
import { Controller, Get, Post } from '@nestjs/common';
@Controller('cats') // 👈 route PREFIX → everything here starts with /cats
export class CatsController {
@Get() // GET /cats
findAll() {
return 'this returns all cats';
}
@Post() // POST /cats
create() {
return 'this creates a cat';
}
}
The full URL is always prefix + method path:
@Controller('cats') + @Get('breeds') ──→ GET /cats/breeds
│ │
prefix method path
Here are the HTTP-method decorators you'll use every day:
| Decorator | HTTP Verb | Typical Use |
|---|---|---|
@Get() | GET | Read / fetch data |
@Post() | POST | Create a new resource |
@Put() | PUT | Replace a resource fully |
@Patch() | PATCH | Update part of a resource |
@Delete() | DELETE | Remove a resource |
3. Route Params — @Param
A route param is a dynamic piece of the URL, like the 42 in /cats/42. You mark it with :name and read it with @Param.
import { Controller, Get, Param } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get(':id') // 👈 :id is a placeholder
findOne(@Param('id') id: string) { // 👈 pull just the "id" out
return `this returns cat #${id}`;
}
}
Let's trace a real request by hand:
HAND TRACE: GET /cats/42
──────────────────────────
1. Nest matches @Controller('cats') + @Get(':id')
2. It sees the URL segment "42" lines up with ":id"
3. @Param('id') extracts id = "42" (always a STRING!)
4. findOne("42") runs
▼
Response: "this returns cat #42" ✔
Important: route params are always strings. If you need a number, convert it (or use a Pipe — we'll automate that in Chapter 07). You can also grab the whole params object with @Param() (no argument).
4. Query Strings — @Query
A query string is the part after the ? — used for filtering, sorting, and pagination. Read it with @Query.
import { Controller, Get, Query } from '@nestjs/common';
@Controller('cats')
export class CatsController {
// GET /cats?limit=10&breed=persian
@Get()
findAll(
@Query('limit') limit: string,
@Query('breed') breed: string,
) {
return `first ${limit} cats of breed ${breed}`;
}
}
PARAM vs QUERY — don't mix them up: ──────────────────────────────────── /cats/42 → 42 is a PARAM (identifies ONE resource) /cats?limit=10 → limit is a QUERY (filters/options)
5. Request Body — @Body
For POST/PUT/PATCH, the client sends data in the body (usually JSON). Read it with @Body.
import { Controller, Post, Body } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Post()
create(@Body() body: { name: string; age: number }) {
// body = the parsed JSON the client sent
return `created a cat named ${body.name}, age ${body.age}`;
}
}
Q: How does Nest parse the JSON automatically?
A: Under the hood, Express's body-parser is enabled by default, so incoming JSON is already parsed into a plain object by the time @Body() reads it. You don't wire anything up.
Pro tip: That inline type { name: string; age: number } works, but the professional way is a DTO (Data Transfer Object) — a dedicated class describing the body. DTOs unlock automatic validation, and we give them a whole chapter (Ch 06). For now, just know the body arrives as an object.
6. The Request Decorators Cheat Sheet
You've now seen the three you'll use 90% of the time. Here's the full family:
| Decorator | Reads | Example URL / source |
|---|---|---|
@Param('id') | A route param | /cats/42 |
@Query('limit') | A query field | /cats?limit=10 |
@Body() | The request body | JSON payload |
@Headers('auth') | A request header | Authorization: ... |
@Req() | The whole request object | escape hatch (use sparingly) |
Note: avoid reaching for @Req() (the raw Express request) unless you truly need it. Using the specific decorators keeps your code readable and framework-agnostic.
7. Status Codes, Headers & Redirects
By default Nest returns 200 for everything except @Post, which returns 201. Override when needed:
import { Controller, Post, HttpCode, Header, Redirect } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Post()
@HttpCode(204) // 👈 force a 204 No Content
create() {
return;
}
@Get('docs')
@Redirect('https://docs.nestjs.com', 301) // 👈 redirect
docs() {}
@Get('report')
@Header('Cache-Control', 'no-store') // 👈 set a response header
report() {
return 'fresh report';
}
}
In simple words: you rarely touch the raw response object. Decorators like @HttpCode, @Header, and @Redirect declare your intent, and Nest does the plumbing.
8. Little Traps Beginners Fall Into
┌─────────────────────────────────────────────────────────────┐
│ COMMON MISTAKES ❌ │
├─────────────────────────────────────────────────────────────┤
│ │
│ Putting business logic in the controller. │
│ → Keep it thin. Delegate to a service. │
│ │
│ Expecting @Param('id') to be a number. │
│ → It's a STRING. Convert it (or use ParseIntPipe). │
│ │
│ Route order matters: @Get('active') must come BEFORE │
│ @Get(':id'), or "active" gets captured as an :id. │
│ │
│ Forgetting the controller must be listed in a module's │
│ `controllers: []` array, or its routes won't exist. │
│ │
└─────────────────────────────────────────────────────────────┘
Interview Questions — Quick Fire!
Q: What is a controller in NestJS?
"A controller is a class decorated with @Controller that handles incoming HTTP requests and returns responses. Its methods are mapped to routes using decorators like @Get and @Post. A controller should stay thin — it extracts data from the request and delegates the actual work to a service."
Q: How do you read a route parameter, a query string, and the body?
"You use parameter decorators: @Param to read route params like the id in /cats/:id, @Query to read query-string values like ?limit=10, and @Body to read the request payload. Each can take a key to extract a single field, or no argument to get the whole object."
Q: What HTTP status codes does Nest return by default?
"By default it returns 200 OK for most handlers and 201 Created for POST handlers. You can override this with the @HttpCode decorator, for example @HttpCode(204) for a no-content response."
Q: Why does route order matter?
"Nest matches routes in the order they're declared. A specific path like @Get('active') must be declared before a dynamic one like @Get(':id'), otherwise 'active' would be captured as the id parameter and the specific handler would never run."
Q: Should you use @Req() to access the request?
"Generally no. Nest provides dedicated decorators like @Param, @Query, and @Body that are cleaner and keep you decoupled from the underlying Express or Fastify object. @Req is an escape hatch for the rare case you need something those decorators don't expose."
Key Points to Remember
| Concept | Key Takeaway |
|---|---|
| Controller | Handles routes; stays thin; delegates to services. |
| @Controller('x') | Sets a route prefix for the whole class. |
| HTTP decorators | @Get, @Post, @Put, @Patch, @Delete map methods to verbs. |
| @Param / @Query / @Body | Extract route params, query fields, and the payload. |
| Params are strings | Convert to numbers yourself (or via a pipe). |
| Route order | Specific routes before dynamic :id routes. |
What's Next?
Controllers keep pointing to a "service" that does the real work — but where do services come from, and how does Nest hand them to the controller without us calling new? That's Dependency Injection, the beating heart of Nest. In Chapter 04 we cover Providers & DI, and the "magic" finally makes sense.
Keep coding, keep shipping! See you in the next one!
Post a Comment