Route & Query Parameters
Almost every request carries data that your handler needs: an ID in the URL path, filters in the query string, a JSON payload in the body, or metadata in the headers. NestJS exposes all of this through parameter decorators that you attach to method arguments, so you extract exactly what you need without ever touching the raw request object. This keeps controllers declarative, testable, and framework-agnostic. This page walks through each decorator and the trade-offs of dropping down to library-specific request/response handling.
Route parameters with @Param
Route parameters are the dynamic segments of a path, declared with a leading colon. Use @Param('name') to pull a single token, or @Param() with no argument to receive the whole params object.
import { Controller, Get, Param } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id') id: string): string {
return `Fetching user ${id}`;
}
@Get(':id/posts/:postId')
findPost(
@Param('id') userId: string,
@Param('postId') postId: string,
): string {
return `User ${userId}, post ${postId}`;
}
}
Output:
GET /users/42 -> Fetching user 42
GET /users/42/posts/7 -> User 42, post 7
Route params always arrive as strings. To get a real
number, apply a pipe such as@Param('id', ParseIntPipe) id: number. Pipes validate and transform before your handler runs.
Query strings with @Query
Query parameters follow the ? in a URL and are ideal for optional filtering, pagination, and sorting. @Query('key') extracts one value; @Query() returns the entire parsed object.
import { Controller, Get, Query } from '@nestjs/common';
@Controller('products')
export class ProductsController {
@Get()
list(
@Query('page') page = '1',
@Query('limit') limit = '20',
@Query('sort') sort?: string,
): object {
return { page: Number(page), limit: Number(limit), sort };
}
}
Output:
GET /products?page=2&limit=50&sort=price
-> { "page": 2, "limit": 50, "sort": "price" }
For anything beyond a couple of keys, bind the query to a DTO and let a ValidationPipe coerce and validate it, rather than reading loose strings one by one.
Request body with @Body
@Body() deserializes the incoming JSON (or form) payload. Pair it with a DTO class so validation and typing flow through automatically.
import { Controller, Post, Body } from '@nestjs/common';
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
}
@Controller('users')
export class UsersController {
@Post()
create(@Body() dto: CreateUserDto): object {
return { created: true, ...dto };
}
}
You can also grab a single body field with @Body('email'), though binding the whole DTO is preferred so validation covers the full shape.
Headers and beyond
@Headers('name') reads one header (case-insensitive); @Headers() returns all of them. Related decorators cover IPs, hosts, and uploaded files.
import { Controller, Get, Headers, Ip } from '@nestjs/common';
@Controller('diagnostics')
export class DiagnosticsController {
@Get()
info(
@Headers('user-agent') userAgent: string,
@Ip() ip: string,
): object {
return { ip, userAgent };
}
}
Decorator reference
| Decorator | Reads | Typical use |
|---|---|---|
@Param(key?) | Path segments | Resource identifiers |
@Query(key?) | Query string | Filters, pagination, sorting |
@Body(key?) | Request payload | Create/update operations |
@Headers(key?) | HTTP headers | Auth tokens, content negotiation |
@Ip() | Client IP | Rate limiting, audit logs |
@HostParam() | Host subdomain | Multi-tenant routing |
@Req() / @Request() | Raw request object | Escape hatch (avoid) |
@Res() / @Response() | Raw response object | Library-specific mode |
The raw request with @Req
When no dedicated decorator fits, @Req() injects the underlying platform request (an Express Request or Fastify FastifyRequest). Type it so you keep IntelliSense, but reach for it sparingly.
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('debug')
export class DebugController {
@Get()
raw(@Req() req: Request): object {
return { method: req.method, path: req.path };
}
}
Depending on the raw request couples your controller to a specific HTTP platform. Prefer
@Param,@Query,@Body, and custom decorators so the same code runs on Express or Fastify unchanged.
Library-specific @Res mode and its trade-offs
By default Nest is in standard mode: you return a value and the framework serializes it, sets the status code, and ends the response. The moment you inject @Res(), Nest switches that handler into library-specific mode — you are now responsible for sending the response yourself.
import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';
@Controller('files')
export class FilesController {
@Get('report')
download(@Res() res: Response): void {
res.set('Content-Type', 'text/csv');
res.status(200).send('id,name\n1,Ada');
}
}
In this mode, returned values are ignored, and interceptors that map the response stream stop working as expected. If you only need the raw response occasionally (say, to set a cookie) but still want Nest to handle the body, opt into passthrough mode:
import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';
@Controller('session')
export class SessionController {
@Get('ping')
ping(@Res({ passthrough: true }) res: Response): object {
res.cookie('seen', '1');
return { ok: true }; // Nest still serializes this
}
}
| Mode | How response is sent | Interceptors apply | When to use |
|---|---|---|---|
| Standard (default) | return a value | Yes | Almost always |
@Res() library-specific | You call res.send() | No (response mapping bypassed) | Streaming, custom headers + manual body |
@Res({ passthrough: true }) | return a value, tweak res | Yes | Set a cookie/header but keep Nest’s pipeline |
Best practices
- Reach for the specific decorator (
@Param,@Query,@Body) before ever touching@Req/@Res. - Bind request data to DTO classes and enable a global
ValidationPipeso input is validated and typed. - Convert route/query strings with pipes like
ParseIntPipeinstead of manualNumber()calls. - Avoid bare
@Res()unless you genuinely need manual control; prefer{ passthrough: true }to keep Nest’s response pipeline intact. - Keep handlers platform-agnostic so you can swap Express for Fastify without rewriting controllers.
- Factor recurring extraction logic (e.g. pulling the authenticated user) into custom parameter decorators rather than repeating
@Reqaccess.