@TypedParam
export function TypedParam(name: string): ParameterDecorator;Drop-in replacement for @Param("name") from @nestjs/common. Identical behavior plus:
- The path parameter is coerced to the TypeScript type (
"42"→42,"42"→42nfor abigint,"true"→true,"null"→null). A string with a format, such as a date, stays a string and is validated. - The coerced value is validated — invalid
uuid, out-of-rangenumber, malformeddate, etc. become a400 Bad Request. - The parameter is visible to
@nestia/sdk— appears in the generated SDK with the same type your handler sees.
Vanilla @Param("id") always gives you a string. @TypedParam("id") gives you exactly the type you declared.
You can reuse an imported scalar alias such as type UuidString = string & tags.Format<"uuid">. Imports resolved through compilerOptions.paths retain the alias’s validation rules during both normal compilation and ttsc --noEmit, including when the paths configuration is inherited.
Basic usage
import { TypedParam, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import { tags } from "typia";
@Controller("articles")
export class ArticlesController {
// Path: GET /articles/2b5e21d8-0e44-4482-bd3e-4540dee7f3d6
// `id` is typed as `string & tags.Format<"uuid">`
@TypedRoute.Get(":id")
public async at(
@TypedParam("id") id: string & tags.Format<"uuid">,
): Promise<IArticle> { ... }
// Path: GET /articles/section/general/page/42
// `section` is `string`, `page` is `number & tags.Type<"uint32">`
@TypedRoute.Get("section/:section/page/:page")
public async list(
@TypedParam("section") section: string,
@TypedParam("page") page: number & tags.Type<"uint32">,
): Promise<IPage<IArticle>> { ... }
}The string "42" arrives at the wire, but page in your handler is already a number. A request to /articles/section/general/page/abc returns 400 before your handler is called.
Supported parameter types
| Type | Wire example | Coerces to |
|---|---|---|
string | "hello" | "hello" |
string & tags.Format<"uuid"> | "2b5e2…" | "2b5e2…" (validated) |
string & tags.Format<"date"> | "2024-04-01" | "2024-04-01" (validated) |
number | "42" | 42 |
number & tags.Type<"uint32"> | "42" | 42 (validated) |
bigint | "42" | 42n |
boolean | "true" / "false" | true / false |
null | "null" | null |
"a" | "b" | "c" | "a" | "a" (validated) |
Nullable: T | null | as above | T or null |
Tags layered on top (MinLength, Maximum, Pattern, etc.) work the same as on body / query types.
Path parameters are flat by definition — you cannot decode a nested object out of a single segment. If you need an object, use @TypedQuery or @TypedBody.
When to use plain @Param instead
Reach for the vanilla decorator when:
- The param is consumed by NestJS-only middleware that expects raw strings.
- You’re migrating a route piecewise and don’t want to add the dependency yet.
@nestia/sdk reads vanilla params too: the SDK function takes the argument and the Swagger document lists the path parameter, typed as declared. The SDK generators still hold the declared type to the rules @TypedParam() enforces, a single atomic or constant type, and report a diagnostic otherwise; only the handler receives the raw string, without coercion or validation.
Common pitfalls
Validation is on the coerced value, not the raw string. A path of /articles/abc against @TypedParam("id") id: number fails because "abc" cannot become a number — the error is coercion, not a Minimum<…> violation. The 400 response distinguishes between them.
Path-only. @TypedParam reads from path parameters (:name in the route). For querystring values use @TypedQuery; for headers, @TypedHeaders.
A literal colon needs the router’s escape. In a route, :name is a parameter, so a literal :, as in an AIP custom method, is spelled the way your adapter’s router reads it. @nestia/sdk reads both spellings: the SDK sends /items/7:cancel, and the Swagger path is /items/{id}:cancel. @WebSocketRoute accepts both too.
- Express escapes it as
\\:, and escapes*,(,+, and the other characters it reserves the same way. - Fastify spells a literal colon
::. This works only in static text: after a parameter, Fastify reads::as part of the parameter’s name, so it cannot express:idfollowed by:cancel.
@TypedRoute.Post("items/:id\\:cancel") // Express
public cancel(@TypedParam("id") id: string): Promise<void> { ... }
@TypedRoute.Post("items::batchGet") // Fastify
public batchGet(): Promise<void> { ... }@nestia/migrate writes the Express spelling for a document path such as /items/{id}:cancel.
See also
- TypedQuery — query-string parameters, including nested DTOs.
- TypedBody — JSON request bodies.
- TypedRoute — the route decorators these parameters live on.