Home / Articles / Type-Safe Express APIs with Zod and OpenAPI in One Contract

This article is published in English.

Type-Safe Express APIs with Zod and OpenAPI in One Contract

Validate requests at the edge and emit OpenAPI from the same schemas so docs never drift.

746 words

This walkthrough rebuilds an operable path for: Building a Type-Safe Express API with Zod and OpenAPI. Focus on contracts, checks, and code you can drop into a repo without guessing intent. For Overview, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

The idea

For The idea, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Validate at the boundary with schemas that also generate docs. One source of truth beats drift between OpenAPI and handlers.

const CreateUserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

api.post("/users", {
  body: CreateUserSchema,
  response: {
    201: UserSchema,
  },
  handler: async (req) => {
    const user = await createUser(req.body);
    return {
      status: 201,
      body: user,
    };
  },
});

Why build another Express library?

For Why build another Express library?, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments. Validate at the boundary with schemas that also generate docs. One source of truth beats drift between OpenAPI and handlers.

Where it is today

For Where it is today, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Validate at the boundary with schemas that also generate docs. One source of truth beats drift between OpenAPI and handlers. For Where it is today, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

you’d love developer feedback

For you’d love developer feedback, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Return structured errors clients can branch on. Stringly typing failures forces guesswork.

Operational checklist

For Operational checklist, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.

Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish.

Return structured errors clients can branch on. Stringly typing failures forces guesswork.

Prefer boring reliability over clever one-off demos.

Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

Return structured errors clients can branch on. Stringly typing failures forces guesswork.

Before promoting the stack, freeze versions, capture a golden transcript for the critical path, and confirm rollback steps. Shared environments need rate limits, tenancy checks, and a clear owner for secret rotation. Prefer boring reliability over clever one-off demos.