Nuxt3 Best Practices 03: OpenAPI Documentation Output
1. Introduction
In Nuxt 3, we can leverage Nitro’s built-in OpenAPI documentation generation feature and visualize it using Swagger UI or Scalar UI. However, this feature has the following issues:
- Experimental feature: Nitro’s OpenAPI support is still in the experimental stage and may be modified or removed in the future.
- Lack of API filtering mechanism: All Server APIs are exposed, with no configuration option to exclude specific APIs.
- Incompatibility with Zod: If using
zodto define schemas, Nitro cannot automatically convert them to OpenAPI format, requiring additional JSON Schema definitions.
Therefore, I recommend a more flexible approach with the following advantages:
- Seamless integration with
zod, allowing direct generation of OpenAPI documentation fromzodschemas. - Support for API filtering, enabling selective hiding of specific APIs.
2. Implementation Plan
2.1 Install Dependencies
First, we need to install zod and its OpenAPI adapter library zod-openapi.
npm install zod zod-openapi
2.2 Overview of the Approach
The main implementation steps are as follows:
- Create an OpenAPI registry: Used to collect API definitions.
- Provide a
defineOpenAPImethod: Allows each API to register its interface information with the registry. - Define an OpenAPI generation API: Reads registry data and generates an OpenAPI JSON document.
2.3 Create an OpenAPI Registry
Create openapi-registry.ts in the server/utils directory to store and manage API definitions.
// openapi-registry.ts
import z from 'zod';
import { extendZodWithOpenApi } from 'zod-openapi';
extendZodWithOpenApi(z);
export type OpenAPIEndpoint = {
/** API tags */
tags: string[];
/** API path, e.g., '/api/users' */
path: string;
/** Request method, e.g., 'get' or 'post' */
method: string;
/** API operation ID */
operationId?: string;
/** API summary */
summary?: string;
/** API detailed description */
description?: string;
/** Query string parameter schema */
querySchema?: z.ZodObject<any, any>;
/** Path parameter schema */
pathParamsSchema?: z.ZodObject<any, any>;
/** Request body schema */
bodySchema?: z.ZodObject<any, any>;
/** Response body schema */
responseSchema?: z.ZodType<any, any>;
};
// Internal storage for registered APIs
const registry: OpenAPIEndpoint[] = [];
/**
* Register an OpenAPI endpoint
* @param data OpenAPI endpoint data
*/
export function defineOpenAPI(data: OpenAPIEndpoint) {
registry.push(data);
}
/**
* Retrieve registered API data
*/
export function getOpenAPIRegistry() {
return registry;
}
2.4 Define OpenAPI Information in Server APIs
Define an API and register OpenAPI information in server/api/student.post.ts.
// server/api/student.post.ts
import { defineEventHandler, readValidatedBody } from 'h3';
import z from 'zod';
const bodySchema = z.object({
name: z.string().min(1).max(100).describe('Student name'),
address: z.string().min(10).max(100).describe('Student address'),
});
const responseSchema = z.object({
message: z.string(),
});
defineOpenAPI({
tags: ['Student'],
path: '/api/student',
method: 'post',
operationId: 'student-add',
summary: 'Add a student',
description: 'Add student information',
bodySchema,
responseSchema,
});
export default defineEventHandler(async (event) => {
const params = await readValidatedBody(event, bodySchema.parse);
console.log(params);
return { message: 'success' };
});
2.5 Generate OpenAPI JSON Document
Create server/api/openapi.json.dev.ts to dynamically generate OpenAPI documentation.
// openapi.json.dev.ts
import { defineEventHandler } from 'h3';
import { ZodOptional } from 'zod';
import { createSchema } from 'zod-openapi';
export default defineEventHandler(() => {
const endpoints = getOpenAPIRegistry();
const paths: Record<string, any> = {};
endpoints.forEach((ep) => {
if (!paths[ep.path]) {
paths[ep.path] = {};
}
const method = ep.method.toLowerCase();
// Query String Parameters
const queryParameters = ep.querySchema
? Object.entries(ep.querySchema.shape).map(([name, schema]) => {
return {
name,
in: 'query',
required: !(schema instanceof ZodOptional),
...createSchema(schema as any),
};
})
: [];
// Path Parameters
const pathParameters = ep.pathParamsSchema
? Object.entries(ep.pathParamsSchema.shape).map(([name, schema]) => {
return {
name,
in: 'path',
required: true,
...createSchema(schema as any),
};
})
: [];
const parameters = [...pathParameters, ...queryParameters];
// Request Body
const requestBody = ep.bodySchema
? {
content: {
'application/json': createSchema(ep.bodySchema),
},
}
: undefined;
// Responses
const responses = ep.responseSchema
? {
200: {
description: 'Successful response',
content: {
'application/json': createSchema(ep.responseSchema),
},
},
}
: {
200: { description: 'Successful response' },
};
paths[ep.path][method] = {
tags: ep.tags,
operationId: ep.operationId || `${method}_${ep.path}`,
summary: ep.summary,
description: ep.description,
parameters,
...(requestBody ? { requestBody } : {}),
responses,
};
});
return {
openapi: '3.0.0',
info: {
title: 'My API test',
version: '1.0.0',
description: 'This is a test API',
},
servers: [{ url: 'http://localhost:3000' }],
tags: [{ name: 'Student', description: 'Student-related operations' }],
paths,
};
});
3. Summary
This approach, using zod-openapi combined with the defineOpenAPI mechanism, enables automated API documentation generation while offering Zod integration and API filtering, making OpenAPI generation more flexible and efficient.
Previous article: Nuxt3 Best Practices 02: Request Validation