Build a Typed REST API with Express, TypeScript & Zod

Lesson 5 of 8

Validating Requests and Building REST Endpoints

Combine Zod schemas, typed handlers, and the async wrapper into full, validated REST endpoints.

A validation middleware factory

TypeScript

safeParse never throws, it returns { success: true, data } or { success: false, error }, which suits middleware better than .parse()'s throw-based style, a bad request is an expected outcome here, not an exceptional one. <T extends z.ZodTypeAny> makes validateBody reusable for any schema, not just one hard-coded shape.

Wiring up the endpoints

TypeScript

Every layer does one job: validateBody rejects malformed input before the handler ever runs, asyncHandler catches anything the handler itself throws, and the handler is left to assume req.body is already a valid CreateTaskInput, because by the time it runs, Zod already checked that.

Status codes that mean something

CodeWhen
200Successful GET/PATCH
201Successful POST that created something
400The request body failed validation
404The requested resource doesn't exist
500An unexpected server error (caught by errorHandler)

WARNING

Validating inputs (req.body, req.params) is the priority, but don't skip checks on values you construct yourself either, tasks.find(...) returning undefined is exactly the kind of case the 404 branch above exists to catch, TypeScript's strict mode will actually force you to handle it.

📝 Validated Endpoints Quiz

Passing score: 70%
  1. 1.Why use safeParse instead of parse inside validation middleware?

  2. 2.A 201 status code conventionally indicates a successful POST that created a new resource.

  3. 3.A request that fails Zod validation should typically respond with HTTP status ____.

  4. 4.What is <T extends z.ZodTypeAny> on validateBody for?

  5. 5.A 404 response is appropriate when a GET /tasks/:id request references an id that does not exist.