@devscast/zod-openapi
v1.0.0
Published
  [](
Readme
zod-openapi : Decorator-first OpenAPI generation for TypeScript controllers using Zod v4 schemas.
This package is designed for legacy or incremental migrations where documentation should stay as metadata on controller methods instead of becoming application middleware. It uses @asteasolutions/zod-to-openapi under the hood and keeps the authoring experience centered on a single @openapi(...) decorator.
Features
- Zod v4+ only
@openapi(...)method decorator- Request body shorthand for the common JSON case
- OpenAPI 3.0 and 3.1 document generation
- Re-exports
zwith.openapi(...)already enabled
Installation
bun add @devscast/zod-openapi zodIf your project uses legacy decorators, enable them in tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true
}
}Quick Start
import { generateOpenApiDocument, openapi, z } from "@devscast/zod-openapi";
const UserParamsSchema = z.object({
user_id: z.string().min(1),
});
const PermissionsSchema = z
.object({
permissions: z.array(z.string()),
})
.openapi("Permissions");
class UsersController {
@openapi({
method: "put",
path: "/api/users/:user_id/permissions",
tags: ["Users"],
summary: "Update User Permissions",
description: "Update permissions for a specific user by their ID.",
request: {
params: UserParamsSchema,
body: PermissionsSchema,
},
responses: {
200: {
description: "Updated permissions",
content: {
"application/json": {
schema: z.object({
id: UserParamsSchema.shape.user_id,
}),
},
},
},
},
})
updatePermissions() {
return null;
}
}
const document = generateOpenApiDocument({
controllers: [UsersController],
document: {
openapi: "3.0.0",
info: {
title: "Example API",
version: "1.0.0",
},
},
});document.paths will contain /api/users/{user_id}/permissions even though the decorator used the Express-style :user_id path.
Generating a Registry First
If you want to register extra components or mix manual routes with decorated ones, build a registry explicitly:
import {
OpenApiGeneratorV3,
createOpenApiRegistry,
} from "@devscast/zod-openapi";
const registry = createOpenApiRegistry({
controllers: [UsersController],
routes: [
{
method: "get",
path: "/health",
tags: ["System"],
summary: "Health check",
responses: {
200: {
description: "OK",
},
},
},
],
register(registry) {
registry.registerComponent("securitySchemes", "bearerAuth", {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
});
},
});
const document = new OpenApiGeneratorV3(registry.definitions).generateDocument({
openapi: "3.0.0",
info: {
title: "Example API",
version: "1.0.0",
},
});OpenAPI 3.1
Use generateOpenApi31Document(...) when you want a 3.1 document:
import { generateOpenApi31Document } from "@devscast/zod-openapi";
const document = generateOpenApi31Document({
controllers: [UsersController],
document: {
openapi: "3.1.0",
info: {
title: "Example API",
version: "1.0.0",
},
},
});