Express + TypeScript cheatsheet.
Install
npm i express
npm i -D @types/express
Basic app
import express, { Request, Response, NextFunction } from "express";
const app = express();
app.use(express.json());
app.get("/", (req, res) => {
res.json({ ok: true });
});
app.listen(3000);
Typed handler
app.get("/users/:id", (req: Request<{ id: string }>, res: Response<User>) => {
res.json({ id: Number(req.params.id), name: "A", email: "[email protected]" });
});
Request<Params, ResBody, ReqBody, Query>:
Request<
{ id: string }, // params
User, // res body type
{ name: string }, // req body
{ page?: string } // query
>
RequestHandler type
import { RequestHandler } from "express";
const getUser: RequestHandler<{ id: string }, User> = (req, res) => {
res.json({ id: Number(req.params.id), name: "A", email: "..." });
};
app.get("/users/:id", getUser);
Middleware
const logger: RequestHandler = (req, res, next) => {
console.log(req.method, req.url);
next();
};
app.use(logger);
Async handler wrapper
Express doesn’t catch async errors by default:
type AsyncHandler = (req: Request, res: Response, next: NextFunction) => Promise<unknown>;
const asyncHandler = (fn: AsyncHandler): RequestHandler =>
(req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
app.get("/x", asyncHandler(async (req, res) => {
const data = await fetchSomething();
res.json(data);
}));
Or use express-async-errors.
Error handler
class AppError extends Error {
constructor(public status: number, msg: string) { super(msg); }
}
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.status).json({ error: err.message });
}
console.error(err);
res.status(500).json({ error: "internal" });
});
Error middleware must have 4 args.
Augmenting Request
// types/express.d.ts
declare global {
namespace Express {
interface Request {
user?: { id: number; email: string };
}
}
}
export {};
app.use((req, res, next) => {
req.user = { id: 1, email: "[email protected]" };
next();
});
app.get("/", (req, res) => {
res.json({ id: req.user?.id });
});
Zod validation middleware
import { z } from "zod";
function validate<T>(schema: z.ZodSchema<T>) {
return (req: Request, res: Response, next: NextFunction) => {
const r = schema.safeParse(req.body);
if (!r.success) return res.status(400).json(r.error.flatten());
req.body = r.data;
next();
};
}
const NewUser = z.object({
name: z.string().min(1),
email: z.string().email(),
});
app.post("/users", validate(NewUser), (req, res) => {
// req.body typed as z.infer<typeof NewUser>
});
Router
import { Router } from "express";
const router = Router();
router.get("/", listUsers);
router.post("/", validate(NewUser), createUser);
router.get("/:id", getUser);
app.use("/users", router);
CORS
npm i cors
npm i -D @types/cors
import cors from "cors";
app.use(cors({
origin: ["https://app.example.com"],
credentials: true,
}));
Helmet
npm i helmet
import helmet from "helmet";
app.use(helmet());
Body parsing
app.use(express.json({ limit: "1mb" }));
app.use(express.urlencoded({ extended: true }));
app.use(express.raw({ type: "application/octet-stream" }));
File upload (multer)
npm i multer
npm i -D @types/multer
import multer from "multer";
const upload = multer({ dest: "uploads/", limits: { fileSize: 10 * 1024 * 1024 } });
app.post("/upload", upload.single("file"), (req, res) => {
req.file; // { fieldname, filename, path, ... }
});
Sessions / cookies
npm i cookie-parser express-session
npm i -D @types/cookie-parser @types/express-session
import session from "express-session";
app.use(session({
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: { secure: true, httpOnly: true },
}));
Augment session:
declare module "express-session" {
interface SessionData {
userId?: number;
}
}
Graceful shutdown
const server = app.listen(3000);
function shutdown() {
console.log("shutting down");
server.close(() => process.exit(0));
}
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
Environment
import { z } from "zod";
const Env = z.object({
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(["development", "production", "test"]),
});
const env = Env.parse(process.env);
Common mistakes
- Forgetting to
awaitasync middleware; errors silently lost. req.bodytyped asany— wrap with Zod validation.- Catch-all
app.use(err)without 4 args — middleware order matters. - Not setting
express.jsonsize limit — DoS vector. - Trusting
req.querytypes — always strings.
Read this next
If you want my Express + Zod + Pino starter, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .