After this lesson, you will be able to: Type Node.js + Express/Fastify APIs: requests, middleware, validation with Zod.
TS shines on backend boundaries, request/response shapes are exactly where types catch bugs.
Validate at the boundary; derive types from the schema.
import { z } from "zod";const CreateUserSchema = z.object({email: z.string().email(),name: z.string().min(1),age: z.number().int().min(0).optional(),});type CreateUser = z.infer<typeof CreateUserSchema>;// { email: string; name: string; age?: number }// In an API handlerapp.post("/users", async (req, res) => {const parsed = CreateUserSchema.safeParse(req.body);if (!parsed.success) {return res.status(400).json({ error: parsed.error.flatten() });}const user = await createUser(parsed.data); // typed!res.json(user);});
Augment Request via declaration merging when adding context.
import express, { type Request, type Response, type NextFunction } from "express";// Augment Request with auth userdeclare module "express-serve-static-core" {interface Request {user?: { id: string };}}const app = express();app.use(express.json());function requireAuth(req: Request, res: Response, next: NextFunction) {const token = req.headers.authorization?.replace("Bearer ", "");if (!token) return res.status(401).json({ error: "no token" });const user = verifyToken(token);if (!user) return res.status(401).json({ error: "invalid" });req.user = user;next();}app.get("/me", requireAuth, (req, res) => {res.json({ id: req.user!.id });});
Newer; cleaner TS integration.
import Fastify from "fastify";import { z } from "zod";import { fastifyZodOpenApi } from "fastify-zod-openapi";const app = Fastify();app.register(fastifyZodOpenApi);app.post("/users", {schema: {body: CreateUserSchema,response: { 200: z.object({ id: z.string() }) },},async handler(req, reply) {// req.body is typed as CreateUserconst user = await createUser(req.body);return { id: user.id };},});
Trusting req.body without parsing. JS objects from network are `unknown` until validated. Defining types separately from runtime validators. They drift. Always derive types from Zod schemas (or similar). Manual JSON.parse without try/catch. Throws on invalid input. Sending raw error objects in responses. Leak stack traces. Always format to safe shape.
Pick the cleanest answer.
Sign in and purchase access to unlock this lesson.