Skip to content
NestJS ns fundamentals 5 min read

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

DecoratorReadsTypical use
@Param(key?)Path segmentsResource identifiers
@Query(key?)Query stringFilters, pagination, sorting
@Body(key?)Request payloadCreate/update operations
@Headers(key?)HTTP headersAuth tokens, content negotiation
@Ip()Client IPRate limiting, audit logs
@HostParam()Host subdomainMulti-tenant routing
@Req() / @Request()Raw request objectEscape hatch (avoid)
@Res() / @Response()Raw response objectLibrary-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
  }
}
ModeHow response is sentInterceptors applyWhen to use
Standard (default)return a valueYesAlmost always
@Res() library-specificYou call res.send()No (response mapping bypassed)Streaming, custom headers + manual body
@Res({ passthrough: true })return a value, tweak resYesSet 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 ValidationPipe so input is validated and typed.
  • Convert route/query strings with pipes like ParseIntPipe instead of manual Number() 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 @Req access.
Last updated June 14, 2026
Was this helpful?