express-zod-api
Version:
A Typescript framework to help you get an API server up and running with I/O schema validation and custom middlewares in minutes.
1,230 lines (973 loc) • 206 kB
Markdown
# Changelog
## Version 25
### v25.5.3
- Updated environment requirements in the Readme:
- Version 25 should be run in an ESM environment because it installs the Zod plugin using the `import` statement;
- To ensure this, either set `type: module` in `package.json`, use `.mts` extension, or run using `tsx`/`vite-node`;
- Although all Node.js versions support `require(ESM)`, `zod` remains a dual package with both CJS and ESM copies;
- If your code uses `require("zod")` (CJS), it is not the same instance that the framework operates;
- This can lead to `TypeError: example is not a function` and loss of metadata set by the `.meta()` method;
- This issue was reported by [@squishykid](https://github.com/squishykid).
### v25.5.2
- Added `z.function()` to the list of JSON-incompatible schemas:
- It will warn when using such schema in a JSON operating Endpoint;
- `z.function()` [became a schema again](https://github.com/colinhacks/zod/issues/4143) in Zod 4.1.0.
### v25.5.1
- Aligned the requirements on Node.js versions between for `@express-zod-api/zod-plugin`;
- Added the `signal` property, introduced in [v25.5.0](#v2550), to example in Readme.
### v25.5.0
- Feature: `AbortSignal` for subscription handlers:
- The `handler` of `EventStreamFactory::build()` is now equipped with `signal` option;
- That signal is aborted when the client disconnects;
- The feature was suggested and implemented by [@jakub-msqt](https://github.com/jakub-msqt).
### v25.4.1
- This patch fixes the issue for users facing `TypeError: .example is not a function`, but not using that method:
- Some environments fail to load Zod plugin properly so that the usage of `ZodType::example()` (plugin method)
internally by the framework itself could cause that error;
- This version fixes the problem by replacing the usage of `.example()` with `globalRegistry.add()`;
- The issue was found and reported by [@misha-z1nchuk](https://github.com/misha-z1nchuk).
### v25.4.0
- Feat: configurable query parser:
- In Express 5 the default query parser was changed from "extended" to "simple";
- The "extended" parser is the `qs` module, while the "simple" parser is the `node:querystring` module;
- This version introduces the new config option `queryParser` having the default value "simple" for compatibility;
- The "extended" parser supports nested objects and arrays with optional indexes in square brackets;
- You can now choose between "simple" and "extended" parsers as well as configure a custom implementation.
```ts
import { createConfig } from "express-zod-api";
import qs from "qs";
const config = createConfig({
// for comma-separated arrays: ?values=1,2,3
queryParser: (query) => qs.parse(query, { comma: true }),
});
```
### v25.3.1
- Small optimization for running diagnostics (non-production mode);
- Fixed the type of the `examples` property for `ez.dateIn()` and `ez.dateOut()` arguments.
### v25.3.0
- Changed bundler from `tsup` to `tsdown`.
### v25.2.0
- Zod Plugin extracted into a standalone package — `@express-zod-api/zod-plugin`:
- The framework declares the plugin as a runtime dependency, you don't need to install it;
- The plugin will continue to evolve independently and could now be used for developing other zod-based software.
### v25.1.0
- Ability to disable the depiction of the `HEAD` method:
- New option `hasHeadMethod` on the argument of `Documentation` and `Integration` constructors;
- Depicts the HEAD method for each Endpoint supporting the GET method;
- The option is enabled by default (the behaviour introduced in [v24.7.0](#v2470));
- The feature suggested by [@GreaterTamarack](https://github.com/GreaterTamarack).
### v25.0.0
- Supported Node.js versions: `^20.19.0 || ^22.12.0 || ^24.0.0`;
- The framework distribution is now ESM-only (finally);
- All the Node.js versions listed above support `require(ESM)` syntax;
- If facing TypeScript error `TS1479`, ensure either:
- using the [recommended tsconfig base for Node 20+](https://github.com/tsconfig/bases/blob/main/bases/node20.json);
- or switching your project to ESM by setting `"type": "module"` in `package.json`;
- Supported `zod` version: `^4.0.0`;
- Compatibility with `zod@^3` is dropped;
- You SHOULD now `import { z } from "zod"` without the `/v4` suffix;
- Changes to the Zod plugin and metadata processing:
- Dropped support of examples that are given as `example` property of `.meta()` argument;
- Dropped support of examples given within an object-based value of `examples` property of `.meta()` argument;
- Use either `.example()` method or `.meta()` method with `examples` property being an array;
- Changes to the `Middleware` class:
- When the `input` schema is not defined, the `input` argument of the `handler` method is now `unknown`;
- Changes to publicly exposed method:
- The `getExamples()` helper is removed, use `.meta().examples` or `globalRegistry.get().examples` instead.
- Consider [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration).
```diff
- z.string().meta({ example: "test" });
- z.string().meta({ examples: { one: { value: "test" } } });
+ z.string().meta({ examples: ["test"] });
+ z.string().example("test").example("another"); // plugin method
```
```diff
- getExamples(schema);
+ schema.meta()?.examples || [];
+ globalRegistry.get(schema)?.examples || [];
```
## Version 24
### v24.7.3
- Fixed the depiction of the negative response to `HEAD` requests:
- Should have no response body, exactly as the positive one;
- This version corrects the implementation introduced in [v24.7.0](#v2470).
### v24.7.2
- Fixed the negative response MIME type for ~~`arrayResultHandler`~~ (deprecated entity):
- Should have been `text/plain`.
### v24.7.1
- Compatibility fix for `zod@^3.25.68` and `^4.0.0`:
- Previously typed metadata properties `example` and `examples` were removed from `zod` core:
See [Commit ee5615d](https://github.com/colinhacks/zod/commit/ee5615d76b93aac15d7428a17b834a062235f6a1);
- This version restores those properties as a part of Zod plugin in order to comply to the existing implementation;
- The `example` property is marked as deprecated and will be removed in v25 — please refrain from using it;
- The `examples` property still supports an object for backward compatibility, but it will only support array in v25;
- To avoid confusion, consider using the Zod plugin's method `.example()` where possible (except `ez.dateIn()`).
### v24.7.0
- Supporting `HEAD` method:
- The purpose of the `HEAD` method is to retrieve the headers without performing `GET` request;
- It is the built-in feature of Express to handle `HEAD` requests by the handlers for `GET` requests;
- Therefore, each `Endpoint` supporting `get` method also handles `head` requests (no work needed);
- Added `HEAD` method to CORS response headers, along with `OPTIONS`, for `GET` method supporting endpoints;
- ~~Positive~~ Response to `HEAD` request should contain same headers as `GET` would, but without the body:
- Added `head` request depiction to the generated `Documentation`;
- Added `head` request types to the generated `Integration` client;
- ~~Positive~~ Response to `HEAD` request should contain the `Content-Length` header:
- `ResultHandler`s using `response.send()` (as well as its shorthands such as `.json()`) automatically do that
instead of sending the response body (no work needed);
- Other approaches, such as stream piping, might require to implement `Content-Length` header for `HEAD` requests;
- This feature was suggested by [@pepegc](https://github.com/pepegc);
- Caveats:
- The following properties, when assigned with functions, can now receive `head` as an argument:
- `operationId` supplied to `EndpointsFactory::build()`;
- `isHeader` supplied to `Documentation::constructor()`;
- If the `operationId` is assigned with a `string` then it may be appended with `__HEAD` for `head` method;
### v24.6.2
- Correcting recommendations given in [v24.6.0](#v2460) regarding using with `zod@^4.0.0`:
- Make sure the `moduleResolution` in your `tsconfig.json` is either `node16`, `nodenext` or `bundler`;
- Consider the [recommended tsconfig base, Node 20+](https://github.com/tsconfig/bases/blob/main/bases/node20.json);
- Then you MAY `import { z } from "zod"`;
- Otherwise, you MUST keep `import { z } from "zod/v4"`;
- In some cases module augmentation (Zod plugin) did not work and caused schema assignment errors for some users;
- The issue was reported by [@MichaelHindley](https://github.com/MichaelHindley);
- This potential inconvenience will be resolved by dropping `zod@^3` in the next version of Express Zod API:
- If you're having troubles using the framework with `zod@^4.0.0`, consider upgrading to v25 (currently beta).
### v24.6.1
- Compatibility fix for recently changed type of Express native middleware:
- Fixes error `TS2345` when passing over an Express middleware to:
- `EndpointsFactory::use()`;
- `EndpointsFactory::addExpressMiddleware()`;
- The issue caused by `@types/express-serve-static-core` v5.0.7;
- The issue reported by [@zoton2](https://github.com/zoton2).
### v24.6.0
- Supporting `zod` versions `^3.25.35 || ^4.0.0`:
- If you use `zod@^4.0.0` then you MAY `import { z } from "zod"`:
- If facing error, ensure `moduleResolution` in your `tsconfig.json` is either `node16`, `nodenext` or `bundler`;
- If you use `zod@^3.25.35` then keep `import { z } from "zod/v4"`;
- For more details, see the [Explanation of the versioning strategy](https://github.com/colinhacks/zod/issues/4371).
### v24.5.0
- `openapi3-ts` version is `^4.5.0`;
- Compatibility adjustment for Zod 3.25.72.
### v24.4.3
- Externalized the code responsible for the Zod plugin's method `z.object().remap()`:
- Using the new method `R.renameKeys()` of `ramda@0.31`.
### v24.4.2
- Improved the type of the `input` argument for `Endpoint::handler()`:
- For `z.object()`-based schema removed `never` for unknown properties;
- Fixed incorrect typing of excessive properties when using a `z.looseObject()`-based schema;
- The issue reported by [@ThomasKientz](https://github.com/ThomasKientz).
```ts
import { defaultEndpointsFactory } from "express-zod-api";
import { z } from "zod/v4";
const endpoint1 = defaultEndpointsFactory.buildVoid({
input: z.object({
foo: z.string(),
}),
handler: async ({ input: { bar } }) => {
console.log(bar); // before: never, after: TypeScript Error
},
});
const endpoint2 = defaultEndpointsFactory.buildVoid({
input: z.looseObject({
foo: z.string(),
}),
handler: async ({ input: { bar } }) => {
console.log(bar); // before: never, after: unknown
},
});
```
### v24.4.1
- Compatibility fix for Zod 3.25.67.
### v24.4.0
- Automated migration moved to the [dedicated package](https://www.npmjs.com/package/@express-zod-api/migration).
### v24.3.2
- Removed previously deprecated plural properties from `ApiResponse` interface: `statusCodes`, `mimeTypes`.
### v24.3.1
- Compatibility fix for Zod 3.25.60.
### v24.3.0
- Technical update: switched to `pnpm`, no changes to the code.
### v24.2.3
- Fixed a bug about missing CORS headers in case of request parser errors:
- This includes the case where exceeding the configured `upload.limits` cause the configured `upload.limitError`;
- This bug was found and reported by [@james10424](https://github.com/james10424);
- The reproduction config is explained in [issue 2706](https://github.com/RobinTail/express-zod-api/issues/2706).
### v24.2.2
- Zod plugin compatibility fix for [Zod v3.25.50](https://github.com/colinhacks/zod/releases/tag/v3.25.50).
### v24.2.1
- Prioritizing Zod native depiction for `z.enum()` and `z.literal()` by the `Documentation` generator:
- Enum `type` implemented in [Zod v3.25.45](https://github.com/colinhacks/zod/releases/tag/v3.25.45);
- Literal `type` implemented in [Zod v3.25.49](https://github.com/colinhacks/zod/releases/tag/v3.25.49).
### v24.2.0
- Supporting `z.nonoptional()` schema by `Integration` generator.
### v24.1.0
- Supporting the new `z.templateLiteral()` schema by the `Integration` (client side types generator);
- Compatibility improvements due to the recent changes in Zod 4:
- Restoring publicly exposed `getExamples()` helper (with a new signature);
```ts
// z.templateLiteral(["start", z.number(), "mid", z.boolean(), "end"])
type Type1 = `start${number}mid${boolean}end`;
```
### v24.0.0
- Switched to Zod 4:
- Minimum supported version of `zod` is 3.25.35, BUT imports MUST be from `zod/v4`;
- Read the [Explanation of the versioning strategy](https://github.com/colinhacks/zod/issues/4371);
- Express Zod API, however, is not aiming to support both Zod 3 and Zod 4 simultaneously due to:
- incompatibility of data structures;
- operating composite schemas (need to avoid mixing schemas of different versions);
- the temporary nature of this transition;
- the advantages of Zod 4 that provide opportunities to simplifications and corrections of known issues.
- `IOSchema` type had to be simplified down to a schema resulting to an `object`, but not an `array`;
- Refer to [Migration guide on Zod 4](https://v4.zod.dev/v4/changelog) for adjusting your schemas;
- Changes to `ZodType::example()` (Zod plugin method):
- Now acts as an alias for `ZodType::meta({ examples })`;
- The argument has to be the output type of the schema (used to be the opposite):
- This change is only breaking for transforming schemas;
- In order to specify an example for an input schema the `.example()` method must be called before `.transform()`;
- The transforming proprietary schemas `ez.dateIn()` and `ez.dateOut()` now accept metadata as its argument:
- This allows to set examples before transformation (`ez.dateIn()`) and to avoid the examples "branding";
- Changes to `Documentation`:
- Generating Documentation is mostly delegated to Zod 4 `z.toJSONSchema()`;
- Express Zod API implements some overrides and improvements to fit it into OpenAPI 3.1 that extends JSON Schema;
- The `numericRange` option removed from `Documentation` class constructor argument;
- The `Depicter` type signature changed: became a postprocessing function returning an overridden JSON Schema;
- Changes to `Integration`:
- The `optionalPropStyle` option removed from `Integration` class constructor:
- Use `.optional()` to add question mark to the object property as well as `undefined` to its type;
- Use `.or(z.undefined())` to add `undefined` to the type of the object property;
- See the [reasoning](https://x.com/colinhacks/status/1919292504861491252);
- Properties assigned with `z.any()` or `z.unknown()` schema are now typed as required:
- Read the [details here](https://v4.zod.dev/v4/changelog#changes-zunknown-optionality);
- Added types generation for `z.never()`, `z.void()` and `z.unknown()` schemas;
- The fallback type for unsupported schemas and unclear transformations in response changed from `any` to `unknown`;
- The argument of `ResultHandler::handler` is now discriminated: either `output` or `error` is `null`, not both;
- The `getExamples()` public helper removed — ~~use `.meta()?.examples` instead~~, restored in v24.1.0;
- Added the new proprietary schema `ez.buffer()`;
- The `ez.file()` schema removed: use `z.string()`, `z.base64()`, `ez.buffer()` or their union;
- Consider [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration).
```diff
- import { z } from "zod";
+ import { z } from "zod/v4";
```
```diff
input: z.string()
+ .example("123")
.transform(Number)
- .example("123")
```
```diff
- ez.dateIn().example("2021-12-31");
+ ez.dateIn({ examples: ["2021-12-31"] });
- ez.file("base64");
+ z.base64();
- ez.file("buffer");
+ ez.buffer();
```
## Version 23
### v23.6.1
- `createServer()` displays a warning when no server is configured.
### v23.6.0
- Featuring `gracefulShutdown.beforeExit()` hook:
- The function to execute after the server was closed, but before terminating the process (can be asynchronous);
- The feature suggested by [@HeikoOsigus](https://github.com/HeikoOsigus).
### v23.5.0
- Integer number `format` in generated Documentation now also depends on the `numericRange` option:
- `int64` is the default format for the range of JavaScript safe integers;
- `int32` is used when the specified range fits 32 bits (`4294967295`);
- omitted when `numericRange` is set to `null` (opt-out);
- The feature suggested by [@crgeary](https://github.com/crgeary).
### v23.4.1
- Fixed headers for an edge case of flat routing with explicit methods:
- Routes resolved into same path but using different methods had incomplete CORS headers (when enabled);
- Similarly, could affect `Allow` header when `wrongMethodBehavior` set to `405` (default).
```ts
// reproduction sample
import { Routing } from "express-zod-api";
const routing: Routing = {
v1: {
"get /user/retrieve": endpointA,
user: {
// same /v1/user/retrieve, but another method:
"post retrieve": endpointB, // POST was missing in response headers to OPTIONS request
},
},
};
```
### v23.4.0
- Feature: flat routing syntax with explicit method support:
- `Routing` now supports slashes within keys, so that nested segments could be flattened;
- `Routing` also supports explicitly specified `Method` for the keys assigned with `Endpoint`;
- Leading slash is optional;
- The feature suggested by [@williamgcampbell](https://github.com/williamgcampbell).
```ts
import { Routing } from "express-zod-api";
const routing: Routing = {
// flat syntax:
"v1/books/:bookId": getBookEndpoint,
// with method:
"post /v1/books": addBookEndpoint,
// nested:
v1: {
"delete /books/:bookId": deleteBookEndpoint,
"patch /books/:bookId": changeBookEndpoint,
},
};
```
### v23.3.0
- Upgraded `ansis` (direct dependency) to `^4.0.0`.
### v23.2.0
- Supporting Node 24.
### v23.1.2
- Simplified implementation for generating Documentation of `z.enum()` and `z.literal()`;
- Fixed duplication in the Documentation generator code determining the requirement for a request body.
### v23.1.1
- Fixed response depiction in the generated Documentation:
- coerced types were marked as nullable;
- coerced, preprocessed and `z.any()` schemas were marked as optional.
### v23.1.0
- Improved generated Documentation:
- Arrays having fixed length: `z.boolean().array().length(2)`;
- Records with non-literal keys (added `propertyNames`): `z.record(z.string().regex(/x-\w+/), z.boolean())`.
### v23.0.0
- Minimum version of `express` (required peer dependency) is `5.1.0` (first release of v5 marked as `latest`);
- Minimum version of `compression` (optional peer dependency) is `1.8.0` (it supports Brotli);
- The default value for `wrongMethodBehavior` config option is changed to `405`;
- Publicly exposed interfaces: `CustomHeaderSecurity` renamed to `HeaderSecurity`, `NormalizedResponse` removed.
- The `errorHandler` property removed from `testMiddleware()` argument in favor of config option having same name;
- Only the following methods remained public, while other methods and properties were marked internal or removed:
- `Endpoint`: `.execute()` and `.deprecated()`;
- `Middleware`: `.execute()`;
- `ResultHandler`: `.execute()`;
- `DependsOnMethod`: `.deprecated()`;
- `Documentation`: constructor only;
- `Integration`: `.print()` and `.printFormatted()`;
- `ServeStatic`: constructor only;
- Consider the automated migration using the built-in ESLint rule.
```js
// eslint.config.mjs — minimal ESLint 9 config to apply migrations automatically using "eslint --fix"
import parser from "@typescript-eslint/parser";
import migration from "express-zod-api/migration";
export default [
{ languageOptions: { parser }, plugins: { migration } },
{ files: ["**/*.ts"], rules: { "migration/v23": "error" } },
];
```
## Version 22
### v22.13.2
- Fixed inconsistency between the actual catcher behavior and the error handling documentation:
- Removed conversion of non-`HttpError`s to `BadRequest` before passing them to `errorHandler`;
- A `ResultHandler` configured as `errorHandler` is responsible to handling all errors and responding accordingly.
- The default `errorHandler` is `defaultResultHandler`:
- Using `ensureHttpError()` it coverts non-`HttpError`s to `InternalServerError` and responds with status code `500`;
- The issue has occurred since [v19.0.0](#v1900).
### v22.13.1
- Fixed: the output type of the `ez.raw()` schema (without an argument) was missing the `raw` property (since v19.0.0).
### v22.13.0
- Ability to configure and disable access logging:
- New config option: `accessLogger` — the function for producing access logs;
- The default value is the function writing messages similar to `GET: /v1/path` having `debug` severity;
- The option can be assigned with `null` to disable writing of access logs;
- Thanks to the contributions of [@gmorgen1](https://github.com/gmorgen1) and [@crgeary](https://github.com/crgeary);
- [@danmichaelo](https://github.com/danmichaelo) fixed a broken link in the Security policy;
- Added JSDoc for several types involved into creating Middlewares and producing Endpoints.
```ts
import { createConfig } from "express-zod-api";
const config = createConfig({
accessLogger: (request, logger) => logger.info(request.path), // or null to disable
});
```
### v22.12.0
- Featuring HTML forms support (URL Encoded request body):
- Introducing the new proprietary schema `ez.form()` accepting an object shape or a custom `z.object()` schema;
- Introducing the new config option `formParser` having `express.urlencoded()` as the default value;
- Requests to Endpoints having `input` schema assigned with `ez.form()` are parsed using `formParser`;
- Exception: requests to Endpoints having `ez.upload()` within `ez.form()` are still parsed by `express-fileupload`;
- The lack of this feature was reported by [@james10424](https://github.com/james10424).
```ts
import { defaultEndpointsFactory, ez } from "express-zod-api";
import { z } from "zod";
// The request content type should be "application/x-www-form-urlencoded"
export const submitFeedbackEndpoint = defaultEndpointsFactory.build({
method: "post",
input: ez.form({
name: z.string().min(1),
email: z.string().email(),
message: z.string().min(1),
}),
});
```
### v22.11.2
- Fixed: allow future versions of Express 5:
- Incorrect condition for the peer dependency was introduced in v21.0.0.
```diff
- "express": "^4.21.1 || 5.0.1",
+ "express": "^4.21.1 || ^5.0.1",
```
### v22.11.1
- Simplified the type of `requestMock` returned from `testEndpoint` and `testMiddleware`.
### v22.11.0
- Featuring an ability to configure the numeric range of the generated Documentation:
- The new property `numericRange` on the `Documentation::constructor()` argument provides the way to specify
acceptable limits of `z.number()` and `z.number().int()` that your API can handle;
- Possible values: `{ integer: [number, number], float: [number, number] } | null`;
- Those numbers are used to depict min/max values for `z.number()` schema without limiting refinements;
- The default value is the limits of the JavaScript engine:
- for integers: `[ Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER ]`;
- for floats: `[ -Number.MAX_VALUE, Number.MAX_VALUE ]`;
- The `null` value disables the feature (no min/max values printed unless defined explicitly by the schema);
- This can be useful when a third party tool is having issues to process the generated Documentation;
- Such an issue was reported by [@APTy](https://github.com/APTy) for the Java-based tool:
[openapi-generator](https://github.com/OpenAPITools/openapi-generator);
- Examples of representation of numerical schemes depending on this setting:
```yaml
- schema: z.number()
numericRange: undefined
depicted:
type: number
format: double
minimum: -1.7976931348623157e+308 # -Number.MAX_VALUE
maximum: 1.7976931348623157e+308 # Number.MAX_VALUE
- schema: z.number()
numericRange: null
depicted:
type: number
format: double
- schema: z.number().int()
numericRange: undefined
depicted:
type: integer
format: int64
minimum: -9007199254740991 # Number.MIN_SAFE_INTEGER
maximum: 9007199254740991 # Number.MAX_SAFE_INTEGER
- schema: z.number().int()
numericRange: null
depicted:
type: integer
format: int64
- schema: z.number().int().nonnegative().max(100)
numericRange: undefined
depicted:
type: integer
format: int64
exclusiveMinimum: 0 # explicitly defined by .nonnegative()
maximum: 100 # explicitly defined by .max()
```
### v22.10.1
- Fixed catching errors in a custom `ResultHandler` used as `errorHandler` for Not Found routes having async `handler`.
```ts
// reproduction
import { createConfig, ResultHandler } from "express-zod-api";
createConfig({
errorHandler: new ResultHandler({
// rejected promise was not awaited:
handler: async () => {
throw new Error(
"You should not do it. But if you do, we've got LastResortHandler to catch it.",
);
},
}),
});
```
### v22.10.0
- Featuring required request bodies in the generated Documentation:
- This version sets the `required` property to `requestBody` when:
- It contains the required properties on it;
- Or it's based on `ez.raw()` (proprietary schema);
- The presence of `requestBody` depends on the Endpoint method(s) and the configuration of `inputSources`;
- The lack of the property was reported by [@LufyCZ](https://github.com/LufyCZ).
### v22.9.1
- Minor refactoring and optimizations.
### v22.9.0
- Featuring Deprecations:
- You can deprecate all usage of an `Endpoint` using `EndpointsFactory::build({ deprecated: true })`;
- You can deprecate a route using the assigned `Endpoint::deprecated()` or `DependsOnMethod::deprecated()`;
- You can deprecate a schema using `ZodType::deprecated()`;
- All `.deprecated()` methods are immutable — they create a new copy of the subject;
- Deprecated schemas and endpoints are reflected in the generated `Documentation` and `Integration`;
- The feature suggested by [@mlms13](https://github.com/mlms13).
```ts
import { Routing, DependsOnMethod } from "express-zod-api";
import { z } from "zod";
const someEndpoint = factory.build({
deprecated: true, // deprecates all routes the endpoint assigned to
input: z.object({
prop: z.string().deprecated(), // deprecates the property or a path parameter
}),
});
const routing: Routing = {
v1: oldEndpoint.deprecated(), // deprecates the /v1 path
v2: new DependsOnMethod({ get: oldEndpoint }).deprecated(), // deprecates the /v2 path
v3: someEndpoint, // the path is assigned with initially deprecated endpoint (also deprecated)
};
```
### v22.8.0
- Feature: warning about the endpoint input scheme ignoring the parameters of the route to which it is assigned:
- There is a technological gap between routing and endpoints, which at the same time allows an endpoint to be reused
across multiple routes. Therefore, there are no constraints between the route parameters and the `input` schema;
- This version introduces checking for such discrepancies:
- non-use of the path parameter or,
- a mistake in manually entering its name;
- The warning is displayed when the application is launched and NOT in production mode.
```ts
const updateUserEndpoint = factory.build({
method: "patch",
input: z.object({
id: z.string(), // implies path parameter "id"
}),
});
const routing: Routing = {
v1: {
user: {
":username": updateUserEndpoint, // path parameter is "username" instead of "id"
},
},
};
```
```shell
warn: The input schema of the endpoint is most likely missing the parameter of the path it is assigned to.
{ method: 'patch', path: '/v1/user/:username', param: 'username' }
```
### v22.7.0
- Technical release in connection with the implementation of workspaces into the project architecture.
### v22.6.0
- Feature: pulling examples up from the object schema properties:
- When describing I/O schemas for generating `Documentation` the examples used to work properly only when assigned to
the top level (`z.object().example()`), especially complex scenarios involving path parameters and middlewares;
- This version supports examples assigned to the individual properties on the I/O object schemas;
- It makes the syntax more readable and fixes the issue when example is only set for a path parameter.
```ts
const before = factory.build({
input: z
.object({
key: z.string(),
})
.example({
key: "1234-5678-90",
}),
});
const after = factory.build({
input: z.object({
key: z.string().example("1234-5678-90"),
}),
});
```
### v22.5.0
- Feature: `defaultResultHandler` sets headers from `HttpError`:
- If you `throw createHttpError(400, "message", { headers })` those `headers` go to the negative response.
- Feature: Ability to respond with status code `405` (Method not allowed) to requests having wrong method:
- Previously, in all cases where the method and route combination was not defined, the response had status code `404`;
- For situations where a known route does not support the method being used, there is a more appropriate code `405`:
- See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405 for details;
- You can activate this feature by setting the new `wrongMethodBehavior` config option `405` (default: `404`).
```ts
import { createConfig } from "express-zod-api";
createConfig({ wrongMethodBehavior: 405 });
```
### v22.4.2
- Excluded 41 response-only headers from the list of well-known ones used to depict request params in Documentation.
### v22.4.1
- Fixed a bug that could lead to duplicate properties in generated client types:
- If the middleware and/or endpoint schemas had the same property, it was duplicated by Integration.
- The issue was introduced in [v20.15.3](#v20153) and reported by [@bobgubko](https://github.com/bobgubko).
```ts
// reproduction
factory
.addMiddleware({
input: z.object({ query: z.string() }), // ...
})
.build({
input: z.object({ query: z.string() }), // ...
});
```
```ts
type Before = {
query: string;
query: string; // <— bug #2352
};
type After = {
query: string;
};
```
### v22.4.0
- Feat: ability to supply extra data to a custom implementation of the generated client:
- You can instantiate the client class with an implementation accepting an optional context of your choice;
- The public `.provide()` method can now accept an additional argument having the type of that context;
- The problem on missing such ability was reported by [@LucWag](https://github.com/LucWag).
```ts
import { Client, Implementation } from "./generated-client.ts";
interface MyCtx {
extraKey: string;
}
const implementation: Implementation<MyCtx> = async (
method,
path,
params,
ctx, // ctx is optional MyCtx
) => {};
const client = new Client(implementation);
client.provide("get /v1/user/retrieve", { id: "10" }, { extraKey: "123456" });
```
### v22.3.1
- Fixed issue on emitting server-sent events (SSE), introduced in v21.5.0:
- Emitting SSE failed due to internal error `flush is not a function` having `compression` disabled in config;
- The `.flush()` method of `response` is a feature of `compression` (optional peer dependency);
- It is required to call the method when `compression` is enabled;
- This version fixes the issue by calling the method conditionally;
- This bug was reported by [@bobgubko](https://github.com/bobgubko).
### v22.3.0
- Feat: `Subscription` class for consuming Server-sent events:
- The `Integration` can now also generate a frontend helper class `Subscription` to ease SSE support;
- The new class establishes an `EventSource` instance and exposes it as the public `source` property;
- The class also provides the public `on` method for your typed listeners;
- You can configure the generated class name using `subscriptionClassName` option (default: `Subscription`);
- The feature is only applicable to the `variant` option set to `client` (default).
```ts
import { Subscription } from "./client.ts"; // the generated file
new Subscription("get /v1/events/stream", {}).on("time", (time) => {});
```
### v22.2.0
- Feat: detecting headers from `Middleware::security` declarations:
- When `headers` are enabled within `inputSources` of config, the `Documentation` generator can now identify them
among other inputs additionally by using the security declarations of middlewares attached to an `Endpoint`;
- This approach enables handling of custom headers without `x-` prefix.
```ts
const authMiddleware = new Middleware({
security: { type: "header", name: "token" },
});
```
### v22.1.1
- This version contains an important technical simplification of routines related to processing of `security`
declarations of the used `Middleware` when generating API `Documentation`.
- No changes to the operation are expected. This refactoring is required for a feature that will be released later.
### v22.1.0
- Feat: ability to configure the generated client class name:
- New option `clientClassName` for `Integration::constructor()` argument, default: `Client`.
- Feat: default implementation for the generated client:
- The argument of the generated client class constructor became optional;
- The `Implementation` previously suggested as an example (using `fetch`) became the one used by default;
- You may no longer need to write the implementation if the default one suits your needs.
```ts
import { Integration } from "express-zod-api";
new Integration({ clientClassName: "FancyClient" });
```
```ts
import { FancyClient } from "./generated-client.ts";
const client = new FancyClient(/* optional implementation */);
```
### v22.0.0
- Minimum supported Node versions: 20.9.0 and 22.0.0:
- Node 18 is no longer supported; its end of life is April 30, 2025.
- `BuiltinLogger::profile()` behavior changed for picoseconds: expressing them through nanoseconds;
- Feature: handling all (not just `x-` prefixed) headers as an input source (when enabled):
- Behavior changed for `headers` inside `inputSources` config option: all headers are addressed to the `input` object;
- This change is motivated by the deprecation of `x-` prefixed headers;
- Since the order inside `inputSources` matters, consider moving `headers` to the first place to avoid overwrites;
- The generated `Documentation` recognizes both `x-` prefixed inputs and well-known headers listed on IANA.ORG;
- You can customize that behavior by using the new option `isHeader` of the `Documentation::constructor()`.
- The `splitResponse` property on the `Integration::constructor()` argument is removed;
- Changes to the client code generated by `Integration`:
- The class name changed from `ExpressZodAPIClient` to just `Client`;
- The overload of the `Client::provide()` having 3 arguments and the `Provider` type are removed;
- The public `jsonEndpoints` const is removed — use the `content-type` header of an actual response instead;
- The public type `MethodPath` is removed — use the `Request` type instead.
- The approach to tagging endpoints changed:
- The `tags` property moved from the argument of `createConfig()` to `Documentation::constructor()`;
- The overload of `EndpointsFactory::constructor()` accepting `config` property is removed;
- The argument of `EventStreamFactory::constructor()` is now the events map (formerly assigned to `events` property);
- Tags should be declared as the keys of the augmented interface `TagOverrides` instead;
- The public method `Endpoint::getSecurity()` now returns an array;
- Consider the automated migration using the built-in ESLint rule.
```js
// eslint.config.mjs — minimal ESLint 9 config to apply migrations automatically using "eslint --fix"
import parser from "@typescript-eslint/parser";
import migration from "express-zod-api/migration";
export default [
{ languageOptions: { parser }, plugins: { migration } },
{ files: ["**/*.ts"], rules: { "migration/v22": "error" } },
];
```
```diff
createConfig({
- tags: {},
inputSources: {
- get: ["query", "headers"] // if you have headers on last place
+ get: ["headers", "query"] // move headers to avoid overwrites
}
});
new Documentation({
+ tags: {},
+ isHeader: (name, method, path) => {} // optional
});
new EndpointsFactory(
- { config, resultHandler: new ResultHandler() }
+ new ResultHandler()
);
new EventStreamFactory(
- { config, events: {} }
+ {} // events map only
);
```
```ts
// new tagging approach
import { Documentation } from "express-zod-api";
// Add similar declaration once, somewhere in your code, preferably near config
declare module "express-zod-api" {
interface TagOverrides {
users: unknown;
files: unknown;
subscriptions: unknown;
}
}
// Add extended description of the tags to Documentation (optional)
new Documentation({
tags: {
users: "All about users",
files: { description: "All about files", url: "https://example.com" },
},
});
```
## Version 21
### v21.11.1
- Common styling methods (coloring) are extracted from the built-in logger instance:
- This measure is to reduce memory consumption when using a child logger.
### v21.11.0
- New public property `ctx` is available on instances of `BuiltinLogger`:
- When using the built-in logger and `childLoggerProvider` config option, the `ctx` property contains the argument
that was used for creating the child logger using its `.child()` method;
- It can be utilized for accessing its `requestId` property for purposes other than logging;
- The default value of `ctx` is an empty object (when the instance is not a child logger).
```ts
import { BuiltinLogger, createConfig, createMiddleware } from "express-zod-api";
// Declaring the logger type in use
declare module "express-zod-api" {
interface LoggerOverrides extends BuiltinLogger {}
}
// Configuring child logger provider
const config = createConfig({
childLoggerProvider: ({ parent }) =>
parent.child({ requestId: randomUUID() }),
});
// Accessing child logger context
createMiddleware({
handler: async ({ logger }) => {
doSomething(logger.ctx.requestId); // <—
},
});
```
### v21.10.0
- New `Integration` option: `serverUrl`, string, optional, the API URL for the generated client:
- Currently used for generating example implementation;
- Default value remains `https://example.com`;
- Using `new URL()` for constructing the final request URL in the example implementation of the generated client:
- That enables handling `serverUrl` both with and without trailing slash;
### v21.9.0
- Deprecating `MethodPath` type in the code generated by `Integration`:
- Introducing the `Request` type to be used instead;
- Added JSDoc having the request in description for every type and interface generated by `Integration`;
- A couple adjustments for consistency and performance.
### v21.8.0
- Deprecating `jsonEndpoints` from the code generated by `Integration`:
- Use the `content-type` header of an actual response instead, [example](#v20212).
### v21.7.0
- Feature: introducing `EncodedResponse` public interface in the code generated by `Integration`:
- The new entity should enable making custom clients having response type awereness based on the status code;
- The difference between `Response` and `EndcodedResponse` is the second hierarchical level.
```ts
import { EncodedResponse } from "./generated.ts";
type UsageExample = EncodedResponse["get /v1/user/retrieve"][200];
```
### v21.6.1
- `node-mocks-http` version is `^1.16.2`;
- Fixed possible duplicates in the `Path` type generated by `Integration`:
- Duplicates used to be possible when using `DependsOnMethod` instance within specified `routing`.
### v21.6.0
- Supporting the following `z.string()` formats by the `Documentation` generator:
- `base64` (as `byte`), `date`, `time`, `duration`, `nanoid`;
- And new formats introduced by Zod 3.24: `jwt`, `base64url`, `cidr`;
- Fixed missing `minLength` and `maxLength` properties when depicting `z.string().length()` (fixed length strings).
### v21.5.0
- Feat: Introducing [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events):
- Basic implementation of the event streams feature is now available using `EventStreamFactory` class;
- The new factory is similar to `EndpointsFactory` including the middlewares support;
- Client application can subscribe to the event stream using `EventSource` class instance;
- `Documentation` and `Integration` do not have yet a special depiction of such endpoints;
- This feature is a lightweight alternative to [Zod Sockets](https://github.com/RobinTail/zod-sockets).
```ts
import { z } from "zod";
import { EventStreamFactory } from "express-zod-api";
import { setTimeout } from "node:timers/promises";
const subscriptionEndpoint = new EventStreamFactory({
events: { time: z.number().int().positive() },
}).buildVoid({
input: z.object({}), // optional input schema
handler: async ({ options: { emit, isClosed } }) => {
while (!isClosed()) {
emit("time", Date.now());
await setTimeout(1000);
}
},
});
```
```js
const source = new EventSource("https://example.com/api/v1/time");
source.addEventListener("time", (event) => {
const data = JSON.parse(event.data); // number
});
```
### v21.4.0
- Return type of public methods `getTags()` and `getScopes()` of `Endpoint` corrected to `ReadyonlyArray<string>`;
- Featuring `EndpointsFactory::buildVoid()` method:
- It's a shorthand for returning `{}` while having `output` schema `z.object({})`;
- When using this method, `handler` may return `void` while retaining the object-based operation internally.
```diff
- factory.build({
+ factory.buildVoid({
- output: z.object({}),
handler: async () => {
- return {};
},
});
```
### v21.3.0
- Fixed `provide()` method usage example in the code of the generated client;
- Always splitting the response in the generated client:
- This will print the positive and negative response types separately;
- The `splitResponse` property on the `Integration` class constructor argument is deprecated.
### v21.2.0
- Minor performance adjustments;
- Introducing stricter overload for the generated `ExpressZodAPIClient::provide()` method:
- The method can now also accept two arguments: space-separated method with path and parameters;
- Using this overload provides strict constraints on the first argument so that undeclared routes can not be used;
- This design is inspired by the OctoKit and aims to prevent the misuse by throwing a Typescript error.
- Using `ExpressZodAPIClient::provide()` with three arguments is deprecated:
- The return type when using undeclared routes corrected to `unknown`.
- The `Provider` type of the generated client is deprecated;
- The type of the following generated client entities is corrected so that it became limited to the listed routes:
- `Input`, `Response`, `PositiveResponse`, `NegativeResponse`, `MethodPath`.
```diff
- client.provide("get", "/v1/user/retrieve", { id: "10" }); // deprecated
+ client.provide("get /v1/user/retrieve", { id: "10" }); // featured
```
### v21.1.0
- Featuring empty response support:
- For some REST APIs, empty responses are typical: with status code `204` (No Content) and redirects (302);
- Previously, the framework did not offer a straightforward way to describe such responses, but now there is one;
- The `mimeType` property can now be assigned with `null` in `ResultHandler` definition;
- Both `Documentation` and `Integration` generators ignore such entries so that the `schema` can be `z.never()`:
- The body of such response will not be depicted by `Documentation`;
- The type of such response will be described as `undefined` (configurable) by `Integration`.
```ts
import { z } from "zod";
import {
ResultHandler,
ensureHttpError,
EndpointsFactory,
Integration,
} from "express-zod-api";
const resultHandler = new ResultHandler({
positive: { statusCode: 204, mimeType: null, schema: z.never() },
negative: { statusCode: 404, mimeType: null, schema: z.never() },
handler: ({ error, response }) => {
response.status(error ? ensureHttpError(error).statusCode : 204).end(); // no content
},
});
new Integration({ noContent: z.undefined() }); // undefined is default
```
### v21.0.0
- Minimum supported versions of `express`: 4.21.1 and 5.0.1 (fixed vulnerabilities);
- Breaking changes to `createConfig()` argument:
- The `server` property renamed to `http` and made optional — (can now configure HTTPS only);
- These properties moved to the top level: `jsonParser`, `upload`, `compression`, `rawParser` and `beforeRouting`;
- Both `logger` and `getChildLogger` arguments of `beforeRouting` function are replaced with all-purpose `getLogger`.
- Breaking changes to `createServer()` resolved return:
- Both `httpServer` and `httpsServer` are combined into single `servers` property (array, same order).
- Breaking changes to `EndpointsFactory::build()` argument:
- Plural `methods`, `tags` and `scopes` properties replaced with singular `method`, `tag`, `scope` accordingly;
- The `method` property also made optional and can now be derived from `DependsOnMethod` or imply `GET` by default;
- When `method` is assigned with an array, it must be non-empty.
- Breaking changes to `positive` and `negative` properties of `ResultHandler` constructor argument:
- Plural `statusCodes` and `mimeTypes` props within the values are replaced with singular `statusCode` and `mimeType`.
- Other breaking changes:
- The `serializer` property of `Documentation` and `Integration` constructor argument removed;
- The `originalError` property of `InputValidationError` and `OutputValidationError` removed (use `cause` instead);
- The `getStatusCodeFromError()` method removed (use the `ensureHttpError().statusCode` instead);
- The `testEndpoint()` method can no longer test CORS headers — that function moved to `Routing` traverse;
- For `Endpoint`: `getMethods()` may return `undefined`, `getMimeTypes()` removed, `getSchema()` variants reduced;
- Public properties `pairs`, `firstEndpoint` and `siblingMethods` of `DependsOnMethod` replaced with `entries`.
- Consider the automated migration using the built-in ESLint rule.
```js
// eslint.config.mjs — minimal ESLint 9 config to apply migrations automatically using "eslint --fix"
import parser from "@typescript-eslint/parser";
import migration from "express-zod-api/migration";
export default [
{ languageOptions: { parser }, plugins: { migration } },
{ files: ["**/*.ts"], rules: { "migration/v21": "error" } },
];
```
```ts
// The sample of new structure
const config = createConfig({
http: { listen: 80 }, // became optional
https: { listen: 443, options: {} },
upload: true,
compression: true,
beforeRouting: ({ app, getLogger }) => {
const logger = getLogger();
app.use((req, res, next) => {
const childLogger = getLogger(req);
});
},
});
const { servers } = await createServer(config, {});
```
## Version 20
### v20.22.1
- Avoids startup logo distortion when the terminal is too narrow;
- Self-diagnosis for potential problems disabled in production mode to ensure faster startup:
- Warning about potentially unserializable schema for JSON operating endpoints was introduced in v20.15.0.
### v20.22.0
- Featuring a helper to describe nested Routing for already assigned routes:
- Suppose you want to describe `Routing` for both `/v1/path` and `/v1/path/subpath` routes having Endpoints attached;
- Previously, an empty path segment was proposed for that purpose, but there is more elegant and readable way now;
- The `.nest()` method is available both on `Endpoint` and `DependsOnMethod` instances:
```ts
import { Routing } from "express-zod-api";
// Describing routes /v1/path and /v1/path/subpath both having endpoints assigned:
const before: Routing = {
v1: {
path: {
"": endpointA,
subpath: endpointB,
},
},
};
const after: Routing = {
v1: {
path: endpointA.nest({
subpath: endpointB,
}),
},
};
```
### v20.21.2
- Fixed the example implementation in the generated client for endpoints using path params:
- The choice of parser was made based on the exported `const jsonEndpoints` indexed by `path`;
- The actual `path` used for the lookup already contained parameter substitutions so that JSON parser didn't work;
- The new example implementation suggests choosing the parser based on the actual `response.headers`;
- The issue was found and reported by [@HenriJ](https://github.com/HenriJ).
```diff
- const parser = `${method} ${path}` in jsonEndpoints ? "json" : "text";
+ const isJSON = response.headers
+ .get("content-type")
+ ?.startsWith("application/json");
- return response[parser]();
+ return response[isJSON ? "json" : "text"]();
```
### v20.21.1
- Performance tuning: `Routing` traverse made about 12 times faster.
### v20.21.0
- Feat: input schema made optional:
- The `input` property can be now omitted on the argument of the following methods:
`Middlware::constructor`, `EndpointsFactory::build()`, `EndpointsFactory::addMiddleware()`;
- When the input schema is not specified `z.object({})` is used;
- This feature aims to simplify the implementation for Endpoints and Middlwares having no inputs.
### v20.20.1
- Minor code st