Building APIs with Express and TypeScript

Build a fully typed REST API. You'll wire up express with @types/express , type Request and Response (including the Request<Params, ResBody, ReqBody> generics), write typed middleware and a Router , handle errors, and validate bodies with Zod .

Learn Building APIs with Express and TypeScript in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a…

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

An Express app is a restaurant kitchen . Each route is a dish on the menu, the handler is the chef who prepares it, and middleware are the prep stations every order passes through — washing, seasoning, plating — before it reaches the chef. next() is sliding the ticket to the next station; a failed check (no reservation) sends the order back without ever reaching the stove.

1. A Typed Express App

An Express app registers routes, each handled by a function that receives a typed Request and Response . The Request<Params, ResBody, ReqBody> generics let you type req.body . Read the real, fully-typed app here:

2. The Request / Response Model

A handler is just a function of (req, res) . You read input from req (params, query, body) and build the reply on res — res.status(code).json(payload) chains together. The box models req / res so you can run it:

3. Middleware & next()

Middleware is a function (req, res, next) that runs before (or around) your handler. It can log, authenticate, parse a body, then call next() to continue — or send a response and not call next() to stop the chain. The box runs a small middleware chain:

4. Routing, Router & Zod Validation

An express.Router() groups related routes into a mountable mini-app ( app.use("/users", router) ). Because Express's types are compile-time only, you pair them with a Zod schema to validate req.body at runtime. The read-only block shows the real Router + Zod pattern; the box models the routing idea:

Your turn. Fill in the two blanks marked ___ to finish a "create user" handler that returns 201, then run it.

No blanks this time — write handleCreate yourself, validating a non-empty title and returning 201 or 400, then run it against a good and a bad body.

Practice quiz

Why install @types/express alongside express?

  • It provides the TypeScript types for Express's Request, Response, and Router
  • It speeds up routing
  • It replaces express
  • It is the runtime server

Answer: It provides the TypeScript types for Express's Request, Response, and Router. express ships as JavaScript; @types/express adds the type declarations so Request, Response, and middleware are typed.

What two core objects does an Express route handler receive?

  • get and post
  • url and body
  • req and res (Request and Response)
  • input and output

Answer: req and res (Request and Response). A handler has the signature (req: Request, res: Response): the incoming request and the outgoing response.

What is Express middleware?

  • A database driver
  • A function that runs during the request/response cycle and can call next()
  • A type definition
  • A test runner

Answer: A function that runs during the request/response cycle and can call next(). Middleware is a function (req, res, next) that can inspect or modify the request, end it, or pass control with next().

Which generic signature lets you type the request body?

  • Request only accepts one type
  • Body<Request>
  • Request<Body>
  • Request<Params, ResBody, ReqBody>

Answer: Request<Params, ResBody, ReqBody>. Express types Request as Request<Params, ResBody, ReqBody, ReqQuery>, so the third parameter types req.body.

What does express.json() middleware do?

  • Parses incoming JSON request bodies into req.body
  • Converts responses to XML
  • Starts the server
  • Renders HTML

Answer: Parses incoming JSON request bodies into req.body. express.json() is body-parsing middleware that reads a JSON request body and populates req.body.

What is the purpose of an express.Router()?

  • To type the response
  • To group related routes into a mountable mini-application
  • To start the HTTP server
  • To validate JSON

Answer: To group related routes into a mountable mini-application. A Router bundles related routes and middleware so they can be mounted under a path like app.use('/users', router).

How do you send a 404 JSON response in a handler?

  • req.send(404)
  • return 404
  • res.error(404)
  • res.status(404).json({ error: 'Not found' })

Answer: res.status(404).json({ error: 'Not found' }). res.status(404) sets the status code and .json(...) serialises and sends the body; they chain together.

What signature identifies an Express error-handling middleware?

  • (res, req)
  • (req, res)
  • (err, req, res, next) with four parameters
  • (next)

Answer: (err, req, res, next) with four parameters. Express recognises error middleware by its FOUR parameters: (err, req, res, next).

How does Zod combine well with an Express API?

  • It replaces Express
  • It validates req.body at runtime before the handler trusts it
  • It compiles the routes
  • It renders templates

Answer: It validates req.body at runtime before the handler trusts it. Express types are compile-time only, so you run req.body through a Zod schema to validate the actual incoming data at runtime.

Why type req.body with a generic instead of leaving it as any?

  • So the compiler checks how you read the body and autocompletes its fields
  • It changes the HTTP method
  • any is required by Express
  • It makes Express faster

Answer: So the compiler checks how you read the body and autocompletes its fields. Typing the body via Request<...> means the compiler verifies the fields you access and provides autocomplete, catching typos.