An open API service indexing awesome lists of open source software.

https://github.com/tazo90/next-openapi-gen

Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for Zod schemas and TypeScript types.
https://github.com/tazo90/next-openapi-gen

api api-docs docs drizzle nextjs openapi reactjs redoc scalar swagger ts typescript zod

Last synced: 22 days ago
JSON representation

Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for Zod schemas and TypeScript types.

Awesome Lists containing this project

README

          

# next-openapi-gen

Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for Zod schemas and TypeScript types.

## Features

- ✅ Automatic OpenAPI 3.0 documentation generation from Next.js App Router and Pages Router 🆕
- ✅ Multiple schema types: `TypeScript`, `Zod`, `Drizzle-Zod`, or `custom YAML/JSON` files
- ✅ Mix schema sources simultaneously - perfect for gradual migrations
- ✅ JSDoc comments with intelligent parameter examples
- ✅ Multiple UI interfaces: `Scalar`, `Swagger`, `Redoc`, `Stoplight`, and `RapiDoc` available at `/api-docs` url
- ✅ Auto-detection of path parameters (e.g., `/users/[id]/route.ts`)
- ✅ Intuitive CLI for quick setup and generation

## Supported interfaces

- Scalar 💡(default)
- Swagger
- Redoc
- Stoplight Elements
- RapiDoc

## Installation

```bash
npm install next-openapi-gen --save-dev
```

## Quick Start

```bash
# Initialize OpenAPI configuration
npx next-openapi-gen init

# Generate OpenAPI documentation
npx next-openapi-gen generate
```

> [!TIP]
> Scalar UI and Zod are set by default

### Init Command Options

| Option | Choices | Default | Description |
|--------|---------|---------|-------------|
| `--ui` | `scalar`, `swagger`, `redoc`, `stoplight`, `rapidoc`, `none` | `scalar` | UI framework for API docs |
| `--schema` | `zod`, `typescript` | `zod` | Schema validation tool |
| `--docs-url` | any string | `api-docs` | URL path for documentation page |
| `--output` | any path | `next.openapi.json` | Output file for OpenAPI template |

> [!TIP]
> Use `--ui none` to skip UI setup and only generate the OpenAPI specification file.

## Configuration

During initialization (`npx next-openapi-gen init`), a configuration file `next.openapi.json` will be created in the project's root directory:

```json
{
"openapi": "3.0.0",
"info": {
"title": "Next.js API",
"version": "1.0.0",
"description": "API generated by next-openapi-gen"
},
"servers": [
{
"url": "http://localhost:3000",
"description": "Local server"
}
],
"apiDir": "src/app/api", // or "pages/api" for Pages Router
"routerType": "app", // "app" (default) or "pages" for legacy Pages Router
"schemaDir": "src/types", // or ["src/types", "src/schemas"] for multiple directories
"schemaType": "zod", // or "typescript", or ["zod", "typescript"] for multiple
"schemaFiles": [], // Optional: ["./schemas/models.yaml", "./schemas/api.json"]
"outputFile": "openapi.json",
"outputDir": "./public",
"docsUrl": "/api-docs",
"includeOpenApiRoutes": false,
"ignoreRoutes": [],
"debug": false
}
```

### Configuration Options

| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------- |
| `apiDir` | Path to the API directory |
| `routerType` | Router type: `"app"` (default) or `"pages"` for legacy Pages Router |
| `schemaDir` | Path to types/schemas directory, or array of paths for multiple directories |
| `schemaType` | Schema type: `"zod"`, `"typescript"`, or `["zod", "typescript"]` for multiple |
| `schemaFiles` | Optional: Array of custom OpenAPI schema files (YAML/JSON) to include |
| `outputFile` | Name of the OpenAPI output file |
| `outputDir` | Directory where OpenAPI file will be generated (default: `"./public"`) |
| `docsUrl` | API documentation URL (for Swagger UI) |
| `includeOpenApiRoutes` | Whether to include only routes with @openapi tag |
| `ignoreRoutes` | Array of route patterns to exclude from documentation (supports wildcards) |
| `defaultResponseSet` | Default error response set for all endpoints |
| `responseSets` | Named sets of error response codes |
| `errorConfig` | Error schema configuration |
| `debug` | Enable detailed logging during generation |

## Documenting Your API

### With Zod Schemas

```typescript
// src/app/api/products/[id]/route.ts

import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";

export const ProductParams = z.object({
id: z.string().describe("Product ID"),
});

export const ProductResponse = z.object({
id: z.string().describe("Product ID"),
name: z.string().describe("Product name"),
price: z.number().positive().describe("Product price"),
});

/**
* Get product information
* @description Fetches detailed product information by ID
* @pathParams ProductParams
* @response ProductResponse
* @openapi
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
// Implementation...
}
```

### With TypeScript Types

```typescript
// src/app/api/users/[id]/route.ts

import { NextRequest, NextResponse } from "next/server";

type UserParams = {
id: string; // User ID
};

type UserResponse = {
id: string; // User ID
name: string; // Full name
email: string; // Email address
};

/**
* Get user information
* @description Fetches detailed user information by ID
* @pathParams UserParams
* @response UserResponse
* @openapi
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
// Implementation...
}
```

### With Drizzle-Zod

```typescript
// src/db/schema.ts - Define your Drizzle table
import { pgTable, serial, varchar, text } from "drizzle-orm/pg-core";

export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: varchar("title", { length: 255 }).notNull(),
content: text("content").notNull(),
});

// src/schemas/post.ts - Generate Zod schema with drizzle-zod
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { posts } from "@/db/schema";

export const CreatePostSchema = createInsertSchema(posts, {
title: (schema) => schema.title.min(5).max(255).describe("Post title"),
content: (schema) => schema.content.min(10).describe("Post content"),
});

export const PostResponseSchema = createSelectSchema(posts);

// src/app/api/posts/route.ts - Use in your API route
/**
* Create a new post
* @description Create a new blog post with Drizzle-Zod validation
* @body CreatePostSchema
* @response 201:PostResponseSchema
* @openapi
*/
export async function POST(request: NextRequest) {
const body = await request.json();
const validated = CreatePostSchema.parse(body);
// Implementation...
}
```

## JSDoc Documentation Tags

| Tag | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `@description` | Endpoint description |
| `@operationId` | Custom operation ID (overrides auto-generated ID) |
| `@pathParams` | Path parameters type/schema |
| `@params` | Query parameters type/schema (use `@queryParams` if you have prettier-plugin-jsdoc conflicts) |
| `@body` | Request body type/schema |
| `@bodyDescription` | Request body description |
| `@response` | Response type/schema with optional code and description (`User`, `201:User`, `User:Description`, `201:User:Description`) |
| `@responseDescription` | Response description |
| `@responseSet` | Override default response set (`public`, `auth`, `none`) |
| `@add` | Add custom response codes (`409:ConflictResponse`, `429`) |
| `@contentType` | Request body content type (`application/json`, `multipart/form-data`) |
| `@auth` | Authorization type (`bearer`, `basic`, `apikey`) |
| `@tag` | Custom tag |
| `@deprecated` | Marks the route as deprecated |
| `@openapi` | Marks the route for inclusion in documentation (if includeOpenApiRoutes is enabled) |
| `@ignore` | Excludes the route from OpenAPI documentation |
| `@method` | HTTP method for Pages Router (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) - required for Pages Router only |

## Pages Router Support 🆕

The library now supports the legacy Next.js Pages Router. To use it:

1. Set `routerType` to `"pages"` in your configuration
2. Use the `@method` JSDoc tag to specify HTTP methods

See **[next15-pages-router](./examples/next15-pages-router)** for a complete working example.

## CLI Usage

### 1. Initialization

```bash
npx next-openapi-gen init
```

This command will generate following elements:

- Generate `next.openapi.json` configuration file
- Set up `Scalar` UI for documentation display
- Add `/api-docs` page to display OpenAPI documentation
- Configure `zod` as the default schema tool

### 2. Generate Documentation

```bash
npx next-openapi-gen generate
```

This command will generate OpenAPI documentation based on your API code:

- Scan API directories for routes
- Analyze types/schemas
- Generate OpenAPI file (`openapi.json`) in specified output directory (default: `public` folder)
- Create Scalar/Swagger UI endpoint and page (if enabled)

### 3. View API Documentation

To see API documenation go to `http://localhost:3000/api-docs`

## Examples

### Path Parameters

```typescript
// src/app/api/users/[id]/route.ts

// Zod
const UserParams = z.object({
id: z.string().describe("User ID"),
});

// Or TypeScript
type UserParams = {
id: string; // User ID
};

/**
* @pathParams UserParams
*/
export async function GET() {
// ...
}
```

### Query Parameters

```typescript
// src/app/api/users/route.ts

// Zod
const UsersQueryParams = z.object({
page: z.number().optional().describe("Page number"),
limit: z.number().optional().describe("Results per page"),
search: z.string().optional().describe("Search phrase"),
});

// Or TypeScript
type UsersQueryParams = {
page?: number; // Page number
limit?: number; // Results per page
search?: string; // Search phrase
};

/**
* @params UsersQueryParams
*/
export async function GET() {
// ...
}
```

### Request Body

```typescript
// src/app/api/users/route.ts

// Zod
const CreateUserBody = z.object({
name: z.string().describe("Full name"),
email: z.string().email().describe("Email address"),
password: z.string().min(8).describe("Password"),
});

// Or TypeScript
type CreateUserBody = {
name: string; // Full name
email: string; // Email address
password: string; // Password
};

/**
* @body CreateUserBody
* @bodyDescription User registration data including email and password
*/
export async function POST() {
// ...
}
```

### Response

```typescript
// src/app/api/users/route.ts

// Zod
const UserResponse = z.object({
id: z.string().describe("User ID"),
name: z.string().describe("Full name"),
email: z.string().email().describe("Email address"),
createdAt: z.date().describe("Creation date"),
});

// Or TypeScript
type UserResponse = {
id: string; // User ID
name: string; // Full name
email: string; // Email address
createdAt: Date; // Creation date
};

/**
* @response UserResponse
* @responseDescription Returns newly created user object
*/
export async function GET() {
// ...
}

// Alternative formats with inline description
/**
* @response UserResponse:Returns user profile data
*/
export async function GET() {
// ...
}

/**
* @response 201:UserResponse:Returns newly created user
*/
export async function POST() {
// ...
}

/**
* @response 204:Empty:User successfully deleted
*/
export async function DELETE() {
// ...
}
```

### Authorization

```typescript
// src/app/api/protected/route.ts

/**
* @auth bearer
*/
export async function GET() {
// ...
}
```

### Deprecated

```typescript
// src/app/api/v1/route.ts

// Zod
const UserSchema = z.object({
id: z.string(),
name: z.string(),
fullName: z.string().optional().describe("@deprecated Use name instead"),
email: z.string().email(),
});

// Or TypeScript
type UserResponse = {
id: string;
name: string;
/** @deprecated Use firstName and lastName instead */
fullName?: string;
email: string;
};

/**
* @body UserSchema
* @response UserResponse
*/
export async function GET() {
// ...
}
```

### Custom Operation ID

```typescript
// src/app/api/users/[id]/route.ts

/**
* Get user by ID
* @operationId getUserById
* @pathParams UserParams
* @response UserResponse
*/
export async function GET() {
// ...
}
// Generates: operationId: "getUserById" instead of auto-generated "get-users-{id}"
```

### File Uploads / Multipart Form Data

```typescript
// src/app/api/upload/route.ts

// Zod
const FileUploadSchema = z.object({
file: z.custom().describe("Image file (PNG/JPG)"),
description: z.string().optional().describe("File description"),
category: z.string().describe("File category"),
});

// Or TypeScript
type FileUploadFormData = {
file: File;
description?: string;
category: string;
};

/**
* @body FileUploadSchema
* @contentType multipart/form-data
*/
export async function POST() {
// ...
}
```

## Response Management

### Zero Config + Response Sets

Configure reusable error sets in `next.openapi.json`:

```json
{
"defaultResponseSet": "common",
"responseSets": {
"common": ["400", "401", "500"],
"public": ["400", "500"],
"auth": ["400", "401", "403", "500"]
}
}
```

### Usage Examples

```typescript
/**
* Auto-default responses
* @response UserResponse
* @openapi
*/
export async function GET() {}
// Generates: 200:UserResponse + common errors (400, 401, 500)

/**
* With custom description inline
* @response UserResponse:Complete user profile information
* @openapi
*/
export async function GET() {}
// Generates: 200:UserResponse (with custom description) + common errors

/**
* Override response set
* @response ProductResponse
* @responseSet public
* @openapi
*/
export async function GET() {}
// Generates: 200:ProductResponse + public errors (400, 500)

/**
* Add custom responses with description
* @response 201:UserResponse:User created successfully
* @add 409:ConflictResponse
* @openapi
*/
export async function POST() {}
// Generates: 201:UserResponse (with custom description) + common errors + 409:ConflictResponse

/**
* Combine multiple sets
* @response UserResponse
* @responseSet auth,crud
* @add 429:RateLimitResponse
* @openapi
*/
export async function PUT() {}
// Combines: auth + crud errors + custom 429
```

### Error Schema Configuration

#### Define consistent error schemas using templates:

```json
{
"defaultResponseSet": "common",
"responseSets": {
"common": ["400", "500"],
"auth": ["400", "401", "403", "500"],
"public": ["400", "500"]
},
"errorConfig": {
"template": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "{{ERROR_MESSAGE}}"
},
"code": {
"type": "string",
"example": "{{ERROR_CODE}}"
}
}
},
"codes": {
"400": {
"description": "Bad Request",
"variables": {
"ERROR_MESSAGE": "Invalid request parameters",
"ERROR_CODE": "BAD_REQUEST"
}
},
"401": {
"description": "Unauthorized",
"variables": {
"ERROR_MESSAGE": "Authentication required",
"ERROR_CODE": "UNAUTHORIZED"
}
},
"403": {
"description": "Forbidden",
"variables": {
"ERROR_MESSAGE": "Access denied",
"ERROR_CODE": "FORBIDDEN"
}
},
"404": {
"description": "Not Found",
"variables": {
"ERROR_MESSAGE": "Resource not found",
"ERROR_CODE": "NOT_FOUND"
}
},
"500": {
"description": "Internal Server Error",
"variables": {
"ERROR_MESSAGE": "An unexpected error occurred",
"ERROR_CODE": "INTERNAL_ERROR"
}
}
}
}
}
```

## Ignoring Routes

You can exclude routes from OpenAPI documentation in two ways:

### Using @ignore Tag

Add the `@ignore` tag to any route you want to exclude:

```typescript
// src/app/api/internal/route.ts

/**
* Internal route - not for documentation
* @ignore
*/
export async function GET() {
// This route will not appear in OpenAPI documentation
}
```

### Using ignoreRoutes Configuration

Add patterns to your `next.openapi.json` configuration file to exclude multiple routes at once:

```json
{
"openapi": "3.0.0",
"info": {
"title": "Next.js API",
"version": "1.0.0"
},
"apiDir": "src/app/api",
"ignoreRoutes": ["/internal/*", "/debug", "/admin/test/*"]
}
```

Pattern matching supports wildcards:

- `/internal/*` - Ignores all routes under `/internal/`
- `/debug` - Ignores only the `/debug` route
- `/admin/*/temp` - Ignores routes like `/admin/users/temp`, `/admin/posts/temp`

## Advanced Usage

### Automatic Path Parameter Detection

The library automatically detects path parameters and generates documentation for them:

```typescript
// src/app/api/users/[id]/posts/[postId]/route.ts

// Will automatically detect 'id' and 'postId' parameters
export async function GET() {
// ...
}
```

If no type/schema is provided for path parameters, a default schema will be generated.

### Intelligent Examples

The library generates intelligent examples for parameters based on their name:

| Parameter name | Example |
| -------------- | ---------------------------------------- |
| `id`, `*Id` | `"123"` or `123` |
| `slug` | `"example-slug"` |
| `uuid` | `"123e4567-e89b-12d3-a456-426614174000"` |
| `email` | `"user@example.com"` |
| `name` | `"example-name"` |
| `date` | `"2023-01-01"` |

### TypeScript Generics Support

The library supports TypeScript generic types and automatically resolves them during documentation generation:

```typescript
// src/app/api/llms/route.ts

import { NextResponse } from "next/server";

// Define generic response wrapper
type MyApiSuccessResponseBody = T & {
success: true;
httpCode: string;
};

// Define specific response data
type LLMSResponse = {
llms: Array<{
id: string;
name: string;
provider: string;
isDefault: boolean;
}>;
};

/**
* Get list of available LLMs
* @description Get list of available LLMs with success wrapper
* @response 200:MyApiSuccessResponseBody
* @openapi
*/
export async function GET() {
return NextResponse.json({
success: true,
httpCode: "200",
llms: [
{
id: "gpt-5",
name: "GPT-5",
provider: "OpenAI",
isDefault: true,
},
],
});
}
```

### TypeScript Utility Types Support

The library supports TypeScript utility types for extracting types from functions:

```typescript
// src/app/api/products/route.utils.ts
export async function getProductById(id: string): Promise<{
product: Product;
fetchedAt: string;
}> {
// Implementation...
}

export function createProduct(
data: { name: string; price: number },
options: { notify: boolean }
): { success: boolean; productId: string } {
// Implementation...
}

// src/types/product-types.ts

// Extract return type from async functions
export type ProductResponse = Awaited>;

// Extract return type from sync functions
export type CreateResult = ReturnType;

// Extract parameter types as tuple
export type CreateProductParams = Parameters;

// Extract specific parameter using indexed access
export type ProductData = Parameters[0];
export type ProductOptions = Parameters[1];

// Use with generic types
interface ApiResponse {
success: boolean;
data: T;
}

export type ProductApiResponse = ApiResponse;

/**
* @response ProductApiResponse
* @openapi
*/
export async function GET() {
// Fully typed response automatically documented
}
```

**Supported utility types:**
- `Awaited` - Unwraps Promise types
- `ReturnType` - Extracts function return type
- `Parameters` - Extracts function parameters as tuple
- `Parameters[N]` - Indexed access to specific parameter
- Generic interfaces like `ApiResponse` with type parameter substitution

## Advanced Zod Features

The library supports advanced Zod features such as:

### Validation Chains

```typescript
// Zod validation chains are properly converted to OpenAPI schemas
const EmailSchema = z
.string()
.email()
.min(5)
.max(100)
.describe("Email address");

// Converts to OpenAPI with email format, minLength and maxLength
```

### Type Aliases with z.infer

```typescript
// You can use TypeScript with Zod types
import { z } from "zod";

const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(2),
});

// Use z.infer to create a TypeScript type
type User = z.infer;

// The library will be able to recognize this schema by reference `UserSchema` or `User` type.
```

### Factory Functions (Schema Generators)

The library automatically detects and expands Zod factory functions - any function that returns a Zod schema:

```typescript
// Define reusable schema factory
export function createPaginatedSchema(dataSchema: T) {
return z.object({
data: z.array(dataSchema).describe("Array of items"),
pagination: z.object({
nextCursor: z.string().nullable(),
hasMore: z.boolean(),
limit: z.number().int().positive(),
}),
});
}

// Use in your schemas - automatically expanded in OpenAPI
export const PaginatedUsersSchema = createPaginatedSchema(UserSchema);
export const PaginatedProductsSchema = createPaginatedSchema(ProductSchema);
```

Factory functions work with any naming convention and support:
- Generic type parameters
- Inline schemas as arguments
- Imported schemas
- Multiple factory patterns in the same project

### Schema Composition with `.extend()`

Zod's `.extend()` method allows you to build upon existing schemas:

```typescript
// src/schemas/user.ts

// Base user schema
export const BaseUserSchema = z.object({
id: z.string().uuid().describe("User ID"),
email: z.string().email().describe("Email address"),
});

// Extend with additional fields
export const UserProfileSchema = BaseUserSchema.extend({
name: z.string().describe("Full name"),
bio: z.string().optional().describe("User biography"),
});

// Multiple levels of extension
export const AdminUserSchema = UserProfileSchema.extend({
role: z.enum(["admin", "moderator"]).describe("Admin role"),
permissions: z.array(z.string()).describe("Permission list"),
});

export const UserIdParams = z.object({
id: z.string().uuid().describe("User ID"),
});

// src/app/api/users/[id]/route.ts

/**
* Get user profile
* @pathParams UserIdParams
* @response UserProfileSchema
* @openapi
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
// Returns: { id, email, name, bio? }
}
```

### Drizzle-Zod Support

The library fully supports **drizzle-zod** for generating Zod schemas from Drizzle ORM table definitions. This provides a single source of truth for your database schema, validation, and API documentation.

**Supported Functions:**

- `createInsertSchema()` - Generate schema for inserts
- `createSelectSchema()` - Generate schema for selects
- `createUpdateSchema()` - Generate schema for updates

**Features:**

- ✅ Automatic field extraction from refinements
- ✅ Validation method conversion (min, max, email, url, etc.)
- ✅ Optional/nullable field detection
- ✅ Intelligent type mapping based on field names
- ✅ Full OpenAPI schema generation

**Example:**

```typescript
import { createInsertSchema } from "drizzle-zod";
import { posts } from "@/db/schema";

export const CreatePostSchema = createInsertSchema(posts, {
title: (schema) => schema.title.min(5).max(255),
content: (schema) => schema.content.min(10),
published: (schema) => schema.published.optional(),
});
```

See the [complete Drizzle-Zod example](./examples/next15-app-drizzle-zod) for a full working implementation with a blog API.

## Multiple Schema Types Support 🆕

Use **multiple schema types simultaneously** in a single project - perfect for gradual migrations, combining hand-written schemas with generated ones (protobuf, GraphQL), or using existing OpenAPI specs.

### Configuration

```json
{
"schemaType": ["zod", "typescript"],
"schemaDir": "./src/schemas",
"schemaFiles": ["./schemas/external-api.yaml"]
}
```

### Schema Resolution Priority

1. **Custom files** (highest) - from `schemaFiles` array
2. **Zod schemas** (medium) - if `"zod"` in `schemaType`
3. **TypeScript types** (fallback) - if `"typescript"` in `schemaType`

### Common Use Cases

```json
// Gradual TypeScript → Zod migration
{ "schemaType": ["zod", "typescript"] }

// Zod + protobuf schemas
{
"schemaType": ["zod"],
"schemaFiles": ["./proto/schemas.yaml"]
}

// Everything together
{
"schemaType": ["zod", "typescript"],
"schemaFiles": ["./openapi-models.yaml"]
}
```

Custom schema files support YAML/JSON in OpenAPI 3.0 format. See **[next15-app-mixed-schemas](./examples/next15-app-mixed-schemas)** for a complete working example.

## Examples

Explore complete demo projects in the **[examples](./examples/)** directory, covering integrations with Zod, TypeScript, Drizzle and documentation tools like Scalar and Swagger.

### 🚀 Run an Example

```bash
cd examples/next15-app-zod
npm install
npx next-openapi-gen generate
npm run dev
```

Then open `http://localhost:3000/api-docs` to view the generated docs.

## Available UI providers


Scalar
Swagger
Redoc
Stoplight Elements
RapiDoc




scalar


swagger


redoc


stoplight


rapidoc


## Contributing

We welcome contributions! 🎉

Please read our [Contributing Guide](CONTRIBUTING.md) for details.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for release history and changes.

## License

MIT