đź“– Guide Documents
Core Library
TypedQuery

Outline

@nestia/core
export function TypedQuery(): ParameterDecorator;
export namespace TypedQuery {
  export function Body(): ParameterDecorator;
  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;
}

Decorators for query parameters.

What the query parameters are?

This is the query parameters!

  • name=Samchon&age=20&sex=male
⚠️

@TypedQuery() is not essential for Swagger Documents or SDK Library building.

Therefore, it is not a matter to use @TypedQuery() or @Query() of the original NestJS.

@TypedQuery()

@nestia/core
export function TypedQuery(): ParameterDecorator;

Type safe URL query decorator.

@TypedQuery() is a decorator parsing URL query.

It's almost same with original @Query() function of NestJS, but @TypedQuery() is more stable and general.

While NestJS does not support query type validation, @TypedQuery() validates the request query type and throws 400 bad request error when mismatched. Also, while NestJS does not support property type (@Query() only supports string typed properties), @TypedQuery() can define variable property types like bigint, number or boolean.

BbsArticlesController.ts
import { TypedQuery, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import { tags } from "typia";
 
import { IBbsArticle } from "./IBbsArticle";
import { IPage } from "./IPage";
 
@Controller("bbs/articles")
export class BbsArticlesController {
  @TypedRoute.Get()
  public async index(
    @TypedQuery() query: IPage.IRequest
  ): Promise<IPage<IBbsArticle.ISummary>> {
    return {
      pagination: {
        current: query.page ?? 1,
        limit: query.limit ?? 100,
        records: 0,
        pages: 0,
      },
      data: [],
    };
  }
}

Just call @TypedQuery() function on the query parameter, that's all.

Nestia will analyze your type (IPage.IRequest), and writes optimal code for the target type, in the compilation level. If you click the "Compiled JavaScript" file tab of above and fine enhanced lines by blue lines, you can see the optimal parsing and validation code.

Such optimization is called AOT (Ahead of Time) compilation, and it is the secret of @TypedQuery.

TypedQuery.Body()

@nestia/core
export namespace TypedQuery {
  export function Body(): ParameterDecorator;
}

Request body decorator of application/x-www-form-urlencoded format.

If you call @TypedQuery.Body() decorator function on a specific parameter, the parameter will be parsed from the request body as application/x-www-form-urlencoded format. Otherwise, you want to declare a application/json format response body, use @TypedBody() decorator function instead.

BbsArticlesController.ts
import { TypedQuery } from "@nestia/core";
import { Controller } from "@nestjs/common";
 
import { IBbsArticle } from "@api/lib/structures/IBbsArticle";
 
@Controller("bbs/articles")
export class BbsArticlesController {
  @TypedQuery.Post()
  public async store(
    @TypedQuery.Body() body: IBbsArticle.IStore,
  ): Promise<IBbsArticle> {
    return {
      id: "00000000-0000-0000-0000-000000000000",
      writer: "Samchon",
      title: body.title,
      body: body.body,
      created_at: new Date().toISOString(),
    };
  }
}

TypedQuery.Post()

@nestia/core
export namespace TypedQuery {
  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;
}

Route decorators of application/x-www-form-urlencoded format response body.

If you call one of below decorator functions on a method, the method will return application/x-www-form-urlencoded format response body. Otherwise, you want to declare a application/json format response body, use @TypedRoute.Post() decorator function instead.

  • @TypedQuery.Get()
  • @TypedQuery.Post()
  • @TypedQuery.Put()
  • @TypedQuery.Patch()
  • @TypedQuery.Delete()
BbsArticlesController.ts
import { TypedQuery } from "@nestia/core";
import { Controller } from "@nestjs/common";
 
import { IBbsArticle } from "@api/lib/structures/IBbsArticle";
 
@Controller("bbs/articles")
export class BbsArticlesController {
  @TypedQuery.Post()
  public async store(
    @TypedQuery.Body() body: IBbsArticle.IStore,
  ): Promise<IBbsArticle> {
    return {
      id: "00000000-0000-0000-0000-000000000000",
      writer: "Samchon",
      title: body.title,
      body: body.body,
      created_at: new Date().toISOString(),
    };
  }
}

Special Tags

You can enhance validation logic, of @TypedQuery(), through comment tags.

You know what? @TypedQuery() utilizes typia.assert<T>() (opens in a new tab) function for query data validation, and the typia.assert<T>() (opens in a new tab) function supports additional type checking logics through comment tags. For reference, "Type Tag" means a intersection type with atomic type and special tag type of typia like number & tags.Type<"uint32">, and "Comment Tag" means a comment starting from @ symbol following @${name} ${value} format.

With those type and comment tags, you can add additional validation logics. If you want to add a custom validation logic, you also can do it. Read below Guide Docments of typia (opens in a new tab), and see the example code. You may understand how to utilize such type and comment tags, in a few minutes.

examples/src/is-special-tags.ts
import typia, { tags } from "typia";
 
export const checkSpecialTag = typia.createIs<SpecialTag>();
 
interface SpecialTag {
  int32: number & tags.Type<"int32">;
  range?: number & tags.ExclusiveMinimum<19> & tags.Maximum<100>;
  minLength: string & tags.MinLength<3>;
  pattern: string & tags.Pattern<"^[a-z]+$">;
  date: null | (string & tags.Format<"date">);
  ip: string & (tags.Format<"ipv4"> | tags.Format<"ipv6">);
  uuids: Array<string & tags.Format<"uuid">> &
    tags.MinItems<3> &
    tags.MaxItems<100>;
}

Restriction

When using @TypedQuery(), you've to follow such restrction.

At first, type of @TypedQuery() must be a pure object type. It does not allow union type. Also, nullable and undefindable types are not allowed, either. Note that, query parameter type must be a sole object type without any extra definition.

At next, type of properties must be atomic, or array of atomic type. In the atomic type case, the atomic type allows both nullable and undefindable types. However, mixed union atomic type like string | number or "1" | "2" | 3 are not allowed. Also, the array type does not allow both nullable and undefindable types, either.

  • boolean
  • number
  • bigint
  • string
SomeQueryDto.ts
export interface SomeQueryDto {
  //----
  // ATOMIC TYPES
  //----
  // ALLOWED
  boolean: boolean;
  number: number;
  string: string;
  bigint: bigint;
  optional_number?: number;
  nullable_string: string | null;
  literal_union: "A" | "B" | "C" | "D";
 
  // NOT ALLOWED
  mixed_union: string | number | boolean;
  mixed_literal: "A" | "B" | 3;
 
  //----
  // ARRAY TYPES
  //----
  // ALLOWED
  nullable_element_array: (string | null)[];
  string_array: string[];
  number_array: number[];
  literal_union_array: ("A" | "B" | "C")[];
  literal_tuple: ["A", "B", "C"];
 
  // NOT ALLOWED
  optional_element_array: (string | undefined)[];
  optional_array: string[] | undefined;
  nullable_array: string[] | null;
  union_atomic_array: (string | number)[];
  mixed_literal_array: ("A", "B", 3)[];
  mixed_tuple: ["A", "B", 3];
}