Skip to Content

@TypedQuery

@nestia/core
export function TypedQuery(): ParameterDecorator; export namespace TypedQuery { export function Body(): ParameterDecorator; // application/x-www-form-urlencoded body export function Get(path?: string): MethodDecorator; export function Post(path?: string): MethodDecorator; export function Put(path?: string): MethodDecorator; export function Patch(path?: string): MethodDecorator; export function Delete(path?: string): MethodDecorator; }

Family of decorators for the URL query string.

  • @TypedQuery() β€” parameter decorator. Parses the query into a typed DTO, validates each field, coerces strings into number / boolean / bigint / null.
  • @TypedQuery.Body() β€” parameter decorator for application/x-www-form-urlencoded request bodies. Same parsing semantics, different content type.
  • @TypedQuery.${method}(path) β€” route decorator companion to TypedRoute.${method}. Pairs naturally with @TypedQuery.Body() for form-urlencoded POSTs.

Drop-in replacement for @Query() from @nestjs/common β€” but @Query() only sees strings; @TypedQuery() gives you the type your handler declared.


Basic usage β€” @TypedQuery()

src/controllers/ArticlesController.ts
import { TypedQuery, TypedRoute } from "@nestia/core"; import { Controller } from "@nestjs/common"; import { tags } from "typia"; interface IListQuery { page?: number & tags.Type<"uint32"> & tags.Minimum<1>; limit?: number & tags.Type<"uint32"> & tags.Minimum<1> & tags.Maximum<100>; sort?: "newest" | "oldest"; q?: string; } @Controller("articles") export class ArticlesController { @TypedRoute.Get() public async list( @TypedQuery() query: IListQuery, ): Promise<IPage<IArticle>> { const page = query.page ?? 1; const limit = query.limit ?? 20; // … } }

A request to GET /articles?page=2&limit=50&sort=newest calls the handler with { page: 2, limit: 50, sort: "newest" }. A request to GET /articles?page=abc returns 400 before the handler runs.

Things @TypedQuery() does that vanilla @Query() does not:

  • Coerce "42" β†’ 42 when the field is typed number.
  • Coerce "true" / "false" β†’ boolean.
  • Coerce "null" β†’ null, "123n" β†’ bigint.
  • Validate tags.* constraints (Format, MinLength, Maximum, …).
  • Accept arrays via repeated keys (?tag=ts&tag=node β†’ tags: ["ts", "node"]).

Nested DTOs and Swagger decomposition

A flat DTO is the typical case, but nested objects work too:

interface ISearchQuery { filter?: { section?: string; after?: string & tags.Format<"date-time">; }; }

By default, Swagger documents the whole object as a single query schema. To decompose it into one query parameter per leaf field (the more conventional OpenAPI style), set swagger.decompose: true in nestia.config.ts:

nestia.config.ts
const config: INestiaConfig = { input: () => NestFactory.create(AppModule), output: "src/api", swagger: { output: "dist/swagger.json", decompose: true, }, };

The runtime parsing is identical either way β€” decompose only affects the generated OpenAPI schema.


@TypedQuery.Body() β€” form-urlencoded bodies

src/controllers/AuthController.ts
import { TypedQuery, TypedRoute } from "@nestia/core"; import { Controller } from "@nestjs/common"; import { tags } from "typia"; interface ILoginForm { email: string & tags.Format<"email">; password: string; remember?: boolean; } @Controller("auth") export class AuthController { @TypedQuery.Post("login") public async login( @TypedQuery.Body() form: ILoginForm, ): Promise<{ token: string }> { ... } }

Use this for legacy HTML form posts and OAuth token endpoints where the body is application/x-www-form-urlencoded. For multipart/form-data (file uploads), use @TypedFormData.


When to use plain @Query instead

Reach for the vanilla decorator when:

  • You need raw strings without coercion or validation (rare).
  • A single route was working with @Query() and you don’t want the migration churn.

Vanilla query parameters are invisible to @nestia/sdk’s generators.


Common pitfalls

Array params need repeated keys. ?tags=a,b,c does not parse as ["a","b","c"]. Send ?tags=a&tags=b&tags=c instead, or declare the field as string and split it yourself.

Trailing = for boolean. Browsers sometimes send ?remember (no value) to mean true. @TypedQuery() follows form-urlencoded semantics β€” declare remember: boolean and send ?remember=true.

Big numbers. Query strings carry no type information, so 42.0000001 round-trips as number. Use bigint and send ?x=42n if you need integer precision.


See also

Last updated on