Skip to content
TypeWire

@tahanabavi/typewire-nestjs

documents v0.1.1

NestJS integration for the TypeWire ecosystem — serve one set of Zod contracts over every wire your client speaks: REST/HTTP, gRPC (Connect JSON), GraphQL and typesocket WebSocket gateways, with request/response validation on all of them.

Retrofitting existing routes: @UseContract

Keep your own route decorators and add only validation:

ts
@Controller("users")
export class UserController {
@Get(":id")
@UseContract(contracts.user.getUser)
getUser(@ContractPath() path: InferRequest<GetUser>["path"]) { ... }
}

Param decorators

DecoratorReturns
@ContractInput()The whole validated input, shaped like the contract's request schema — exactly what the frontend passed to the client method.
@ContractPath()Validated (and coerced) path params.
@ContractQuery()Validated (and coerced) query params.
@ContractBody()Validated body (the whole input for flat contracts).
@ContractHeaders()Validated headers part.

Native @Param(), @Query(), and @Body() also see the validated, coerced values — the interceptor mirrors them back onto the platform request.

Wire-type coercion

HTTP turns everything in a URL into strings. The typefetch client serializes query/path values with URLSearchParams (true"true", arrays → repeated keys, Date → ISO string, nested objects → JSON). typewire-nestjs reverses that against the contract schema before validating, so contracts written for the client work unchanged on the server:

Contract declaresWire valueHandler receives
z.number()"25"25
z.boolean()"true"true
z.date() (query or body)"2026-01-01T00:00:00.000Z"Date
z.array(z.string())?tags=a&tags=b / ?tags=a["a","b"] / ["a"]
z.object({...}) in query'{"a":1}' (JSON string){ a: 1 }
z.bigint() in query"9007199254740993"9007199254740993n

Coercion never invents data — if a value can't be coerced it is passed through untouched and Zod reports the real error. Disable with coerce: false (per endpoint or globally).

Flat (non-structured) contracts

A request schema that isn't shaped as { path, query, body, headers } is "flat" — the client sends the whole input as the JSON body, and the server validates req.body against the whole schema. Both styles work with both decorators.