UNPKG

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,122 lines (879 loc) 228 kB
# Changelog ## Version 29 ### v29.0.0 - Supported Node.js versions: `^22.19.0 || ^24.11.0 || ^26.0.0`; - Major changes to the package distribution: - `Documentation` and `Depicter` type moved to the `express-zod-api/documentation` subpath; - `Integration` and `Producer` type moved to the `express-zod-api/integration` subpath; - Several types and interfaces are no longer exposed: - `CommonConfig`, `AppConfig`, `ServerConfig` — use `createConfig()` instead; - `ApiResponse` — use `createApiResponse()` instead (new); - `CacheControl` and `CachePolicy` — use `createCacheMiddleware()` or `EndpointsFactory::useCacheMiddleware()`; - `FlatObject`, `IOSchema` and every type ending with `Security`; - The public nature of these types was a workaround for user-side type declarations, but it's solved differently. - Breaking change to the `cors` config option: - Changed from `boolean | HeadersProvider` to `boolean | RequestHandler`; - You can now use the well-known `cors` package or pass a conventional `RequestHandler` directly. - Body parsers are now applied globally rather than per-endpoint (`createServer` experience only): - Endpoints can accept requests in different content types (e.g., either JSON or URL-encoded body); - The `Documentation` generator continues to guess the desired request type from the input schema; - Parsers retain the ability for configuration via `jsonParser`, `formParser`, and `rawParser` config options; - If using `attachRouting()` in a DIY server, parsers have to be installed manually. - Potentially breaking changes to the server lifecycle hooks: - The hooks (`beforeRouting` and `afterRouting` config options) are no longer async; - `beforeRouting` now runs after the installation of globally enabled parsers; - Added new `beforeParsers` hook that runs before all parsers are installed. - The `createServer()` function is now synchronous — that should simplify the daily routines for beginners; - Added HTTP QUERY method support (RFC 10008): - The QUERY method is like GET but with a body — safe, idempotent, and cacheable; - Default input sources for QUERY: `["query", "body", "params"]` (from the lowest priority to highest); - Supported by `Integration` and `Documentation` generators. - Changes to `Documentation`: - `serverUrl` constructor option renamed to `server` and now also accepts OpenAPI's ServerObject; - `title` option and `version` must be wrapped into `info`, assignable with OpenAPI's InfoObject; - Now produces OpenAPI 3.2.0 with better SSE support and other features. - Changes to `Integration`: - The static async method `create()` removed — use `new Integration()` instead; - Removed `typescript` option from constructor — now imported statically; - Generated type for `ez.buffer()` is now `Blob` instead of `Buffer` (which didn't exist in browser environments); - The generated Client now excludes cookie-based security fields from input types (using `Omit`); - The `Implementation` type requires `ctx` to be an object, the default one has `override` method for customizations; - The default `Client` Implementation got improved response parsing and now supports `Blob` in request and response; - The default `Client` Implementation now also supports uploads from the properties assigned with `File` or `Blob`; - Both `Client` and `Subscription` delegate cookies handling (credentials) to the browser when supported; - Added `hasCredentials` — an explicit declaration that the API supports credentialed CORS. - Consider using [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration). ```diff - import { Integration, Documentation, type Producer, type Depicter } from "express-zod-api"; + import { Integration, type Producer } from "express-zod-api/integration"; + import { Documentation, type Depicter } from "express-zod-api/documentation"; const config = createConfig({ - cors: () => ({ origin: "https://example.com" }), + cors: cors({ origin: "https://example.com" }), // import cors from "cors" }); - await Integration.create({}); + new Integration({}); - const {} = await createServer({}); + const {} = createServer({}); new Documentation({ + info: { title: "Sample API", version: "1.2.3", + }, - serverUrl: "https://example.com", + server: "https://example.com", }); ``` ## Version 28 ### v28.7.11 - Added missing path params check to the Documentation generator: - An Endpoint assigned to `get /users/:id` must declare the `id` property on the `input` schema; - Similar check is already implemented in runtime (development mode only); - Found and reported by [@marco-carvalho](https://github.com/marco-carvalho). ### v28.7.10 - Fixed issue for routes having differently named path params: - In case of same method it will now always throw a RoutingError: - Example: having both `get /items/:id` and `get /items/:slug` routes. - If methods are different, Documentation generator will throw `DocumentationError`: - Example: OpenAPI standard does not allow having both `get /users/:id` and `delete /users/:userId`; - See https://spec.openapis.org/oas/v3.1.0#paths-object for details. - Found and reported by [@marco-carvalho](https://github.com/marco-carvalho). ### v28.7.9 - Fixes the issue where the same `.meta({ id })` could be used on different schemas: - Zod 4.3 removed the `id` uniqueness constraint, but the `Documentation` generator needs it for making references; - The Documentation generator will now throw a `DocumentationError` if it finds the same `id` on different schemas; - Found and reported by [@marco-carvalho](https://github.com/marco-carvalho). ### v28.7.8 - Fixes component duplication in the generated Documentation: - When a schema with `.meta({ id })` is reused across endpoints with `composition: "components"`; - Found and reported by [@marco-carvalho](https://github.com/marco-carvalho). ### v28.7.7 - Fixes an issue where an empty path could be emitted for the root route by Documentation instead of `/`: - Found and reported by [@marco-carvalho](https://github.com/marco-carvalho). ### v28.7.6 - Added well-known header, recognized by Documentation generator: `want-unencoded-digest`. ### v28.7.5 - Small performance improvement for startup and Documentation generator (cache for request type resolution). ### v28.7.4 - Centralized the `ResultHandler` error handling by `AbstractResultHandler::execute()`: - Last Resort Handler is now an internal static method of the abstract class; - When `errorHandler` fails to process a routing error (Not found) it's now also handled by the Last Resort Handler; - Added an error handling diagram to the documentation in the Readme file. ### v28.7.3 - Several minor improvements to the `gracefulShutdown` feature: - Ensured a single graceful shutdown event listener per signal on the process; - Handling potential errors thrown from syncronous `beforeExit` hooks and ensured the hooks run only once. ### v28.7.2 - Several minor memory optimizations: - Ensured a single deprecation event listener on the process; - When `gracefulShutdown` is enabled, destroying broken sockets immediately (edge case); - For SSE endpoints, removing the stream ensuring timer when the connection is closed. ### v28.7.1 - Fix: supporting the latest versions of `express-rate-limit` (v8) for the rate limiting feature. ### v28.7.0 - Added rate limiting support: - Requires `express-rate-limit` — new optional peer dependency; - Added the `createRateLimitMiddleware()` method and `EndpointsFactory::useRateLimit()` shorthand; - When the limit is exceeded, the Middleware throws a `429` HTTP error, handled by your ResultHandler; - The middleware provides `rateLimit` property to the context with the current rate limit info (`limit`, `used`, `remaining`, `resetTime`) and the limiter API (`getKey()` and `resetKey()` methods) for programmatic management. ### v28.6.0 - Changes to the `Integration` generator: - The `typescript` option is no longer required for constructor; - `Integration.create()` is now deprecated — use `new Integration()` instead; - This is possible thanks to the `require(ESM)` feature, available on all supported Node.js versions; ### v28.5.0 - Changes to the proprietary `Date` handling schemas: - Supporting timezones by `ez.dateIn()` schema (e.g. `2021-12-31T23:59:59+02:00`); - Piping the transformation inside the `ez.dateOut()` through `z.iso.datetime()` for certainty; - The Documentation generator removes custom depictions from both schemas and delegates them to Zod for clarity: - The previous fallback description (`YYYY-MM-DDTHH:mm:ss.sssZ`) is removed; - use `{ description: "whatever you like" }` as an argument to either schema instead; - Fixed the type of the argument accepting the metadata for both schemas. ### v28.4.0 - Improved `testMiddleware()` to return the typed `output` property: - Changed from `Record<string, unknown>` to `Partial<RET>`, where `RET` is the context type the Middleware returns; - This should simplify testing the context properties, especially functional ones; - If Middleware throws, the `output` would still be an empty object, which is aligned with its new type. ### v28.3.0 - `EndpointsFactory::addContext()` now passes the previously accumulated context to its callback: - The argument function receives the current context as the first argument; - Improved the declaration file for most of the essential entities. ### v28.2.0 - Added `createCacheMiddleware()` function for solving most of the caching problems: - The function accepts an optional default policy to apply `Cache-Control` header to every response; - It returns a Middleware providing several properties and caching helpers to context: - `cacheControl` — the parsed request's `Cache-Control` header into a typed object; - `ifNoneMatch` — the parsed `If-None-Match` request header into an array of ETags or `"*"`; - `ifModifiedSince` — the parsed `If-Modified-Since` request header into a `Date`; - `addCachePolicy()` — extends the default policy into the `Cache-Control` response header; - `setETag()` — sets the `ETag` response header; - `setLastModified()` — sets the `Last-Modified` response header; - `setVary()` — sets the `Vary` response header; - `setExpires()` — sets the `Expires` response header; - `clearSiteData()` — sets the `Clear-Site-Data` header with `cache` directive; - `notModified()` — sends an HTTP 304 response and ends the stream; - Adjusted the Endpoint execution to skip output validation in case `response.writableEnded` (`notModified` called); - Added two shorthand methods to `EndpointsFactory` class: `useCache()` and `useCookies()`. ### v28.1.1 - Depicting Endpoints built on `new EventStreamFactory({})` (having empty argument) will throw a `ResultHandlerError`: - Applies to `Documentation`, `Integration` as well as self-diagnostics (starting the server in development mode). ### v28.1.0 - Added support for cookie handling: - Cookie parsing can be enabled and configured in config (requires to install `cookie-parser`); - `cookies` and `signedCookies` can be used as `inputSources` in config; - `createCookieMiddleware()` creates a Middleware that exposes `setCookie()` and `clearCookie()` helpers into context as well as the `getCookie()` one as an alternative to using cookies within `inputSources`; - Documentation depicts request parameters when Middleware has `security` schema with `type: cookie`. ### v28.0.1 - Adjusted the list of well-known headers, recognized by Documentation generator: - Added: cdn-loop; - Removed (not expected in Request): accept-ch, alternates, authentication-control, authentication-info, cross-origin-embedder-policy, cross-origin-embedder-policy-report-only, cross-origin-opener-policy, cross-origin-opener-policy-report-only, cross-origin-resource-policy, dpop-nonce, nel, p3p, public, public-key-pins, public-key-pins-report-only, setprofile, use-as-dictionary. ### v28.0.0 - Supported Node.js versions: `^22.19.0 || ^24.0.0 || ^26.0.0`; - Zod compatibility: `^4.3.4` (supports Zod 4.4+ without upper limit); - The Zod plugin is no longer installed automatically — it's an optional peer dependency now: - To keep using `.example()`, `.label()`, `.remap()` and `.deprecated()` methods on schemas, as well as runtime distinguishable brands, install the `@express-zod-api/zod-plugin` manually and import it (ideally at the top of a file declaring your `Routing`); - Breaking change: `ZodType::brand()` method is no longer patched by the plugin: - Use `.xBrand()` method instead — alias for `.meta({ "x-brand": ... })` and does not conflict with Zod 4.4; - Breaking changes to the `createConfig()` argument (object): - property `wrongMethodBehavior` (number) changed to `hintAllowedMethods` (boolean); - property `methodLikeRouteBehavior` (string literal) changed to `recognizeMethodDependentRoutes` (boolean); - Breaking change to the `EndpointsFactory::build()` argument (object): - property `shortDescription` renamed to `summary`; - Breaking change to the `Documentation` constructor argument (object): - property `hasSummaryFromDescription` (boolean) replaced with `summarizer` (function); - If used with `false` value, replace it with `summarizer: ({ summary, trim }) => trim(summary)` for same behavior; - Featuring `summarizer` option to customize the summary of the Endpoint in the generated Documentation: - The function receives `summary`, `description` and the default `trim()` function as arguments; - The default summarizer uses `description` as a fallback for missing `summary`; - The `trim()` function accepts a string and the limit (default: 50, best practice) that you can now customize; - Breaking change to the `Integration` constructor argument (object): - property `noContent` renamed to `noBodySchema`; - Consider using [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration): - Now requires `eslint@^10.0.0` and `typescript-eslint@^8.58.0`. ```diff createConfig({ - wrongMethodBehavior: 404, + hintAllowedMethods: false, - methodLikeRouteBehavior: "path", + recognizeMethodDependentRoutes: false, }); factory.build({ - shortDescription: "Retrieves the user.", + summary: "Retrieves the user.", }); new Documentation({ - hasSummaryFromDescription: false, + summarizer: ({ summary, trim }) => trim(summary), }); new Integration({ - noContent: z.undefined(), + noBodySchema: z.undefined(), }); ``` ## Version 27 ### v27.3.0 - Supporting Node 26. ### v27.2.6 - Limited Zod compatibility to `~4.3.4` (<4.4.0): - Zod 4.4.0 introduced a breaking change to how `brand` method works, making the current plugin approach incompatible; - `@express-zod-api/zod-plugin` version bumped to `^4.1.1` (limited Zod compatibility as well); - Supporting Zod 4.4+ requires a breaking change and will be addressed in v28 (next major version). ### v27.2.5 - Reduced memory footprint at runtime: deferred population of well-known headers to Documentation; - Faster header lookup in Documentation generator (only when `headers` enabled in `inputSources`). ### v27.2.4 - Fixed performance regression since v24.0.0: - Removed unnecessary processing of output schema examples; - Those used to be processed when the first API request occurred on endpoint; - Examples are only required for Documentation generator, not for runtime validation. ### v27.2.3 - Optimizing the performance of traverses: - Replaced O(n) `Array::shift()` and `::unshift()` operations with O(1) index-based iteration; - The expected boost is from 1.8x to 20x depending on queue size; - Scope: generators (Integration and Documentation), startup (diagnostics, routing). ### v27.2.2 - `@express-zod-api/zod-plugin` version bumped to `^4.1.0`. ### v27.2.1 - Improved handling of native Express middleware return values: - Avoids potential errors when middleware returns non-promise truthy values. ### v27.2.0 - Supporting TypeScript 6: - Supported `typescript` versions: `^5.1.3 || ^6.0.2`. ### v27.1.0 - Introducing `ez.paginated()` helper for creating paginated endpoints: - The configurable helper returns `input` and `output` schemas for your Endpoint; - Use the `style` option to choose between `offset` and `cursor` pagination; - The `itemsName` option configures the name of the property containing the items array; - The `Integration` generator now equips the `Client` with a static `hasMore()` method; - The `Incremental` added to the list of well-known recognizable request headers. ```ts import { z } from "zod"; import { ez, defaultEndpointsFactory } from "express-zod-api"; const pagination = ez.paginated({ style: "offset", // or "cursor" itemSchema: z.object({ id: z.number(), name: z.string() }), itemsName: "users", // defines the output name of the items array maxLimit: 100, defaultLimit: 20, }); const listUsers = defaultEndpointsFactory.build({ input: pagination.input, output: pagination.output, handler: async ({ input: { limit, offset } }) => { const { users, total } = await db.getUsers(limit, offset); return { users, total, limit, offset }; // or { users, nextCursor, limit } for cursor pagination }, }); ``` ### v27.0.1 - Removed debug-level comments from the declaration files in the distribution. ### v27.0.0 - Supported `zod` versions: `^4.3.4`; - The new version of Zod Plugin uses the inheritable metadata feature of Zod 4.3; - The `typescript` dependency is now optional and only required for making `Integration`: - Either import and assign the `typescript` property to its constructor argument; - Or use the new static async method `create()` to delegate the import; - This change addresses the memory consumption issue fixed previously in v26.1.0, but with proper ESM handling; - Consider [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration). ```diff /** Option 1: import and assign */ import { Integration } from "express-zod-api"; + import typescript from "typescript"; const client = new Integration({ routing, config, + typescript, }); ``` ```diff /** Option 2: delegate asynchronously */ import { Integration } from "express-zod-api"; - const client = new Integration({ + const client = await Integration.create({ routing, config, }); ``` ## Version 26 ### v26.3.2 - Improved readability of the types declaration in the bundle; ### v26.3.1 - Fixed the type of `ctx` for Endpoints built on factories having at least one Middleware: - Removed `Record<string, never>` from the type union; - This prevents accessing non-existing properties on `ctx`; - The issue was reported by [@pycanis](https://github.com/pycanis). ### v26.3.0 - Supporting several latest features of Zod 4.2 and 4.3 by the `Integration` generator: - `z.looseRecord()` schema; - Object properties wrapped by `z.exactOptional()` or created by `ZodType::exactOptional()` method. ### v26.2.0 - Ability to specify a custom name for a schema in the generated Documentation: - Use the `.meta()` method on a schema with an `{ id }` as an argument; - The `id` must be unique across all schemas used in your API; - The feature proposed by [@arlyon](https://github.com/arlyon) 2 years ago, but the implementation became possible only with Zod 4 and thanks to suggestions from [@Upsilon-Iridani](https://github.com/Upsilon-Iridani). ### v26.1.0 - Optimization to the memory consumption for your API: - It has been discovered that static import of `typescript` within the framework consumes memory unnecessarily; - Importing `typescript` is only necessary to generate an Integration; - This version avoids static importing, but the solution is temporary in order to avoid breaking changes; - The issue was found, investigated and reported by [@NicolasMahe](https://github.com/NicolasMahe). ### v26.0.0 - Supported `http-errors` versions: `^2.0.1`; - Supported `zod` versions: `^4.1.13`: - This Zod patch contains an [important fix](https://github.com/colinhacks/zod/pull/5452) that makes the `globalRegistry` truly global across both CJS and ESM bundles of the Zod distribution; - The issue was found and reported by [@shadone](https://github.com/shadone); - The new version of the Zod plugin now also extends the CJS exports of Zod: - This fixes the "TypeError: example is not a function" in CJS and removes the requirement to use an ESM environment; - The issue was reported by [@squishykid](https://github.com/squishykid) and addressed earlier in [v25.5.3](#v2553); - `DependsOnMethod` removed: - You can now specify methods as direct keys of an assigned object in `Routing`; - That object can still contain nested paths as before; - Keys matching lowercase HTTP methods are treated according to the new config setting `methodLikeRouteBehavior`: - `method` — when assigned with an Endpoint, the key is treated as a method of its parent path (default); - `path` — the key is always treated as a nested path segment; - The `options` property has been renamed to `ctx` in the argument of: - `Middleware::handler()`, - `ResultHandler::handler()`, - The `handler` of the `EndpointsFactory::build()` argument, - `testMiddleware()`; - `EndpointsFactory::addOptions()` renamed to `addContext()`; - The `Integration::constructor()` argument object now requires a `config` property, similar to `Documentation`; - Consider [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration). ```diff const routing: Routing = { - "/v1/users": new DependsOnMethod({ + "/v1/users": { get: getUserEndpoint, - }).nest({ create: makeUserEndpoint - }), + }, }; ``` ## Version 25 ### v25.6.1 - Technical update before v26: no significant changes. ### v25.6.0 - Added `afterRouting` hook to server configuration: - Similar to `beforeRouting` one; - A code to execute after processing the Routing of your API, but before error handling. ### 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.objec