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.