Skip to content

devscast/zod-openapi

Repository files navigation

zod-openapi : Decorator-first OpenAPI generation for TypeScript controllers using Zod v4 schemas.

npm npm Lint Tests GitHub

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 z with .openapi(...) already enabled

Installation

bun add @devscast/zod-openapi zod

If 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",
    },
  },
});

Contributors

contributors

About

Decorator-first OpenAPI generation for TypeScript controllers using Zod v4 schemas.

Resources

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors