Chapter 10.2☕ 18 min read

TS with Node.js (Express)

TypeScript secures your Express APIs like palace gate security — every request is verified.

01The Palace Gate Security

Express.js is the most popular web framework for Node.js, and TypeScript makes it significantly safer. Every route handler receives a Request and Response object — and TypeScript lets you type BOTH the incoming data (params, body, query) and the outgoing data (response body). This turns your API into a type-safe fortress, like the security at the Nizam's palace gates.

Imagine the security checkpost at the Nizam's palace in Hyderabad. Every person entering the palace must present proper identification (params — who are you?). They must state their purpose of visit (body — what are you here for?). The guards verify the information (middleware — check the credentials). And they decide which area to grant access to (response — what data to send back).

TypeScript is the guard who checks everything at every gate. The visitor doesn't just say "I'm here to see the Nizam" — they present a typed request: Request<{ visitorId: string }, { name: string }> with a specific purpose: { reason: "meeting", department: "finance" }. The guard knows EXACTLY what information to expect, and TypeScript ensures that the information matches the expected types. If something is wrong, the guard catches it at the gate — before the visitor enters the palace.

02Typing Express Request & Response

The core of Express type safety is the Request and Response types, which accept generic parameters to specify the shape of your data.

import express, {
  Request,
  Response,
  NextFunction
} from "express";

const app = express();
app.use(express.json()); // body parser

interface User {
  id: string;
  name: string;
  email: string;
}

// GET route with typed params
app.get<
  { id: string },  // Params
  User            // Response body
>(
  "/users/:id",
  (req, res) => {
    // req.params.id is string ✓
    const user: User = {
      id: req.params.id,
      name: "Imran",
      email: "imran@example.com"
    };
    res.json(user);
    // res.json() expects User ✓
  }
);

Complete Example with All Type Parameters

interface CreateUserBody {
  name: string;
  email: string;
}

interface User {
  id: string;
  name: string;
  email: string;
}

app.post<
  {},              // Params (none)
  User,            // Response body
  CreateUserBody,  // Request body
  {}               // Query (none)
>(
  "/api/users",
  (req, res) => {
    // req.body.name is string ✓
    // req.body.email is string ✓
    const user: User = {
      id: "123",
      name: req.body.name,
      email: req.body.email,
    };
    res.status(201).json(user);
  }
);
03Route Handlers & Middleware

Express middleware and route handlers also benefit from TypeScript typing. Let's look at common patterns.

Typed Router

const router = express.Router();

router.get("/users/:id", (
  req: Request<{ id: string }>,
  res: Response<User>
) => {
  // ...
});

router.post("/users", (
  req: Request<
    {},
    User,
    CreateUserBody
  >,
  res: Response<User>
) => {
  // ...
});

app.use("/api", router);

Custom Middleware with Typed Request

// Extend Request via declaration merging
declare module "express" {
  interface Request {
    user?: User;
  }
}

// Auth middleware
function authMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
): void {
  const token = req.headers.authorization;
  if (!token) {
    res.status(401).json({
      error: "Unauthorized"
    });
    return;
  }
  // After verification:
  req.user = { id: "1", name: "Imran" };
  next();
}

// Usage in route
app.get(
  "/profile",
  authMiddleware,
  (req, res) => {
    // req.user is available!
    res.json(req.user);
  }
);

Error Handling Middleware

// Must have all 4 parameters!
function errorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
): void {
  console.error(err.stack);
  res.status(500).json({
    error: err.message
  });
}

app.use(errorHandler);
04Express + TS Traps

Express + TypeScript has several common pitfalls. Let's walk through the most important ones.

Trap 1: Missing express.json() middleware

Without app.use(express.json()), req.body remains undefined. TypeScript types req.body based on your generic parameter, but the actual value at runtime is undefined. Always add body-parsing middleware.

Trap 2: Incomplete type parameters in Request

// ❌ All generic params are optional,
//    leaving them out gives any
app.get("/users/:id", (req, res) => {
  req.params.id; // any!
  req.body;      // any!
});

// ✅ Specify the ones you use
app.get<{ id: string }>(
  "/users/:id",
  (req, res) => {
    req.params.id; // string ✓
  }
);

Trap 3: Forgetting NextFunction in error middleware

Express identifies error middleware by the 4-parameter signature. Without the 4th parameter (next), Express treats it as regular middleware, and errors pass through unhandled.

Trap 4: Not extending Request for user/auth properties

When you add properties like req.user in middleware, TypeScript doesn't know about them unless you use declaration merging. Create a types.d.ts file to extend the Express Request interface.

05Express + TypeScript Cheatsheet

Here's your complete cheatsheet for Express + TypeScript!

Request Type Parameters:

Request<P, ResBody, ReqBody, Q>
// P = Params     (default: {})
// ResBody = Response body (default: any)
// ReqBody = Request body  (default: any)
// Q = Query string  (default: qs.ParsedQs)

Basic Route Patterns:

app.get<{ id: string }, User>("/path", handler)
app.post<{}, User, CreateBody>("/path", handler)
app.put<{ id: string }, User, UpdateBody>("/path", handler)
app.delete<{ id: string }>("/path", handler)

Middleware & Error Handling:

app.use(express.json());
app.use(authMiddleware);
app.use(errorHandler); // 4 params!

Key Rules:

  • Always specify at least the params type parameter for routes with URL parameters
  • Use app.use(express.json()) before any route that receives JSON bodies
  • Use declaration merging to extend Request for custom properties like req.user
  • Error middleware MUST have all 4 parameters (err, req, res, next)
  • Use Router for modular route organization

The Golden Rule: "Express + TypeScript is like palace gate security — every request is checked at every gate. The params are the visitor's ID, the body is their purpose, the middleware is the security clearance, and the response is the access granted. Type the parameters, and your API becomes a secure, well-guarded palace, bhai!"

Key Takeaways

  • Express Request has 4 generic type parameters: Params, ResBody, ReqBody, Query
  • Always use app.use(express.json()) to parse JSON request bodies
  • Type route handler parameters with Request for full safety
  • Error-handling middleware must have exactly 4 parameters: err, req, res, next
  • Use declaration merging to extend Express Request for custom properties like req.user
  • Use express.Router() for modular route organization with typed handlers
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase