# express-zod-api validates the request and the response with one Zod schema

> express-zod-api is a TypeScript framework on top of Express and Zod that describes routes as a hierarchical object, derives input and output types from the same schema, and generates OpenAPI 3.2 documentation from it.

**RobinTail/express-zod-api** — A Typescript framework to help you get an API server up and running with I/O schema validation and custom middlewares in minutes.

- Repository: https://github.com/RobinTail/express-zod-api
- Website: https://ez.robintail.cz
- Stars: 840 · Forks: 40
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/robintail-express-zod-api

## Routes as a hierarchical object rather than a stack of app.get calls

The starting complaint in the project's own overview is the repetitive work of starting an API server that also has to validate incoming data. express-zod-api answers it by letting you describe server routes as a hierarchical object instead of registering each path by hand, and by binding the input and output type declarations to the endpoint they belong to.

The structure is the mechanism. An endpoint carries a schema, a handler and a set of middlewares, and the framework derives from that schema what the handler receives and what the client will get back. Because both sides come from the same declaration, the types inside the handler follow the schema, which the README describes as giving your IDE and TypeScript the hints to work with. It also removes a class of bug the framework calls out directly: an empty string, null or undefined arriving where you expected a number.

The audience is TypeScript developers who already like Express and Zod and are tired of wiring the two together by hand. The framework integrates and provides the capabilities of popular web server, logging, validation and documenting solutions rather than replacing them, so an existing Express app can be connected to it rather than rewritten around it.

## The endpoints factory keeps schema, handler and middlewares together

One of the documented features is an endpoints factory, and the example directory shows how it is meant to be used. There are separate files for routing, middlewares, factories, configuration and the entry point, so the shape of a service is visible before you read any logic.

A factory is the piece that makes the pattern repeatable. Instead of hand writing a wrapper for every endpoint, you describe the common shape once and let each endpoint supply only what varies, typically its schema and its handler. That is where the promise about keeping input and output type declarations right next to the handler comes from. When the endpoint is defined in one object, a schema change and a handler change tend to land in the same edit, and the type checker will complain if they drift.

Middlewares are treated as a first class part of that endpoint definition rather than as an Express chain bolted on in front of the router. The README lists middleware features of its own, including refinements and a context object, and also a section on using native Express middlewares, which tells you the framework does not insist on replacing Express conventions.

## Transformations and refinements decide what a request actually becomes

Validation is the headline feature, but the more interesting machinery sits next to it. The feature list includes a query string parser, transformations, top level transformations and mapping, and a dedicated section on dealing with dates. Each of those addresses a case where parsing and validating are not the same operation.

A query string arrives as text. A transformation converts a validated value into a different shape before your handler sees it, and top level mapping lets a whole request be restructured. The refinements feature lets a schema reject or accept values with custom logic beyond the built in type checks. The dates section exists because a timestamp on the wire and a Date object in code are different things, and the framework treats that conversion as part of the schema rather than as boilerplate in every handler.

The same thinking extends to where data comes from. There is a section on customizing input sources, one on headers as an input source, and one on cookies, which means an endpoint can pull validated values out of the body, the query string, the headers or a cookie rather than reading raw request objects by hand. The consequence for review and maintenance is that the shape of an endpoint's data contract is declared once, in one place, and the handlers stay small.

## OpenAPI 3.2 documentation and a typed client come from the same source

Documentation is generated, not written. The framework can export the expected endpoint input and response types to the frontend, which the README frames as end to end type safety so you do not get confused about field names when implementing the client. The example directory contains the machinery for this: a generate-client.ts script, a generate-documentation.ts script, an example.client.ts file and an example.documentation.yaml.

That last file is the useful detail. A checked in documentation snapshot means the generated OpenAPI output is treated as an artifact that can be diffed, which is how you notice that a schema change altered the public contract before it ships. The README also documents tagging endpoints, marking schemas and routes as deprecated, and customizable brands handling for changing the branding of the generated docs.

The framework leans on Zod for this rather than reimplementing a schema language. There is a Zod Plugin section in the feature list, and the repository has a zod-plugin/ directory, so the integration is a maintained part of the project rather than a workaround. The output format is OpenAPI 3.2 and JSON Schema compatible, which matters when your documentation needs to feed other tools.

## Running the example workspace and the tools around it

The repository is a pnpm workspace with the package itself in express-zod-api/ and a runnable example in example/. The root package.json marks the project private and type module, and pins the package manager to pnpm. The scripts show how the pieces relate, with the example being runnable on its own and the workspace able to build and test every package:

```bash
pnpm -F example start
```

That starts the example service. The root scripts also include a build across packages, a test run across packages, a lint step using oxlint with oxfmt as the formatter, and a benchmark target scoped to the framework package:

```bash
pnpm -F express-zod-api bench
```

Two extra scripts run before others. prebuild generates the contributors list and the license file with tools/contributors.ts and tools/license.ts, and pretest runs tools/make-tests.ts, which generates test files. A precommit script chains lint, build and test and then stages generated markdown, example files and the LICENSE, which tells you the generated artifacts are meant to be committed and reviewed. Husky manages the git hooks and renovate.json keeps dependencies moving.

## Endpoints and middlewares are tested as exported units

There are separate README sections for testing endpoints and testing middlewares, and the repository contains several test directories: cjs-test/, esm-test/, compat-test/ and issue952-test/. The names describe what is being covered rather than what is being asserted, and they say something about the framework's priorities. CommonJS and ES module consumption are checked separately, and a directory named after an issue number suggests a regression is pinned in place.

The example folder carries its own vitest.config.ts and a __snapshots__/ directory, which fits a framework that generates OpenAPI output. Snapshotting the generated documentation turns a schema edit into a visible diff in the test run rather than a silent change in the published contract.

For an adopter this is the part to look at first. A framework that produces typed contracts can only deliver on that promise if it has a way to assert what the contracts look like, and this repository does, through unit tests for handlers, snapshot tests for generated output and dedicated compatibility suites for the different module systems.

## The caveat about excessive properties shows the cost of strict output typing

The README carries a caveats section with one entry: excessive properties in endpoint output. That single entry is worth more attention than it first appears, because it points at a genuine design tension in strict response typing.

When the framework types and validates the response of an endpoint, an object with more fields than the schema allows is not automatically wrong. If the schema declares a subset of fields and the handler returns the full record, a strict checker may complain about the extra properties even though the client is meant to ignore them. The alternative, leaving response validation looser, would give up the guarantee that the documented output matches what is sent.

There is a related item under special needs: different responses for different status codes, plus an array response mode for migrating legacy APIs. Those exist for the same reason. A framework that validates output has to decide how strict to be, and it cannot be strict about everything at once without blocking the legitimate cases that every real service accumulates.

## A v29 line shipping several patch releases a month

The project is under active development. The last push was on 2026-10-19, the repository is not archived, and the default branch is master rather than main. Recent releases include v29.8.2 on 2026-09-30, v29.8.1 on 2026-09-22 and v29.8.0 on 2026-09-13, so three releases landed in a single month during September 2026.

That cadence is good news for bug fixes and a warning about upgrade policy. A framework where patch releases arrive weekly will produce changelog entries that need reading before you bump a pinned version, and it is one where a careful pin plus a deliberate update schedule beats blind semver ranges. The README itself points to CHANGELOG.md and to an automated migration tool published on npm, which suggests that breaking changes in this project arrive with an assisted path rather than as manual edits.

Licensing is MIT, with the copy in the LICENSE file, and the repository keeps a separate LICENSE-3RD-PARTY.md for the licenses of bundled third party code. The documentation site is at ez.robintail.cz, and the project's topic list covers express, zod, openapi, swagger, schema validation, middleware and nodejs, which is a fair description of what it does.

## Conclusion

Adopt express-zod-api when your Express service has grown past a handful of routes and you want one schema to drive validation, documentation and the client type, and stay away when you need a small service with predictable hand written behavior or a non Zod validator; before starting, check the caveat on excessive properties in endpoint output and read the CHANGELOG.md entries for the v29 series, since three patch level releases shipped in September 2026.

## FAQ

### What does express-zod-api do for me in an Express project?

It lets you describe routes as a hierarchical object, keeps each endpoint's input and output type declarations next to its handler, validates both sides with Zod schemas and generates OpenAPI 3.2 documentation. It also offers a middleware system and an endpoints factory.

### How do I run the express-zod-api example project?

The repository is a pnpm workspace with the runnable service in example/, and the root package.json defines the start script as pnpm -F example start. The workspace also defines build, test and lint scripts that run across all packages.

### Can express-zod-api generate a typed client for the frontend?

Yes. The README states that expected endpoint input and response types can be exported to the frontend for end to end type safety, and the example folder contains a generate-client.ts script plus a generate-documentation.ts script and an example.documentation.yaml snapshot.

### What limitation should I know about before using express-zod-api?

The README lists a caveat about excessive properties in endpoint output, which matters when a handler returns more fields than the response schema declares. There is also an array response mode for migrating legacy APIs and separate response schemas per status code for the cases strict output typing cannot cover.

## Sources

- [License: MIT](https://github.com/RobinTail/express-zod-api/blob/master/LICENSE)
- [Project website](https://ez.robintail.cz)
- [README](https://github.com/RobinTail/express-zod-api/blob/master/README.md)
- [Releases](https://github.com/RobinTail/express-zod-api/releases)
- [RobinTail/express-zod-api on GitHub](https://github.com/RobinTail/express-zod-api)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/robintail-express-zod-api
