Typing the Server Side
TS in Node
TS on Node — what to install, what to run, and the patterns for request/response, env, and async code.
What you'll learn
- Run a TS Node project with no build step
- Install Node types
- Type Express handlers and middleware
Node + TS is the bread-and-butter server stack. Modern Node has made running TS surprisingly painless.
Run TS Directly
As of Node 22+, you can run .ts files with the
--experimental-strip-types flag:
node --experimental-strip-types server.ts Type-only — no transformations. For full features, use tsx:
npm install --save-dev tsx
npx tsx server.ts Both skip the explicit build step for dev. For production, you
still emit JS via tsc (or your bundler) and run that.
Install Node Types
npm install --save-dev @types/node This gives you types for fs, path, http, etc.
Express With Types
npm install express
npm install --save-dev @types/express import express, { type Request, type Response, type NextFunction } from "express";
const app = express();
app.get("/users/:id", (req: Request, res: Response) => {
const id = req.params.id; // string
res.json({ id });
});
function logger(req: Request, _res: Response, next: NextFunction) {
console.log(`${req.method} ${req.url}`);
next();
}
app.use(logger);
app.listen(3000); Typed Request Bodies
import { type Request, type Response } from "express";
type LoginBody = { email: string; password: string };
app.post("/login", (req: Request<{}, {}, LoginBody>, res: Response) => {
const { email, password } = req.body; // typed!
// ...
}); The Request<P, ResBody, ReqBody, ReqQuery> generic params let
you type each piece.
Typing Env Variables
// src/types/env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
PORT: string;
DATABASE_URL: string;
NODE_ENV: "development" | "production";
}
} Now process.env.PORT is string instead of string | undefined.
Async Errors
Express doesn’t catch async errors out of the box. Wrap your async handlers, or pass them through a helper:
const asyncH = <T extends (req: Request, res: Response) => Promise<unknown>>(fn: T) =>
(req: Request, res: Response, next: NextFunction) => fn(req, res).catch(next);
app.get("/users", asyncH(async (req, res) => {
const users = await db.query("...");
res.json(users);
})); Up Next
Zod — pair TS types with runtime validation.
Runtime Types with Zod →