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,268 lines (1,001 loc) • 196 kB
Markdown
# Changelog
## Version 24
### 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 style refactoring and performance tuning;
- The software is redefined as a framework;
- Thanks to [@JonParton](https://github.com/JonParton) for contribution to the documentation.
### v20.20.0
- Introducing `errorHandler` option for `testMiddleware()` method:
- If your middleware throws an error there was no ability to make assertions other than the thrown error;
- New option can be assigned with a function for transforming the error into response, so that `testMiddlware` itself
would not throw, enabling usage of all returned entities for multiple assertions in test;
- The feature suggested by [@williamgcampbell](https://github.com/williamgcampbell).
```ts
import { testMiddleware, Middleware } from "express-zod-api";
const middlware = new Middleware({
input: z.object({}),
handler: async ({ logger }) => {
logger.info("logging something");
throw new Error("something went wrong");
},
});
test("a middleware throws, but it writes log as well", async () => {
const { loggerMock, responseMock } = await testMiddleware({
errorHandler: (error, response) => response.end(error.message),
middleware,
});
expect(loggerMock._getLogs().info).toEqual([["logging something"]]);
expect(responseMock._getData()).toBe("something went wrong");
});
```
### v20.19.0
- Configuring built-in logger made optional:
- Built-in logger configuration option `level` made optional as well as the `logger` option for `createConfig()`;
- Using `debug` level by default, or `warn` when `NODE_ENV=production`.
- Fixed performance issue on `BuiltinLogger` when its `color` option is not set in config:
- `.child()` method is 50x times faster now by only detecting the color support once;
- Color autodetection was introduced in v18.3.0.
### v20.18.0
- Introducing `ensureHttpError()` method that converts any `Error` into `HttpError`:
- It converts `InputValidationError` to `BadRequest` (status code `400`) and others to `InternalServerError` (`500`).
- Deprecating `getStatusCodeFromError()` — use the `ensureHttpError().statusCode` instead.
- Generalizing server-side error messages in production mode by default:
- This feature aims to improve the security of your API by not disclosing the exact causes of errors;
- Applies to `defaultResultHandler`, `defaultEndpointsFactory` and Last Resort Handler only;
- When `NODE_ENV` is set to `production` (displayed on startup);
- Instead of actual message the default one associated with the corresponding `statusCode` used;
- Server-side errors are those having status code `5XX`, or treated that way by `ensureHttpError()`;
- You can control that behavior by throwing errors using `createHttpError()` and using its `expose` option;
- More about production mode and how to activate it:
https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production
```ts
import createHttpError from "http-errors";
// NODE_ENV=production
// Throwing HttpError from Endpoint or Middleware that is using defaultResultHandler or defaultEndpointsFactory:
createHttpError(401, "Token expired"); // —> "Token expired"
createHttpError(401, "Token expired", { expose: false }); // —> "Unauthorized"
createHttpError(500, "Something is broken"); // —> "Internal Server Error"
createHttpError(501, "We didn't make it yet", { expose: true }); // —> "We didn't make it yet"
```
### v20.17.0
- Added `cause` property to `DocumentationError`;
- Log all server side errors (status codes `>= 500`) and in full (not just the `message`).
### v20.16.0
- Deprecating `originalError` property on both `InputValidationError` and `OutputValidationError`:
- Use `cause` property instead;
- Those error classes are publicly exposed for developers making custom Result Handlers.
```diff
const error = new InputValidationError(new z.ZodError([]));
- logger.error(error.originalError.message);
+ logger.error(error.cause.message);
```
### v20.15.3
- Merge intersected object types in generated client:
- This fixes "empty object" intersection problem for endpoints having middlwares without inputs.
```diff
- type GetV1UserRetrieveInput = {} & {
+ type GetV1UserRetrieveInput = {
/** a numeric string containing the id of the user */
id: string;
};
```
### v20.15.2
- Fixed duplicated client types in unions:
- When `splitResponse` option is disabled on `Integration` primitive response types could have been duplicated.
```diff
- type GetV1AvatarSendResponse = string | string;
+ type GetV1AvatarSendResponse = string;
```
### v20.15.1
- Deprecating `serializer` property on `Documentation` and `Integration` constructor argument:
- That property was introduced in v9.3.0 and utilized for comparing schemas in order to handle possible circular
references within `z.lazy()`;
- The property is no longer in use and will be removed in version 21.
### v20.15.0
- Feat: warn about potentially unserializable schema used for JSON operating endpoints:
- This version will warn you if you're using a schema that might not work in request or response, in particular:
- Generally unserializable objects: `z.map()`, `z.set()`, `z.bigint()`;
- JSON incompatible entities: `z.never()`, `z.void()`, `z.promise()`, `z.symbol()`, `z.nan()`;
- Non-revivable in request: `z.date()`;
- Incorrectly used in request: `ez.dateOut()`;
- Incorrectly used in response: `ez.dateIn()`, `ez.upload()`, `ez.raw()`;
- The feature suggested by [@t1nky](https://github.com/t1nky).
### v20.14.3
- Fixed: missing export of `testMiddleware`:
- The feature introduced in v20.4.0 but was not available;
- The issue reported by [@Tomtec331](https://github.com/Tomtec331).
### v20.14.2
- Documentation: promoting Express 5 as the recommended version for new projects;
- Minor refactoring: response variant constraints, inverted definition of `AbstractLogger` type;
- There is now an opportunity to support the project with sponsorship: https://github.com/sponsors/RobinTail
### v20.14.1
- `node-mocks-http` version is `^1.16.1`:
- This deduplicates the `@types/express` dependency to the version installed in your project;
- This fix is an addition to the support of Express 5 (v20.10.0) and its types (v20.14.0).
### v20.14.0
- Enabling usage of recently released `@types/express@^5.0.0`:
- This is an addition to the support of Express 5 introduced in v20.10.0.
### v20.12.0
- Feat: Graceful Shutdown
- You can enable and configure a special request monitoring that,
if it receives a signal to terminate a process, will:
- first put the server into a mode that rejects new requests,
- attempt to complete started requests within the specified time,
- and then forcefully stop the server and terminate the process;
- This feature utilizes a modernized fork of [http-terminator](https://github.com/gajus/http-terminator).
```ts
import { createConfig } from "express-zod-api";
createConfig({
gracefulShutdown: {
timeout: 1000,
events: ["SIGINT", "SIGTERM"],
},
});
```
### v20.11.0
- Feat: Handling deprecation events by actual logger
- `express` uses `depd` for emitting `deprecation` events when a certain deprecated approach used;
- This version installs a listener of that event and delegates it to the `warn` method of your actual logger.
### v20.10.0
- Feat: Supporting Express 5
- Epic news: after 10 years of struggles and anticipations
[Express 5.0.0 is finally released](https://github.com/expressjs/express/releases/tag/v5.0.0);
- The primary a mostly awaited feature is the proper support of asynchronous handlers;
- This version introduces the initial support of Express 5 without breaking changes;
- Notice: the corresponding `@types/express` for the version 5 is not yet released;
- [Instructions on migrating to Express 5](https://expressjs.com/en/guide/migrating-5.html).
### v20.9.2
- Minor syntax adjustments and cleanup;
- `node-mocks-http` version is 1.16.0.
### v20.9.1
- Plain text MIME type is set for the corresponding responses:
- Last Resort Handler (in case your ResultHandler throws);
- ~~`arrayResultHandler`~~ (deprecated) — in case of errors and failures.
### v20.9.0
- `openapi3-ts` version is 4.4.0:
- Feat: `Documentation::getSpecAsYaml()` accepts the same options as `yaml.stringify`.
### v20.8.0
- Feat: providing child logger to `beforeRouting()` hook:
- The function assigned to config property `server.beforeRouting` now accepts additional argument `getChildLogger()`;
- The featured method accepts `request` and returns a child logger if `childLoggerProvider()` is configured;
- Otherwise, it returns the root logger (same for all requests, same as the `logger` argument);
- The feature suggested by [@williamgcampbell](https://github.com/williamgcampbell).
```ts
import { createConfig } from "express-zod-api";
import { randomUUID } from "node:crypto";
const config = createConfig({
logger: { level: "debug" },
childLoggerProvider: ({ parent }) =>
parent.child({ requestId: randomUUID() }),
server: {
listen: 80,
beforeRouting: ({ app, logger, getChildLogger }) => {
logger.info("This is root logger");
app.use((req, res, next) => {
getChildLogger(req).info("This is a child logger");
next();
});
},
},
});
```
### v20.7.1
- Improved documentation on error handling:
- More clarity on the origins of possible runtime errors and how they are handled by default;
- Revealing details on how routing, parsing and upload errors are handled by default;
- Correction to the JSDoc of the corresponding `errorHandler` property in config.
- Removing redundant type coercion in the migration tool.
### v20.7.0
- Changes to migration plugin (single-use tool, regardless SemVer):
- Requirements: `eslint@^9` and `typescript-eslint@^8` (may work with previous versions, but it's no longer tested);
- The `express-zod-api/migration` is a pure ESLint plugin: no rule applied by default, it must be enabled explicitly;
- The files requiring migration have to be defined explicitly — this should improve clarity on its operation;
- The ESLint plugin was introduced in v20.0.0 for automated migration from v19 (except assertions in tests);
- For migrating from v19 use the following minimal config and run `eslint --fix`:
```javascript
// eslint.config.js (or .mjs if you're developing in a CommonJS environment)
import parser from "@typescript-eslint/parser";
import migration from "express-zod-api/migration";
export default [
{ languageOptions: { parser }, plugins: { migration } },
{
files: ["**/*.ts"], // define the files need to be migrated (source code)
rules: { "migration/v20": "error" }, // enable the rule explicitly
},
];
```
### v20.6.2