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,270 lines • 61.6 kB
TypeScript
import * as _$zod from "zod";
import { z } from "zod";
import { HttpError } from "http-errors";
import * as _$express from "express";
import express, { CookieOptions, IRouter, NextFunction, Request, RequestHandler, Response } from "express";
import http from "node:http";
import https, { ServerOptions } from "node:https";
import { OpenApiBuilder, ReferenceObject, SchemaObject, TagObject } from "openapi3-ts/oas31";
import * as _$node_mocks_http0 from "node-mocks-http";
import { RequestOptions, ResponseOptions } from "node-mocks-http";
import compression from "compression";
import * as _$express_fileupload0 from "express-fileupload";
import fileUpload from "express-fileupload";
import cookieParser from "cookie-parser";
import { ListenOptions } from "node:net";
import * as _$express_serve_static_core0 from "express-serve-static-core";
import * as _$qs from "qs";
import ts from "typescript";
import * as _$zod_v4_core0 from "zod/v4/core";
declare const methods: ("get" | "post" | "put" | "delete" | "patch")[];
declare const clientMethods: ("get" | "post" | "put" | "delete" | "patch" | "head")[];
/**
* @desc Methods supported by the framework API to produce Endpoints on EndpointsFactory.
* @see BuildProps
* @example "get" | "post" | "put" | "delete" | "patch"
* */
type Method = (typeof methods)[number];
/**
* @desc Methods usable on the client side, available via generated Integration and Documentation
* @see withHead
* @example Method | "head"
* */
type ClientMethod = (typeof clientMethods)[number];
/**
* @desc A container for describing an API response: its schema, status code(s) and MIME type(s).
* @see ResultHandler
* */
interface ApiResponse<S extends z.ZodType> {
/** @desc The Zod schema describing the response body. */
schema: S;
/**
* @desc The status code(s) for this response.
* @default 200 for a positive response, 400 for a negative one
* */
statusCode?: number | [number, ...number[]];
/**
* @desc The MIME type(s) of the response.
* @default "application/json"
* @example null — no content, typical for 204 and 302
* */
mimeType?: string | [string, ...string[]] | null;
}
/** @since zod 3.25.61 output type fixed */
declare const emptySchema: z.ZodObject<{}, z.core.$strip>;
type EmptySchema = typeof emptySchema;
/** @desc this type does not allow props assignment, but it works for reading them when merged with another interface */
type EmptyObject = z.output<EmptySchema>;
type FlatObject = Record<string, unknown>;
/** @link https://stackoverflow.com/a/65492934 */
type NoNever<T, F> = [T] extends [never] ? F : T;
/**
* @desc Using module augmentation approach you can specify tags as the keys of this interface
* @example declare module "express-zod-api" { interface TagOverrides { users: unknown } }
* @link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
* */
interface TagOverrides {}
type Tag = NoNever<keyof TagOverrides, string>;
declare const getMessageFromError: (error: Error) => string;
declare const severity: {
debug: number;
info: number;
warn: number;
error: number;
};
type Severity = keyof typeof severity;
/** @desc You can use any logger compatible with this type. */
type AbstractLogger = Record<Severity, (message: string, meta?: any) => any>;
/**
* @desc Using module augmentation approach you can set the type of the actual logger used
* @example declare module "express-zod-api" { interface LoggerOverrides extends winston.Logger {} }
* @link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
* */
interface LoggerOverrides {}
type ActualLogger = AbstractLogger & LoggerOverrides;
interface Context extends FlatObject {
requestId?: string;
}
interface BuiltinLoggerConfig {
/**
* @desc The minimal severity to log or "silent" to disable logging
* @example "debug" also enables pretty output for inspected entities
* */
level: "silent" | "warn" | "info" | "debug";
/** @desc Enables colors on printed severity and inspected entities */
color: boolean;
/**
* @desc Control how deeply entities should be inspected
* @example null
* @example Infinity
* */
depth: number | null;
/**
* @desc Context: the metadata applicable for each logged entry, used by .child() method
* @see childLoggerProvider
* */
ctx: Context;
}
interface ProfilerOptions {
message: string;
/** @default "debug" */
severity?: Severity | ((ms: number) => Severity);
/** @default formatDuration - adaptive units and limited fraction */
formatter?: (ms: number) => string | number;
}
/** @desc Built-in console logger with optional colorful inspections */
declare class BuiltinLogger implements AbstractLogger {
protected readonly config: BuiltinLoggerConfig;
/** @example new BuiltinLogger({ level: "debug", color: true, depth: 4 }) */
constructor({ color, level, depth, ctx }?: Partial<BuiltinLoggerConfig>);
protected format(subject: unknown): string;
protected print(method: Severity, message: string, meta?: unknown): void;
debug(message: string, meta?: unknown): void;
info(message: string, meta?: unknown): void;
warn(message: string, meta?: unknown): void;
error(message: string, meta?: unknown): void;
child(ctx: Context): BuiltinLogger;
/**
* @desc The argument used for instance created by .child() method
* @see ChildLoggerProvider
* */
get ctx(): Context;
/** @desc Measures the duration until you invoke the returned callback */
profile(message: string): () => void;
profile(options: ProfilerOptions): () => void;
}
type Base$1 = object & {
[Symbol.iterator]?: never;
};
/** @desc The type allowed on the top level of Middlewares and Endpoints */
type IOSchema = z.ZodType<Base$1>;
/** EndpointsFactory schema extended type when adding a Middleware */
type Extension<Current extends IOSchema | undefined, Inc extends IOSchema | undefined> = Current extends IOSchema
? Inc extends IOSchema
? z.ZodIntersection<Current, Inc>
: Current
: Inc;
/** The Endpoint input schema type, condition wrapped into schema to make it z.output-compatible */
type FinalInputSchema<FIN extends IOSchema | undefined, BIN extends IOSchema> = z.ZodIntersection<
FIN extends IOSchema ? FIN : BIN,
BIN
>;
type LogicalOr<T> = {
or: T[];
};
type LogicalAnd<T> = {
and: T[];
};
type LogicalContainer<T> = LogicalOr<T | LogicalAnd<T>> | LogicalAnd<T | LogicalOr<T>> | T;
interface BasicSecurity {
type: "basic";
}
interface BearerSecurity {
type: "bearer";
format?: "JWT" | string;
}
interface InputSecurity<K extends string> {
type: "input";
name: K;
}
interface HeaderSecurity {
type: "header";
name: string;
}
interface CookieSecurity {
type: "cookie";
name: string;
}
/**
* @see https://swagger.io/docs/specification/authentication/openid-connect-discovery/
* @desc available scopes has to be provided via the specified URL
*/
interface OpenIdSecurity {
type: "openid";
url: string;
}
interface AuthUrl {
/**
* @desc The authorization URL to use for this flow. Can be relative to the API server URL.
* @see https://swagger.io/docs/specification/api-host-and-base-path/
*/
authorizationUrl: string;
}
interface TokenUrl {
/** @desc The token URL to use for this flow. Can be relative to the API server URL. */
tokenUrl: string;
}
interface RefreshUrl {
/** @desc The URL to be used for obtaining refresh tokens. Can be relative to the API server URL. */
refreshUrl?: string;
}
interface Scopes<K extends string> {
/** @desc The available scopes for the OAuth2 security and their short descriptions. Optional. */
scopes?: Record<K, string>;
}
type AuthCodeFlow<S extends string> = AuthUrl & TokenUrl & RefreshUrl & Scopes<S>;
type ImplicitFlow<S extends string> = AuthUrl & RefreshUrl & Scopes<S>;
type PasswordFlow<S extends string> = TokenUrl & RefreshUrl & Scopes<S>;
type ClientCredFlow<S extends string> = TokenUrl & RefreshUrl & Scopes<S>;
/**
* @see https://swagger.io/docs/specification/authentication/oauth2/
*/
interface OAuth2Security<S extends string> {
type: "oauth2";
flows?: {
/** @desc Authorization Code flow (previously called accessCode in OpenAPI 2.0) */
authorizationCode?: AuthCodeFlow<S>;
/** @desc Implicit flow */
implicit?: ImplicitFlow<S>;
/** @desc Resource Owner Password flow */
password?: PasswordFlow<S>;
/** @desc Client Credentials flow (previously called application in OpenAPI 2.0) */
clientCredentials?: ClientCredFlow<S>;
};
}
/**
* @desc Middleware security schema descriptor
* @param K is an optional input field used by InputSecurity
* @param S is an optional union of scopes used by OAuth2Security
* */
type Security<K extends string = string, S extends string = string> =
| BasicSecurity
| BearerSecurity
| InputSecurity<K>
| HeaderSecurity
| CookieSecurity
| OpenIdSecurity
| OAuth2Security<S>;
type Handler$2<IN, CTX, RET> = (params: {
/** @desc The inputs from the enabled input sources validated against the input schema of the Middleware. */
input: IN;
/**
* @desc The returns of the previously executed Middlewares (typed when chaining Middlewares).
* @link https://github.com/RobinTail/express-zod-api/discussions/1250
* */
ctx: CTX;
/** @link https://expressjs.com/en/5x/api.html#req */
request: Request;
/** @link https://expressjs.com/en/5x/api.html#res */
response: Response;
/** @desc The instance of the configured logger. */
logger: ActualLogger;
}) => Promise<RET>;
declare abstract class AbstractMiddleware {
abstract execute(params: {
input: unknown;
ctx: FlatObject;
request: Request;
response: Response;
logger: ActualLogger;
}): Promise<FlatObject>;
}
/**
* @desc A Middleware that validates its input schema, executes its handler, and returns context properties available to
* next middlewares and the Endpoint handler. Can also declare security schemas for Documentation.
* @see EndpointsFactory#addMiddleware
* */
declare class Middleware<
CTX extends FlatObject,
RET extends FlatObject,
SCO extends string,
IN extends IOSchema | undefined = undefined,
> extends AbstractMiddleware {
constructor({
input,
security,
handler,
}: {
/**
* @desc Input schema of the Middleware, combining properties from all the enabled input sources
* @default undefined
* @see defaultInputSources
* */
input?: IN;
/**
* @desc Declaration of the security schemas implemented within the handler (used by Documentation).
* @see Documentation
* */
security?: LogicalContainer<Security<Extract<keyof z.input<IN>, string>, SCO>>;
/** @desc The handler returning a context available to Endpoints. */
handler: Handler$2<z.output<IN>, CTX, RET>;
});
/** @throws InputValidationError */
execute({
input,
...rest
}: {
input: unknown;
ctx: CTX;
request: Request;
response: Response;
logger: ActualLogger;
}): Promise<RET>;
}
/**
* @desc A wrapper around native Express middlewares that converts them into the framework's Middleware instances.
* Optionally, a `provider` can extend the context, and a `transformer` can convert caught errors.
* @see EndpointsFactory#addExpressMiddleware
* */
declare class ExpressMiddleware<R extends Request, S extends Response, RET extends FlatObject> extends Middleware<
FlatObject,
RET,
string
> {
constructor(
nativeMw: (request: R, response: S, next: NextFunction) => any,
{
provider,
transformer,
}?: {
/** @desc Extracts context properties from request and response after the native middleware execution. */
provider?: (request: R, response: S) => RET | Promise<RET>;
/** @desc Transforms errors caught from the native middleware before they propagate further. */
transformer?: (err: Error) => Error;
},
);
}
type ResultSchema<R extends Result> = R extends Result<infer S> ? S : never;
type DiscriminatedResult =
| {
output: FlatObject;
error: null;
}
| {
output: null;
error: Error;
};
/**
* @example InputValidationError —> BadRequest(400)
* @example Error —> InternalServerError(500)
* */
declare const ensureHttpError: (error: Error) => HttpError;
type Handler$1<RES = unknown> = (
params: DiscriminatedResult & {
/** null in case of failure to parse or to find the matching endpoint (error: not found) */
input: FlatObject | null;
/** can be empty: check the presence of the required property using the "in" operator */
ctx: FlatObject;
request: Request;
response: Response<RES>;
logger: ActualLogger;
},
) => void | Promise<void>;
/**
* @desc Result definition for ResultHandler: a plain schema (for JSON and default status codes) or custom ApiResponse.
* @see ApiResponse
* */
type Result<S extends z.ZodType = z.ZodType> = S | ApiResponse<S> | ApiResponse<S>[];
/** @desc A function that lazily produces a Result definition. */
type LazyResult<R extends Result, A extends unknown[] = []> = (...args: A) => R;
declare abstract class AbstractResultHandler {
protected constructor(handler: Handler$1);
execute(...params: Parameters<Handler$1>): void | Promise<void>;
}
/**
* @desc The entity responsible to respond consistently. Accepts positive and negative Result definitions.
* The positive definition can be a lazy function receiving the output schema of an Endpoint.
* @see Result
* */
declare class ResultHandler<POS extends Result, NEG extends Result> extends AbstractResultHandler {
constructor(params: {
/** @desc A description of the API response in case of success (schema, status code, MIME type) */
positive: POS | LazyResult<POS, [IOSchema]>;
/** @desc A description of the API response in case of error (schema, status code, MIME type) */
negative: NEG | LazyResult<NEG>;
/** @desc The actual implementation to transmit the response in any case */
handler: Handler$1<z.output<ResultSchema<POS> | ResultSchema<NEG>>>;
});
}
/**
* @desc The default ResultHandler wrapping Endpoint output in `{ status: "success", data: output }`
* and errors in `{ status: "error", error: { message } }`. Responds with JSON Content-Type.
* Respects the status of errors from createHttpError(), others become InternalServerError (500).
* @see ensureHttpError
* */
declare const defaultResultHandler: ResultHandler<
z.ZodObject<
{
status: z.ZodLiteral<"success">;
data: IOSchema;
},
z.core.$strip
>,
z.ZodObject<
{
status: z.ZodLiteral<"error">;
error: z.ZodObject<
{
message: z.ZodString;
},
z.core.$strip
>;
},
z.core.$strip
>
>;
/**
* @deprecated Resist the urge of using it: this handler is designed only to simplify the migration of legacy APIs.
* @desc Responding with an array is a bad practice keeping your endpoints from evolving without breaking changes.
* @desc This handler expects your endpoint to have the property 'items' in the output object schema
* */
declare const arrayResultHandler: ResultHandler<
z.ZodArray<z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>> | z.ZodArray<z.ZodAny>,
{
schema: z.ZodString;
mimeType: string;
}
>;
type OriginalStatic = typeof express.static;
declare class ServeStatic {
constructor(...params: Parameters<OriginalStatic>);
}
/** @desc Returns child logger for the given request (if configured) or the configured logger otherwise */
type GetLogger = (request?: Request) => ActualLogger;
/**
* @example { v1: { books: { ":bookId": getBookEndpoint } } }
* @example { "v1/books/:bookId": getBookEndpoint }
* @example { "get /v1/books/:bookId": getBookEndpoint }
* @example { v1: { "patch /books/:bookId": changeBookEndpoint } }
* @example { dependsOnMethod: { get: retrieveEndpoint, post: createEndpoint } }
* @see CommonConfig.recognizeMethodDependentRoutes
* */
interface Routing {
[K: string]: Routing | AbstractEndpoint | ServeStatic;
}
type Handler<IN, OUT, CTX> = (params: {
/** @desc The inputs from the enabled input sources validated against the final input schema (incl. Middlewares) */
input: IN;
/** @desc The returns of the assigned Middlewares */
ctx: CTX;
/** @desc The instance of the configured logger */
logger: ActualLogger;
}) => Promise<OUT>;
declare abstract class AbstractEndpoint {
/** @desc Enables nested routes within the path assigned to the subject */
nest(routing: Routing): Routing;
/** @desc Marks the route as deprecated (makes a copy of the endpoint) */
abstract deprecated(): this;
abstract execute(params: {
request: Request;
response: Response;
logger: ActualLogger;
config: CommonConfig;
}): Promise<void>;
}
declare class Endpoint<IN extends IOSchema, OUT extends IOSchema, CTX extends FlatObject> extends AbstractEndpoint {
constructor(def: {
deprecated?: boolean;
middlewares?: AbstractMiddleware[];
inputSchema: IN;
outputSchema: OUT;
handler: Handler<z.output<IN>, z.input<OUT>, CTX>;
resultHandler: AbstractResultHandler;
description?: string;
summary?: string;
getOperationId?: (method: ClientMethod) => string | undefined;
methods?: Method[];
scopes?: string[];
tags?: string[];
});
deprecated(): this;
execute({
request,
response,
logger,
config,
}: {
request: Request;
response: Response;
logger: ActualLogger;
config: CommonConfig;
}): Promise<undefined>;
}
type InputSource = keyof Pick<Request, "query" | "body" | "files" | "params" | "headers" | "cookies" | "signedCookies">;
type InputSources = Record<Method, InputSource[]>;
type Headers = Record<string, string>;
type HeadersProvider = (params: {
/** @desc The default headers to be overridden. */
defaultHeaders: Headers;
request: Request;
endpoint: AbstractEndpoint;
logger: ActualLogger;
}) => Headers | Promise<Headers>;
type ChildLoggerProvider = (params: { request: Request; parent: ActualLogger }) => ActualLogger | Promise<ActualLogger>;
type LogAccess = (request: Request, logger: ActualLogger) => void;
interface CommonConfig {
/**
* @desc Enables cross-origin resource sharing.
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
* @desc You can override the default CORS headers by setting up a provider function here.
*/
cors: boolean | HeadersProvider;
/**
* @desc Controls how to respond to a request to an existing endpoint with an invalid HTTP method.
* @example true — respond with status code 405 and "Allow" header containing a list of valid methods
* @example false — respond with status code 404 (Not found)
* @default true
*/
hintAllowedMethods?: boolean;
/**
* @desc Controls how to treat Routing keys matching HTTP methods ("get", "post") and having Endpoint assigned.
* @example true — treat such keys as HTTP methods complementing their parent paths
* { users: { get: ... }} becomes GET /users
* @example false — treat such keys as nested path segments regardless of the name
* { users: { get: ... }} remains /users/get
* @default true
*/
recognizeMethodDependentRoutes?: boolean;
/**
* @desc The ResultHandler to use for handling routing, parsing and upload errors
* @default defaultResultHandler
* @see defaultResultHandler
*/
errorHandler?: AbstractResultHandler;
/**
* @desc Built-in logger configuration or an instance of any compatible logger.
* @example { level: "debug", color: true }
* @default { level: NODE_ENV === "production" ? "warn" : "debug", color: isSupported(), depth: 2 }
* */
logger?: Partial<BuiltinLoggerConfig> | AbstractLogger;
/**
* @desc A child logger returned by this function can override the logger in all handlers for each request
* @example ({ parent }) => parent.child({ requestId: uuid() })
* */
childLoggerProvider?: ChildLoggerProvider;
/**
* @desc The function for producing access logs
* @default ({ method, path }, logger) => logger.debug(`${method}: ${path}`)
* @example null — disables the feature
* */
accessLogger?: null | LogAccess;
/**
* @desc You can disable the startup logo.
* @default true
*/
startupLogo?: boolean;
/**
* @desc Which properties of request are combined into the input for endpoints and middlewares.
* @desc The order matters: priority from lowest to highest
* @default defaultInputSources
* @see defaultInputSources
*/
inputSources?: Partial<InputSources>;
}
type BeforeUpload = (params: { request: Request; logger: ActualLogger }) => void | Promise<void>;
type UploadOptions = Pick<
fileUpload.Options,
| "createParentPath"
| "uriDecodeFileNames"
| "safeFileNames"
| "preserveExtension"
| "useTempFiles"
| "tempFileDir"
| "debug"
| "uploadTimeout"
| "limits"
> & {
/**
* @desc The error to throw when the file exceeds the configured fileSize limit (handled by errorHandler).
* @see limits
* @override limitHandler
* @example createHttpError(413, "The file is too large")
* */
limitError?: Error;
/**
* @desc A handler to execute before uploading — it can be used for restrictions by throwing an error.
* @example ({ request }) => { throw createHttpError(403, "Not authorized"); }
* */
beforeUpload?: BeforeUpload;
};
interface CookieParserOptions extends cookieParser.CookieParseOptions {
/** @desc The secret string or array used by cookie-parser for signed cookies */
secret?: Parameters<typeof cookieParser>[0];
}
type CompressionOptions = Pick<
compression.CompressionOptions,
"threshold" | "level" | "strategy" | "chunkSize" | "memLevel"
>;
interface GracefulOptions {
/**
* @desc Time given to drain ongoing requests before closing the server.
* @default 1000
* */
timeout?: number;
/**
* @desc Process event (Signal) that triggers the graceful shutdown.
* @see Signals
* @default [SIGINT, SIGTERM]
* */
events?: string[];
/** @desc The hook to call after the server was closed, but before terminating the process. */
beforeExit?: () => void | Promise<void>;
}
type ServerHook = (params: {
app: IRouter;
/** @desc Returns child logger for the given request (if configured) or the configured logger otherwise */
getLogger: GetLogger;
}) => void | Promise<void>;
interface HttpConfig {
/** @desc Port, UNIX socket or custom options. */
listen: number | string | ListenOptions;
}
interface HttpsConfig extends HttpConfig {
/** @desc At least "cert" and "key" options required. */
options: ServerOptions;
}
interface ServerConfig extends CommonConfig {
/** @desc HTTP server configuration. */
http?: HttpConfig;
/** @desc HTTPS server configuration. */
https?: HttpsConfig;
/**
* @desc Custom JSON parser.
* @default express.json()
* @link https://expressjs.com/en/5x/api.html#express.json
* */
jsonParser?: RequestHandler;
/**
* @desc Enable or configure uploads handling.
* @requires express-fileupload
* */
upload?: boolean | UploadOptions;
/**
* @desc Enable or configure response compression.
* @requires compression
*/
compression?: boolean | CompressionOptions;
/**
* @desc Enable cookie parsing via cookie-parser.
* @requires cookie-parser
* @example true
* @example { secret: "my-secret" }
*/
cookies?: boolean | CookieParserOptions;
/**
* @desc Configure or customize the parser for request query string
* @example "simple" // for "node:querystring" module, array elements must be repeated: ?a=1&a=2
* @example "extended" // for "qs" module, supports nested objects and arrays with brackets: ?a[]=1&a[]=2
* @example (query) => qs.parse(query, {comma: true}) // for comma-separated arrays: ?a=1,2,3
* @default "simple"
* @link https://expressjs.com/en/5x/api.html#req.query
*/
queryParser?: "simple" | "extended" | ((query: string) => object);
/**
* @desc Custom raw parser (assigns Buffer to request body)
* @default express.raw()
* @link https://expressjs.com/en/5x/api.html#express.raw
* */
rawParser?: RequestHandler;
/**
* @desc Custom parser for URL Encoded requests used for submitting HTML forms
* @default express.urlencoded()
* @link https://expressjs.com/en/5x/api.html#express.urlencoded
* */
formParser?: RequestHandler;
/**
* @desc A code to execute before processing the Routing of your API (and before parsing).
* @desc This can be a good place for express middlewares establishing their own routes.
* @desc It can help to avoid making a DIY solution based on the attachRouting() approach.
* @example ({ app }) => { app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); }
* */
beforeRouting?: ServerHook;
/**
* @desc A code to execute after processing the Routing of your API, but before error handling.
* @see beforeRouting
* */
afterRouting?: ServerHook;
/**
* @desc Rejects new connections and attempts to finish ongoing ones in the specified time before exit.
* */
gracefulShutdown?: boolean | GracefulOptions;
}
interface AppConfig extends CommonConfig {
/** @desc Your custom express app or express router instead. */
app: IRouter;
}
declare function createConfig(config: ServerConfig): ServerConfig;
declare function createConfig(config: AppConfig): AppConfig;
/**
* @desc Directives shared by both request and response Cache-Control headers.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#cache_directives
*/
interface CommonDirectives {
/**
* @desc Response: the response remains fresh for N seconds after it was generated.
* @desc Request: the client will accept a stored response that was generated at most N seconds ago.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#max-age
*/
maxAge?: number;
/**
* @desc Forces revalidation with the server before reuse.
* @desc In a response this tells caches to revalidate; in a request it asks caches to revalidate.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#no-cache
*/
noCache?: boolean;
/**
* @desc Prevents storing the response in any cache. In a response this instructs caches not to store.
* @desc In a request it asks caches not to store the request or its response.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#no-store
*/
noStore?: boolean;
/**
* @desc Prevents intermediaries from transforming the response body (e.g. converting images).
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#no-transform
*/
noTransform?: boolean;
/**
* @desc Allows a stale cached response to be reused for N seconds when the origin server returns an error.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#stale-if-error
*/
staleIfError?: number;
}
/**
* @desc Directives that clients send in requests to express their caching preferences.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#request_directives
*/
interface CacheControl extends CommonDirectives {
/**
* @desc The client will accept a stored response that is stale for up to N seconds beyond its freshness lifetime.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#max-stale
*/
maxStale?: number;
/**
* @desc The client requires a stored response that will remain fresh for at least N more seconds.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#min-fresh
*/
minFresh?: number;
/**
* @desc The client wants a response only from the cache. Throw createHttpError(504) in this case.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#only-if-cached
*/
onlyIfCached?: boolean;
}
/**
* @desc Directives that servers send in responses to control how caches store and reuse the response.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#response_directives
*/
interface CachePolicy extends CommonDirectives {
/**
* @desc Restricts which caches may store the response.
* @example "public" — any cache (browser, proxy, CDN); for static assets, responses without user-specific data.
* @example "private" — browser only; for user-specific and personalized content.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#response_directives
*/
scope?: "public" | "private";
/**
* @desc Overrides max-age for shared caches (proxies, CDNs). Ignored by private (browser) caches.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#s-maxage
*/
sMaxAge?: number;
/**
* @desc Forces all caches to revalidate stale responses with the origin server before reusing them.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#must-revalidate
*/
mustRevalidate?: boolean;
/**
* @desc Forces proxies and CDNs to revalidate stale responses with the origin server before reusing them.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#proxy-revalidate
*/
proxyRevalidate?: boolean;
/**
* @desc A cache must understand the caching requirements for the response's status code before storing it.
* @desc Pair with no-store as a fallback for caches that don't support it.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#must-understand
*/
mustUnderstand?: boolean;
/**
* @desc Indicates that the response body will never change while fresh.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#immutable
*/
immutable?: boolean;
/**
* @desc Allows a stale response to be served in the background while the cache revalidates it, for up to N seconds.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#stale-while-revalidate
*/
staleWhileRevalidate?: number;
}
/**
* @desc Creates a Middleware providing caching helpers.
* @param defaultPolicy — Optional default Cache-Control policy applied to all responses.
* @example createCacheMiddleware({ noCache: true, scope: "private" })
*/
declare const createCacheMiddleware: (defaultPolicy?: CachePolicy) => Middleware<
FlatObject,
{
/**
* @desc Provides the parsed If-None-Match request header into an array of ETags. Can also be '*' wildcard.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match
*/
readonly ifNoneMatch: string[] | "*" | undefined;
/**
* @desc Provides the parsed If-Modified-Since request header having the timestamp of the client's cached copy.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Modified-Since
*/
readonly ifModifiedSince: Date | undefined;
/**
* @desc Provides the parsed Cache-Control request header to reveal the client's caching intent.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control
*/
readonly cacheControl: CacheControl | undefined;
/**
* @desc Augments the Cache-Control response header, merging with the defaultPolicy if provided.
* @desc Pass `undefined` for a directive to unset the default value.
* @see defaultPolicy
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching
*/
addCachePolicy: (policy: CachePolicy) => void;
/**
* @desc Sets the ETag response header with a unique identifier for this version of the resource.
* @see ifNoneMatch
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag
*/
setETag: (value: string) => void;
/**
* @desc Sets the Last-Modified response header to the timestamp when the resource was last changed.
* @see ifModifiedSince
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Last-Modified
*/
setLastModified: (date: Date) => void;
/**
* @desc Sets the Vary response header to the list of request headers that influence the response.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Vary
*/
setVary: (...headers: string[]) => void;
/**
* @desc Sets the Expires response header with an explicit expiration date. Consider addCachePolicy({ maxAge }).
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Expires
*/
setExpires: (date: Date) => void;
/**
* @desc Sets the Clear-Site-Data response header with the "cache" directive to remove all cached responses.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Clear-Site-Data
*/
clearSiteData: () => void;
/**
* @desc Sends an HTTP 304 Not Modified empty response and ends the response stream.
* @example return ctx.notModified() as never; // to satisfy the handler's return type
* @see ifNoneMatch
* @see ifModifiedSince
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/304
*/
notModified: () => void;
},
string,
undefined
>;
/**
* @desc Creates a Middleware providing cookie-setting convenience methods.
* @param baseOptions — Default options applied to every setCookie / clearCookie call.
* @example createCookieMiddleware({ httpOnly: true, secure: true, path: "/" })
*/
declare const createCookieMiddleware: (baseOptions?: CookieOptions) => Middleware<
FlatObject,
{
/**
* @desc Reads a cookie value. Checks signedCookies first, then falls back to cookies.
* @requires cookie-parser
* @see ServerConfig.cookies
* */
getCookie: (name: string) => z.core.util.JSONType | undefined;
/** @desc Sets a cookie on the response. Express converts non-string values to JSON. */
setCookie: (name: string, value: string | z.core.util.JSONType, overrides?: CookieOptions) => void;
/** @desc Clears a cookie on the response. */
clearCookie: (name: string, overrides?: Omit<CookieOptions, "expires" | "maxAge">) => void;
},
string,
undefined
>;
interface BuildProps<
IN extends IOSchema,
OUT extends IOSchema | z.ZodVoid,
MIN extends IOSchema | undefined,
CTX extends FlatObject,
SCO extends string,
> {
/**
* @desc Input schema of the Endpoint, combining properties from all the enabled input sources (path params, headers)
* @default z.object({})
* @see defaultInputSources
* */
input?: IN;
/** @desc The schema by which the returns of the Endpoint handler is validated */
output: OUT;
/** @desc The Endpoint handler receiving the validated inputs, returns of added Middlewares (ctx) and a logger */
handler: Handler<z.output<FinalInputSchema<MIN, IN>>, z.input<OUT>, CTX>;
/** @desc The operation description for the generated Documentation (may use Markdown) */
description?: string;
/** @desc The operation summary for the generated Documentation (short plain string) */
summary?: string;
/** @desc The operation ID for the generated Documentation (must be unique) */
operationId?: string | ((method: ClientMethod) => string);
/**
* @desc HTTP method(s) this endpoint can handle
* @default "get" unless method is explicitly defined in Routing keys
* */
method?: Method | [Method, ...Method[]];
/**
* @desc Scope(s) from the list of the ones defined by the added Middlewares having "oauth2" security type
* @see OAuth2Security
* */
scope?: SCO | SCO[];
/**
* @desc Tag(s) for generating Documentation. For establishing constraints:
* @see TagOverrides
* */
tag?: Tag | Tag[];
/** @desc Marks the operation deprecated in the generated Documentation */
deprecated?: boolean;
}
/**
* @desc Creates a factory for building Endpoints. It can be extended by adding Middlewares that enrich the context
* available to the Endpoint handler. It requires a ResultHandler to respond consistently.
* @see Middleware
* @see ResultHandler
* */
declare class EndpointsFactory<
IN extends IOSchema | undefined = undefined,
CTX extends FlatObject = EmptyObject,
SCO extends string = string,
> {
protected resultHandler: AbstractResultHandler;
protected schema: IN;
protected middlewares: AbstractMiddleware[];
/**
* @param resultHandler An instance of ResultHandler for handling both Endpoint outputs and all possible errors.
* @see ResultHandler
* */
constructor(resultHandler: AbstractResultHandler);
/**
* @desc Attaches a Middleware to the factory, extending the context available to Endpoints built on it.
* Accepts either a Middleware instance or a plain object compatible with the Middleware constructor.
* @see Middleware
* */
addMiddleware<RET extends FlatObject, ASCO extends string, AIN extends IOSchema | undefined = undefined>(
subject: Middleware<CTX, RET, ASCO, AIN> | ConstructorParameters<typeof Middleware<CTX, RET, ASCO, AIN>>[0],
): EndpointsFactory<Extension<IN, AIN>, (CTX extends Record<string, never> ? RET : CTX) & RET, SCO & ASCO>;
/** @desc Shorthand for .addMiddleware(createCookieMiddleware()) */
useCookies(...args: Parameters<typeof createCookieMiddleware>): EndpointsFactory<
Extension<IN, undefined>,
(CTX extends Record<string, never>
? {
getCookie: (name: string) => z.core.util.JSONType | undefined;
setCookie: (name: string, value: string | z.core.util.JSONType, overrides?: _$express.CookieOptions) => void;
clearCookie: (name: string, overrides?: Omit<_$express.CookieOptions, "expires" | "maxAge">) => void;
}
: CTX) & {
getCookie: (name: string) => z.core.util.JSONType | undefined;
setCookie: (name: string, value: string | z.core.util.JSONType, overrides?: _$express.CookieOptions) => void;
clearCookie: (name: string, overrides?: Omit<_$express.CookieOptions, "expires" | "maxAge">) => void;
},
SCO
>;
/** @desc Shorthand for .addMiddleware(createCacheMiddleware()) */
useCache(...args: Parameters<typeof createCacheMiddleware>): EndpointsFactory<
Extension<IN, undefined>,
(CTX extends Record<string, never>
? {
readonly ifNoneMatch: string[] | "*" | undefined;
readonly ifModifiedSince: Date | undefined;
readonly cacheControl: CacheControl | undefined;
addCachePolicy: (policy: CachePolicy) => void;
setETag: (value: string) => void;
setLastModified: (date: Date) => void;
setVary: (...headers: string[]) => void;
setExpires: (date: Date) => void;
clearSiteData: () => void;
notModified: () => void;
}
: CTX) & {
readonly ifNoneMatch: string[] | "*" | undefined;
readonly ifModifiedSince: Date | undefined;
readonly cacheControl: CacheControl | undefined;
addCachePolicy: (policy: CachePolicy) => void;
setETag: (value: string) => void;
setLastModified: (date: Date) => void;
setVary: (...headers: string[]) => void;
setExpires: (date: Date) => void;
clearSiteData: () => void;
notModified: () => void;
},
SCO
>;
/**
* @desc Shorthand for addExpressMiddleware(). Use it for wrapping native Express middlewares.
* @see addExpressMiddleware
* */
use: <R extends Request, S extends Response, AOUT extends FlatObject = Record<string, never>>(
nativeMw: (request: R, response: S, next: _$express.NextFunction) => any,
params_1?:
| {
provider?: ((request: R, response: S) => AOUT | Promise<AOUT>) | undefined;
transformer?: (err: Error) => Error;
}
| undefined,
) => EndpointsFactory<Extension<IN, undefined>, (CTX extends Record<string, never> ? AOUT : CTX) & AOUT, SCO>;
/**
* @desc Wraps a native Express middleware and attaches it to the factory as a Middleware. Optionally, a `provider`
* can extract context properties from the request and response, and a `transformer` can convert errors.
* @see ExpressMiddleware
* */
addExpressMiddleware<R extends Request, S extends Response, AOUT extends FlatObject = EmptyObject>(
...params: ConstructorParameters<typeof ExpressMiddleware<R, S, AOUT>>
): EndpointsFactory<Extension<IN, undefined>, (CTX extends Record<string, never> ? AOUT : CTX) & AOUT, SCO>;
/**
* @desc Extends the context available to Endpoints built on this factory by resolving additional properties
* from an asynchronous callback. The callback receives the current accumulated context, allowing further
* context values to depend on previously provided ones. This is a shorthand for addMiddleware() with no schema.
* @see addMiddleware
* */
addContext<RET extends FlatObject>(
provider: (current: CTX) => Promise<RET>,
): EndpointsFactory<Extension<IN, undefined>, (CTX extends Record<string, never> ? RET : CTX) & RET, SCO>;
/**
* @desc Builds an Endpoint using the accumulated Middlewares, the ResultHandler, and the given configuration.
* The output is validated against the output schema; the handler receives the validated input and context.
* @see Endpoint
* */
build<BOUT extends IOSchema, BIN extends IOSchema = EmptySchema>({
input,
output: outputSchema,
operationId,
scope,
tag,
method,
...rest
}: BuildProps<BIN, BOUT, IN, CTX, SCO>): Endpoint<FinalInputSchema<IN, BIN>, BOUT, CTX>;
/**
* @desc shorthand for build() having output schema assigned with an empty object
* @see build
* */
buildVoid<BIN extends IOSchema = EmptySchema>({
handler,
...rest
}: Omit<BuildProps<BIN, z.ZodVoid, IN, CTX, SCO>, "output">): Endpoint<
FinalInputSchema<IN, BIN>,
z.ZodObject<{}, z.core.$strip>,
CTX
>;
}
/**
* @desc The factory based on the default ResultHandler: suitable for JSON responses.
* @see defaultResultHandler
* */
declare const defaultEndpointsFactory: EndpointsFactory<undefined, Record<string, never>, string>;
/**
* @deprecated Resist the urge of using it: this factory is designed only to simplify the migration of legacy APIs.
* @desc Responding with an array is a bad practice keeping your endpoints from evolving without breaking changes.
* @desc The result handler of this factory expects your endpoint to have the property 'items' in the output schema
*/
declare const arrayEndpointsFactory: EndpointsFactory<undefined, Record<string, never>, string>;
declare const attachRouting: (
config: AppConfig,
routing: Routing,
) => {
notFoundHandler: express.RequestHandler<
_$express_serve_static_core0.ParamsDictionary,
any,
any,
_$qs.ParsedQs,
Record<string, any>
>;
logger: AbstractLogger | BuiltinLogger;
};
declare const createServer: (
config: ServerConfig,
routing: Routing,
) => Promise<{
app: _$express_serve_static_core0.Express;
logger: AbstractLogger | BuiltinLogger;
servers: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>[];
}>;
interface ReqResCommons {
makeRef: (key: object | string, value: SchemaObject | ReferenceObject, proposedName?: string) => ReferenceObject;
path: string;
method: ClientMethod;
}
interface OpenAPIContext extends ReqResCommons {
isResponse: boolean;
}
type Depicter = (
zodCtx: {
zodSchema: z.core.$ZodType;
jsonSchema: z.core.JSONSchema.BaseSchema;
},
oasCtx: OpenAPIContext,
) => z.core.JSONSchema.BaseSchema | SchemaObject;
/** @desc Using defaultIsHeader when returns null or undefined */
type IsHeader = (name: string, method: ClientMethod, path: string) => boolean | null | undefined;
type BrandHandling = Record<string | symbol, Depicter>;
declare const depictTags: (
tags: Partial<
Record<
Tag,
| string
| {
description: string;
url?: string;
}
>
>,
) => TagObject[];
/** @desc Ensures the summary string does not exceed the limit */
declare const trimSummary: (summary?: string, limit?: number) => string | undefined;
type Component = "positiveResponse" | "negativeResponse" | "requestParameter" | "requestBody";
/** @desc user defined function that creates a component description from its properties */
type Descriptor = (
props: Record<"method" | "path" | "operationId", string> & {
statusCode?: number;
},
) => string;
type Summarizer = (params: { summary?: string; description?: string; trim: typeof trimSummary }) => string | undefined;
interface DocumentationParams {
title: string;
version: string;
serverUrl: string | [string, ...string[]];
routing: Routing;
config: CommonConfig;
/**
* @desc Descriptions of various components based on their properties (method, path, operationId).
* @desc When composition set to "components", component name is generated from this description
* @default () => `${method} ${path} ${component}`
* */
descriptions?: Partial<Record<Component, Descriptor>>;
/**
* @desc The function that ensures the maximum length for summary fields. Can optionally make them from descriptions.
* @see defaultSummarizer
* @see trimSummary
* */
summarizer?: Summarizer;
/**
* @desc Depict the HEAD method for each Endpoint supporting the GET method (feature of Express)
* @default true
* */
hasHeadMethod?: boolean;
/** @default inline */
composition?: "inline" | "components";
/**
* @desc Handling rules for your own schemas branded with `x-brand` metadata.
* @desc Keys: brands (recommended to use unique symbols).
* @desc Values: functions having Zod context as first argument, second one is the framework context.
* @example { MyBrand: ({ zodSchema, jsonSchema }) => ({ type: "object" })
* @link https://www.npmjs.com/package/@express-zod-api/zod-plugin
*/
brandHandling?: BrandHandling;
/**
* @desc Ability to configure recognition of headers among other input data
* @desc Only applicable when "headers" is present within inputSources config option
* @see defaultIsHeader
* @link https://www.iana.org/assignments/http-fields/http-fields.xhtml
* */
isHeader?: IsHeader;
/**
* @desc Extended description of tags used in endpoints. For enforcing constraints:
* @see TagOverrides
* @example { users: "About users", files: { description: "About files", url: "https://example.com" } }
* */
tags?: Parameters<typeof depictTags>[0];
}
declare class Documentation extends OpenApiBuilder {
constructor({
routing,
config,
title,
version,
serverUrl,
descriptions,
brandHandling,
tags,
isHeader,
hasHeadMethod,
summarizer,
composition,
}: DocumentationParams);
}
/** @desc An error related to the wrong Routing declaration */
declare class RoutingError extends Error {
name: string;
readonly cause: {
method: Method;
path: string;
};
constructor(message: string, method: Method, path: string);
}
/**
* @desc An error related to the generating of the documentation
* */
declare class DocumentationError extends Error {
name: string;
readonly cause: string;
constructor(message: string, { method, path, isResponse }: Pick<OpenAPIContext, "path" | "method" | "isResponse">);
}
/** @desc An error related to the input and output schemas declaration */
declare class IOSchemaError extends Error {
name: string;
}
/** @desc An error of validating the Endpoint handler's returns against the Endpoint output schema */
declare class OutputValidationError extends IOSchemaError {
readonly cause: z.ZodError;
name: string;
constructor(cause: z.ZodError);
}
/** @desc An error of validating the input sources against the Middleware or Endpoint input schema */
declare class InputValidationError extends IOSchemaError {
readonly cause: z.ZodError;
name: string;
constructor(cause: z.ZodError);
}
declare class MissingPeerError extends Error {
name: string;
constructor(module: string);
}
interface TestingProps<REQ, LOG> {
/**
* @desc Additional properties to set on Request mock
* @default { method: "GET", headers: { "content-type": "application/json" } }
* */
requestProps?: REQ;
/**
* @link https://www.npmjs.com/package/node-mocks-http
* @default { req: requestMock }
* */
responseOptions?: ResponseOptions;
/**
* @desc Additional properties to set on config mock
* @default { cors: false, logger }
* */
configProps?: Partial<CommonConfig>;
/**
* @desc Additional properties to set on logger mock
* @default { info, warn, error, debug }
* */
loggerProps?: LOG;
}
declare const testEndpoint: <LOG extends FlatObject, REQ extends RequestOptions>({
endpoint,
...rest
}: TestingProps<REQ, LOG> & {
/** @desc The endpoint to test */
endpoint: AbstractEndpoint;
}) => Promise<{
requestMock: _$node_mocks_http0.MockRequest<
Request<_$express_serve_static_core0.ParamsDictionary, any, any, _$qs.ParsedQs, Record<string, any>> & REQ
>;
responseMock: _$node_mocks_http0.MockResponse<Response<any, Record<string, any>>>;
loggerMock: AbstractLogger &
LOG & {
_getLogs: () => Record<"error" | "debug" | "info" | "warn", unknown[]>;
};
}>;
interface MiddlewareLike<RET extends FlatObject> {
execute(...params: Parameters<AbstractMiddleware["execute"]>): Promise<RET>;
}
declare const testMiddleware: <LOG extends FlatObject, REQ extends RequestOptions, RET extends FlatObject>({
middleware,
ctx,
...rest
}: TestingProps<REQ, LOG> & {
/** @desc The middleware to test */
middleware: MiddlewareLike<RET>;
/** @desc The aggregated returns of previously executed middlewares */
ctx?: FlatObje