█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Programming Languages/TypeScript/TypeScript with Node.js + Express/Fastify
45 minIntermediate

TypeScript with Node.js + Express/Fastify

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.

Prerequisites:TypeScript with React

Zod for runtime + compile-time types

Validate at the boundary; derive types from the schema.

python
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 handler
app.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);
});

Express + TypeScript

Augment Request via declaration merging when adding context.

python
import express, { type Request, type Response, type NextFunction } from "express";
// Augment Request with auth user
declare 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 });
});

Fastify (typed by default)

Newer; cleaner TS integration.

python
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 CreateUser
const user = await createUser(req.body);
return { id: user.id };
},
});

Common mistakes only experienced TS+Node devs avoid

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.

Quick Check

Why derive types from a Zod schema instead of writing both?

Pick the cleanest answer.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←TypeScript with React
Back to TypeScript
Declaration Files (.d.ts)→