TS in Node

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.

4 min read Level 2/5 #typescript#node#server
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 →