import { z } from "zod";
const CreateUser = z.object({
name: z.string().min(1),
age: z.number().int().positive(),
});
type CreateUser = z.infer<typeof CreateUser>;
app.post("/users", (req, res) => {
const result = CreateUser.safeParse(req.body);
if (!result.success) return res.status(400).json(result.error.issues);
const input: CreateUser = result.data;
});
The type is only a promise to the compiler. Nothing checks it when the code runs. TypeScript types are erased at compile time: the compiler removes them, so the running JavaScript has no types left. In Express, req.body is typed any. Adding a type annotation or a cast to it only silences the compiler. A client can still send a missing field, or a string where you expect a number.
To make the type trustworthy, validate external input at the boundary, the place where data enters your app. Use a schema library:
- Zod parses
unknowndata and returns either typed data or a list of issues. z.inferbuilds the static type from the schema.- Because the type comes from the schema, the validator and the type cannot drift apart.
In NestJS, the usual equivalent is DTO (Data Transfer Object) classes with class-validator decorators, plus the global ValidationPipe.
- Types are erased at compile time, so nothing checks request data at runtime.
- Annotating or casting
req.bodyonly silences the compiler. - Validate input at the boundary with a schema, such as zod or class-validator.
z.inferbuilds the type from the schema, so there is one source of truth.
Read more
- Zod (https://zod.dev/)zod.dev
- NestJS validation (https://docs.nestjs.com/techniques/validation)docs.nestjs.com