Express Zod API generates OpenAPI 3.1 (formerly Swagger) compliant documentation, the industry standard for describing RESTful APIs.
What is OpenAPI 3.1?
OpenAPI 3.1 is a specification for describing HTTP APIs in a machine-readable format. It provides:
Standardized API documentation
Interactive API explorers (like Swagger UI)
Code generation for clients and servers
API testing and validation tools
JSON Schema compatibility
OpenAPI 3.1 fully embraces JSON Schema, making it more powerful than previous versions.
Key Features
Express Zod API’s OpenAPI generator provides:
Automatic Generation Documentation is generated directly from your Zod schemas and endpoint definitions
Type Safety Your TypeScript types and OpenAPI schemas are always in sync
JSON Schema Full JSON Schema compatibility for maximum interoperability
Rich Metadata Support for examples, descriptions, deprecation, and more
Basic Usage
Generate OpenAPI documentation:
import { Documentation } from "express-zod-api" ;
import { routing } from "./routing" ;
import { config } from "./config" ;
const openapi = new Documentation ({
routing ,
config ,
version: "1.0.0" ,
title: "My API" ,
serverUrl: "https://api.example.com" ,
});
// Get as YAML string
const yaml = openapi . getSpecAsYaml ();
// Or access the spec object directly
const spec = openapi . rootDoc ;
Schema Mapping
Here’s how Zod schemas map to OpenAPI types:
Zod Schema OpenAPI Type Notes z.string()type: stringz.number()type: numberz.boolean()type: booleanz.object()type: objectWith properties z.array()type: arrayWith items z.enum()enum: [...]Enumeration values z.literal()const: valueSingle value z.union()anyOf: [...]Multiple schemas z.intersection()allOf: [...]Combined schemas z.optional()No required Property is optional z.nullable()type: ["...", "null"]OpenAPI 3.1 style ez.dateIn()type: string, format: date-timeISO 8601 string ez.dateOut()type: string, format: date-timeISO 8601 string ez.upload()type: string, format: binaryFile upload
Security Schemes
Express Zod API automatically generates security schemes from your middleware definitions:
import { Middleware } from "express-zod-api" ;
import { z } from "zod" ;
const authMiddleware = new Middleware ({
security: {
// Generates API key security scheme
and: [
{ type: "input" , name: "apiKey" },
{ type: "header" , name: "X-API-Token" },
],
},
input: z . object ({
apiKey: z . string (). min ( 1 ),
}),
handler : async ({ input , request }) => {
// Validate authentication
const token = request . headers [ "x-api-token" ];
// ...
return { userId: "123" };
},
});
This generates:
securitySchemes :
INPUT_1 :
type : apiKey
in : query
name : apiKey
HEADER_1 :
type : apiKey
in : header
name : X-API-Token
Security Types
Supported security scheme types:
API key in query parameter or request body
API key in request header
name: Header name (case-insensitive)
HTTP authentication (Basic, Bearer, etc.)
scheme: Authentication scheme (e.g., “bearer”, “basic”)
OAuth 2.0 authentication
flows: OAuth flows configuration
Request and Response Examples
Add examples to your schemas for better documentation:
import { z } from "zod" ;
import { defaultEndpointsFactory } from "express-zod-api" ;
const createUserEndpoint = defaultEndpointsFactory . build ({
method: "post" ,
input: z . object ({
name: z . string ()
. min ( 1 )
. example ( "John Doe" )
. describe ( "User's full name" ),
email: z . string ()
. email ()
. example ( "john@example.com" )
. describe ( "User's email address" ),
age: z . number ()
. int ()
. positive ()
. optional ()
. example ( 30 )
. describe ( "User's age in years" ),
}),
output: z . object ({
id: z . string (). example ( "user_123" ),
name: z . string (). example ( "John Doe" ),
email: z . string (). email (). example ( "john@example.com" ),
createdAt: z . string (). example ( "2024-01-01T00:00:00Z" ),
}),
handler : async ({ input }) => {
// Implementation
return {
id: "user_123" ,
name: input . name ,
email: input . email ,
createdAt: new Date (). toISOString (),
};
},
});
Generates:
requestBody :
content :
application/json :
schema :
type : object
properties :
name :
type : string
description : User's full name
example : John Doe
email :
type : string
format : email
description : User's email address
example : john@example.com
Path Parameters
Path parameters are automatically detected and documented:
import { Routing } from "express-zod-api" ;
import { z } from "zod" ;
const getUserEndpoint = defaultEndpointsFactory . build ({
input: z . object ({
userId: z . string ()
. describe ( "The unique user identifier" ),
}),
// ...
});
const routing : Routing = {
user: {
":userId" : getUserEndpoint ,
},
};
Generates:
paths :
/user/{userId} :
get :
parameters :
- name : userId
in : path
required : true
description : The unique user identifier
schema :
type : string
Response Status Codes
Document different status codes using custom result handlers:
import { ResultHandler } from "express-zod-api" ;
import { z } from "zod" ;
const customResultHandler = new ResultHandler ({
positive : ( data ) => ({
statusCode: [ 200 , 201 ], // OK or Created
schema: z . object ({
status: z . literal ( "success" ),
data
}),
}),
negative: [
{
statusCode: 400 , // Bad Request
schema: z . object ({
status: z . literal ( "error" ),
message: z . string ()
}),
},
{
statusCode: 401 , // Unauthorized
schema: z . object ({
status: z . literal ( "unauthorized" )
}),
},
{
statusCode: 500 , // Internal Server Error
schema: z . object ({
status: z . literal ( "error" ),
message: z . string ()
}),
},
],
handler : ({ error , response , output }) => {
// Implementation
},
});
Content Types
Express Zod API supports various content types:
JSON (Default)
// application/json - default for most endpoints
const endpoint = defaultEndpointsFactory . build ({
input: z . object ({ name: z . string () }),
// ...
});
import { ez } from "express-zod-api" ;
// application/x-www-form-urlencoded
const formEndpoint = defaultEndpointsFactory . build ({
method: "post" ,
input: ez . form ({
name: z . string (),
email: z . string (). email (),
}),
// ...
});
File Uploads
import { ez } from "express-zod-api" ;
// multipart/form-data
const uploadEndpoint = defaultEndpointsFactory . build ({
method: "post" ,
input: z . object ({
avatar: ez . upload (),
}),
// ...
});
Raw Data
import { ez } from "express-zod-api" ;
// application/octet-stream
const rawEndpoint = defaultEndpointsFactory . build ({
method: "post" ,
input: ez . raw ({}),
// ...
});
Using Generated Documentation
With Swagger UI
import express from "express" ;
import swaggerUi from "swagger-ui-express" ;
import YAML from "yaml" ;
import { readFileSync } from "fs" ;
import { createConfig } from "express-zod-api" ;
const spec = YAML . parse (
readFileSync ( "openapi.yaml" , "utf-8" )
);
const config = createConfig ({
beforeRouting : ({ app }) => {
app . use (
"/docs" ,
swaggerUi . serve ,
swaggerUi . setup ( spec , {
explorer: true ,
customCss: '.swagger-ui .topbar { display: none }' ,
})
);
},
// ... other config
});
With Redoc
import { createConfig } from "express-zod-api" ;
import { readFileSync } from "fs" ;
const config = createConfig ({
beforeRouting : ({ app }) => {
const spec = readFileSync ( "openapi.yaml" , "utf-8" );
app . get ( "/docs" , ( req , res ) => {
res . send ( `
<!DOCTYPE html>
<html>
<head>
<title>API Documentation</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
</head>
<body>
<redoc spec-url="/openapi.yaml"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>
` );
});
app . get ( "/openapi.yaml" , ( req , res ) => {
res . type ( "text/yaml" ). send ( spec );
});
},
});
With API Clients
Generate clients using OpenAPI Generator:
# Generate TypeScript Axios client
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-axios \
-o ./generated-client
# Generate Python client
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g python \
-o ./python-client
While OpenAPI Generator can create clients, Express Zod API’s type-safe client generator provides superior TypeScript integration.
Best Practices
1. Use Descriptive Names
// Good
const getUserByIdEndpoint = factory . build ({ /* ... */ });
// Less good
const endpoint1 = factory . build ({ /* ... */ });
2. Provide Examples
z . string (). example ( "example-value" )
. describe ( "Clear description of what this is" )
3. Tag Your Endpoints
factory . build ({
tag: "users" ,
// ...
});
4. Add Deprecation Warnings
factory . build ({
deprecated: true , // Marks endpoint as deprecated
input: z . object ({
oldField: z . string (). deprecated (), // Marks field as deprecated
}),
});
5. Use Component Mode for Large APIs
new Documentation ({
composition: "components" , // Reduces duplication
// ...
});
Validation
Validate your generated OpenAPI spec:
# Using swagger-cli
npm install -g @apidevtools/swagger-cli
swagger-cli validate openapi.yaml
# Using openapi-cli
npm install -g @redocly/cli
openapi lint openapi.yaml
Next Steps