Route Parameters and Query

@Param and @Query

@Param('id') reads a path segment. @Query('name') reads a query string value. Both are strings unless a pipe converts them.

import { Controller, DefaultValuePipe, Get, Param, ParseIntPipe, Query } from '@nestjs/common';

@Controller('products')
export class ProductsController {
  // GET /products?category=audio&page=2
  @Get()
  findAll(
    @Query('category') category: string | undefined,
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
  ) {
    return { category: category ?? 'all', page };
  }

  // GET /products/7
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return { id, typeOfId: typeof id };
  }
}

Requests and Responses

Example

curl "http://localhost:3000/products?category=audio&page=2"
curl http://localhost:3000/products
curl http://localhost:3000/products/7
curl -i http://localhost:3000/products/abc

Example output

{"category":"audio","page":2}
{"category":"all","page":1}
{"id":7,"typeOfId":"number"}

HTTP/1.1 400 Bad Request
{"message":"Validation failed (numeric string is expected)","error":"Bad Request","statusCode":400}

How Path Matching Works

Example

Underneath, the router compares the pattern /products/:id with the URL segment by segment. A tiny version:

function match(pattern: string, url: string): Record<string, string> | null {
  const [path, qs = ''] = url.split('?');
  const want = pattern.split('/');
  const got = path.split('/');
  if (want.length !== got.length) return null;
  const params: Record<string, string> = {};
  for (let i = 0; i < want.length; i++) {
    if (want[i].startsWith(':')) params[want[i].slice(1)] = decodeURIComponent(got[i]);
    else if (want[i] !== got[i]) return null;
  }
  for (const pair of qs.split('&').filter(Boolean)) {
    const [k, v] = pair.split('=');
    params['query.' + k] = v;
  }
  return params;
}

console.log(match('/products/:id', '/products/7'));
console.log(match('/orders/:orderId/items/:itemId', '/orders/42/items/3?expand=product'));
console.log(match('/products/:id', '/users/7'));

Output

{ id: '7' }
{ orderId: '42', itemId: '3', 'query.expand': 'product' }
null