Generate comprehensive OpenAPI/Swagger documentation from existing APIs
Generates comprehensive OpenAPI 3.0 specifications with interactive documentation from existing API codebases.
/plugin marketplace add https://www.claudepluginhub.com/api/plugins/jeremylongshore-api-documentation-generator-plugins-api-development-api-documentation-generator-3/marketplace.json/plugin install jeremylongshore-api-documentation-generator-plugins-api-development-api-documentation-generator-3@cpd-jeremylongshore-api-documentation-generator-plugins-api-development-api-documentation-generator-3Automatically generate comprehensive OpenAPI 3.0 specifications with interactive documentation, automated testing, and multi-language client SDKs from your existing API codebase or by analyzing live endpoints.
Use /generate-api-docs when you need to:
DON'T use this when:
/api-contract-generator instead)/build-graphql-server with introspection)This command implements OpenAPI 3.0.3 specification as the primary approach because:
Alternative considered: API Blueprint
Alternative considered: RAML
Alternative considered: AsyncAPI
Before running this command:
Scan codebase and running API to discover all endpoints, methods, and data structures.
Extract request/response schemas from code annotations, TypeScript types, or runtime analysis.
Generate complete OpenAPI 3.0 specification with all paths, schemas, and security definitions.
Add descriptions, examples, and grouping tags for better organization and understanding.
Deploy Swagger UI and Redoc for interactive API exploration and testing.
The command generates:
openapi.yaml - Main OpenAPI 3.0 specificationopenapi.json - JSON format for toolingdocs/ - Static HTML documentation
index.html - Swagger UI interfaceredoc.html - Redoc documentationcollections/ - External tool formats
postman_collection.json - Postman importinsomnia_workspace.json - Insomnia importbruno_collection.bru - Bruno collectionexamples/ - Request/response examplesschemas/ - Extracted JSON schemasREADME.md - Getting started guide// api-documentation-generator.js
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const { z } = require('zod');
const zodToJsonSchema = require('zod-to-json-schema');
const express = require('express');
const fs = require('fs').promises;
const path = require('path');
const yaml = require('js-yaml');
class APIDocumentationGenerator {
constructor(app, config = {}) {
this.app = app;
this.config = {
title: config.title || 'API Documentation',
version: config.version || '1.0.0',
description: config.description || 'API Documentation',
servers: config.servers || [{ url: 'http://localhost:3000' }],
contact: config.contact || {},
license: config.license || { name: 'MIT' },
security: config.security || [],
...config
};
this.paths = {};
this.schemas = {};
this.examples = {};
this.tags = [];
}
// Analyze Express routes and generate OpenAPI paths
analyzeRoutes() {
const routes = [];
// Extract all routes from Express app
this.app._router.stack.forEach((middleware) => {
if (middleware.route) {
// Regular routes
routes.push({
path: middleware.route.path,
methods: Object.keys(middleware.route.methods)
});
} else if (middleware.name === 'router') {
// Router middleware
middleware.handle.stack.forEach((handler) => {
if (handler.route) {
const basePath = middleware.regexp.source
.replace('\\/?', '')
.replace('(?=\\/|$)', '')
.replace(/\\/g, '/')
.replace('^', '');
routes.push({
path: basePath + handler.route.path,
methods: Object.keys(handler.route.methods)
});
}
});
}
});
// Convert Express routes to OpenAPI paths
routes.forEach(route => {
const openApiPath = this.expressToOpenAPIPath(route.path);
if (!this.paths[openApiPath]) {
this.paths[openApiPath] = {};
}
route.methods.forEach(method => {
this.paths[openApiPath][method] = this.generateOperation(
method,
route.path,
openApiPath
);
});
});
return this.paths;
}
// Convert Express path to OpenAPI format
expressToOpenAPIPath(expressPath) {
// Convert :param to {param}
return expressPath.replace(/:([^/]+)/g, '{$1}');
}
// Generate operation object for a route
generateOperation(method, expressPath, openApiPath) {
const operation = {
tags: this.inferTags(expressPath),
summary: this.generateSummary(method, expressPath),
description: this.generateDescription(method, expressPath),
operationId: this.generateOperationId(method, expressPath),
parameters: this.extractParameters(openApiPath),
responses: this.generateResponses(method, expressPath)
};
// Add request body for POST, PUT, PATCH
if (['post', 'put', 'patch'].includes(method)) {
operation.requestBody = this.generateRequestBody(method, expressPath);
}
// Add security if applicable
if (this.requiresAuth(expressPath)) {
operation.security = [{ bearerAuth: [] }];
}
return operation;
}
// Infer tags from path
inferTags(path) {
const segments = path.split('/').filter(s => s && !s.startsWith(':'));
if (segments.length > 0) {
const tag = segments[0].charAt(0).toUpperCase() + segments[0].slice(1);
// Add tag to tags list if not exists
if (!this.tags.find(t => t.name === tag)) {
this.tags.push({
name: tag,
description: `Operations related to ${tag.toLowerCase()}`
});
}
return [tag];
}
return ['General'];
}
// Generate operation summary
generateSummary(method, path) {
const resource = path.split('/').filter(s => s && !s.startsWith(':')).pop() || 'resource';
const summaries = {
get: path.includes(':') ? `Get ${resource} by ID` : `List all ${resource}`,
post: `Create a new ${resource}`,
put: `Update ${resource}`,
patch: `Partially update ${resource}`,
delete: `Delete ${resource}`,
head: `Check ${resource} existence`,
options: `Get ${resource} options`
};
return summaries[method] || `${method.toUpperCase()} ${resource}`;
}
// Generate detailed description
generateDescription(method, path) {
const resource = path.split('/').filter(s => s && !s.startsWith(':')).pop() || 'resource';
return `Performs a ${method.toUpperCase()} operation on ${resource}. ` +
`This endpoint ${this.requiresAuth(path) ? 'requires authentication' : 'is publicly accessible'}.`;
}
// Generate operation ID
generateOperationId(method, path) {
const segments = path.split('/').filter(s => s && !s.startsWith(':'));
const resource = segments.join('_');
return `${method}_${resource}`.replace(/[^a-zA-Z0-9_]/g, '_');
}
// Extract parameters from path
extractParameters(path) {
const parameters = [];
// Extract path parameters
const pathParams = path.match(/{([^}]+)}/g);
if (pathParams) {
pathParams.forEach(param => {
const name = param.slice(1, -1);
parameters.push({
name,
in: 'path',
required: true,
description: `ID of the ${name}`,
schema: {
type: 'string',
format: name.toLowerCase().includes('id') ? 'uuid' : undefined
}
});
});
}
// Add common query parameters for list operations
if (!path.includes('{')) {
parameters.push(
{
name: 'page',
in: 'query',
description: 'Page number for pagination',
schema: { type: 'integer', default: 1, minimum: 1 }
},
{
name: 'limit',
in: 'query',
description: 'Number of items per page',
schema: { type: 'integer', default: 20, minimum: 1, maximum: 100 }
},
{
name: 'sort',
in: 'query',
description: 'Sort field and direction (e.g., "name:asc")',
schema: { type: 'string' }
},
{
name: 'filter',
in: 'query',
description: 'Filter criteria',
schema: { type: 'string' }
}
);
}
return parameters;
}
// Generate request body schema
generateRequestBody(method, path) {
const resource = path.split('/').filter(s => s && !s.startsWith(':')).pop() || 'resource';
const schemaName = resource.charAt(0).toUpperCase() + resource.slice(1);
// Generate or retrieve schema
if (!this.schemas[schemaName]) {
this.schemas[schemaName] = this.generateResourceSchema(schemaName);
}
return {
required: method === 'post',
content: {
'application/json': {
schema: {
$ref: `#/components/schemas/${schemaName}`
},
examples: this.generateExamples(schemaName)
}
}
};
}
// Generate resource schema
generateResourceSchema(name) {
// This would ideally extract from your actual models
// Here's a generic example
return {
type: 'object',
required: ['name'],
properties: {
id: {
type: 'string',
format: 'uuid',
readOnly: true,
description: 'Unique identifier'
},
name: {
type: 'string',
minLength: 1,
maxLength: 255,
description: `Name of the ${name.toLowerCase()}`
},
description: {
type: 'string',
maxLength: 1000,
description: 'Detailed description'
},
status: {
type: 'string',
enum: ['active', 'inactive', 'pending'],
default: 'active',
description: 'Current status'
},
metadata: {
type: 'object',
additionalProperties: true,
description: 'Additional metadata'
},
createdAt: {
type: 'string',
format: 'date-time',
readOnly: true,
description: 'Creation timestamp'
},
updatedAt: {
type: 'string',
format: 'date-time',
readOnly: true,
description: 'Last update timestamp'
}
}
};
}
// Generate response schemas
generateResponses(method, path) {
const responses = {
'200': {
description: 'Successful operation',
content: {
'application/json': {
schema: this.generateResponseSchema(method, path)
}
}
},
'400': {
description: 'Bad request',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' }
}
}
},
'401': {
description: 'Unauthorized',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' }
}
}
},
'404': {
description: 'Resource not found',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' }
}
}
},
'500': {
description: 'Internal server error',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' }
}
}
}
};
// Customize based on method
if (method === 'post') {
responses['201'] = responses['200'];
responses['201'].description = 'Resource created successfully';
delete responses['200'];
} else if (method === 'delete') {
responses['204'] = {
description: 'Resource deleted successfully'
};
delete responses['200'];
}
return responses;
}
// Generate response schema based on operation
generateResponseSchema(method, path) {
const resource = path.split('/').filter(s => s && !s.startsWith(':')).pop() || 'resource';
const schemaName = resource.charAt(0).toUpperCase() + resource.slice(1);
if (!path.includes(':') && method === 'get') {
// List operation
return {
type: 'object',
properties: {
data: {
type: 'array',
items: { $ref: `#/components/schemas/${schemaName}` }
},
pagination: { $ref: '#/components/schemas/Pagination' },
meta: { $ref: '#/components/schemas/Meta' }
}
};
} else {
// Single resource
return { $ref: `#/components/schemas/${schemaName}` };
}
}
// Generate examples for schemas
generateExamples(schemaName) {
return {
default: {
summary: 'Standard example',
value: {
name: `Example ${schemaName}`,
description: 'This is an example description',
status: 'active',
metadata: {
category: 'example',
priority: 'high'
}
}
},
minimal: {
summary: 'Minimal required fields',
value: {
name: `Minimal ${schemaName}`
}
},
complete: {
summary: 'All fields populated',
value: {
id: '550e8400-e29b-41d4-a716-446655440000',
name: `Complete ${schemaName}`,
description: 'This example includes all possible fields',
status: 'active',
metadata: {
category: 'complete',
priority: 'high',
tags: ['example', 'complete'],
customField: 'custom value'
},
createdAt: '2024-01-15T10:30:00Z',
updatedAt: '2024-01-15T14:45:00Z'
}
}
};
}
// Check if path requires authentication
requiresAuth(path) {
// Implement your auth logic
const publicPaths = ['/health', '/docs', '/api-docs', '/login', '/register'];
return !publicPaths.some(p => path.includes(p));
}
// Add common schemas
addCommonSchemas() {
this.schemas.Error = {
type: 'object',
required: ['code', 'message'],
properties: {
code: {
type: 'string',
description: 'Error code'
},
message: {
type: 'string',
description: 'Error message'
},
details: {
type: 'object',
additionalProperties: true,
description: 'Additional error details'
},
timestamp: {
type: 'string',
format: 'date-time',
description: 'Error timestamp'
}
}
};
this.schemas.Pagination = {
type: 'object',
properties: {
page: { type: 'integer', minimum: 1 },
limit: { type: 'integer', minimum: 1, maximum: 100 },
total: { type: 'integer', minimum: 0 },
pages: { type: 'integer', minimum: 0 }
}
};
this.schemas.Meta = {
type: 'object',
properties: {
version: { type: 'string' },
timestamp: { type: 'string', format: 'date-time' },
requestId: { type: 'string', format: 'uuid' }
}
};
}
// Generate complete OpenAPI specification
generateOpenAPISpec() {
// Analyze routes
this.analyzeRoutes();
// Add common schemas
this.addCommonSchemas();
const spec = {
openapi: '3.0.3',
info: {
title: this.config.title,
version: this.config.version,
description: this.config.description,
contact: this.config.contact,
license: this.config.license
},
servers: this.config.servers,
tags: this.tags,
paths: this.paths,
components: {
schemas: this.schemas,
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
},
apiKey: {
type: 'apiKey',
name: 'X-API-Key',
in: 'header'
},
oauth2: {
type: 'oauth2',
flows: {
authorizationCode: {
authorizationUrl: 'https://auth.example.com/oauth/authorize',
tokenUrl: 'https://auth.example.com/oauth/token',
refreshUrl: 'https://auth.example.com/oauth/refresh',
scopes: {
read: 'Read access',
write: 'Write access',
admin: 'Admin access'
}
}
}
}
}
},
security: this.config.security
};
return spec;
}
// Export to various formats
async exportDocumentation(outputDir = './api-docs') {
const spec = this.generateOpenAPISpec();
// Create output directory
await fs.mkdir(outputDir, { recursive: true });
await fs.mkdir(path.join(outputDir, 'collections'), { recursive: true });
// Save OpenAPI spec in YAML and JSON
await fs.writeFile(
path.join(outputDir, 'openapi.yaml'),
yaml.dump(spec, { indent: 2 })
);
await fs.writeFile(
path.join(outputDir, 'openapi.json'),
JSON.stringify(spec, null, 2)
);
// Generate Postman collection
const postmanCollection = this.convertToPostmanCollection(spec);
await fs.writeFile(
path.join(outputDir, 'collections', 'postman_collection.json'),
JSON.stringify(postmanCollection, null, 2)
);
// Generate HTML documentation
await this.generateHTMLDocs(spec, outputDir);
// Generate README
await this.generateREADME(spec, outputDir);
console.log(`API documentation generated in ${outputDir}`);
return spec;
}
// Convert OpenAPI to Postman Collection
convertToPostmanCollection(openApiSpec) {
const collection = {
info: {
name: openApiSpec.info.title,
description: openApiSpec.info.description,
version: openApiSpec.info.version,
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: [],
auth: {
type: 'bearer',
bearer: [{
key: 'token',
value: '{{access_token}}',
type: 'string'
}]
},
variable: [
{
key: 'baseUrl',
value: openApiSpec.servers[0].url,
type: 'string'
},
{
key: 'access_token',
value: '',
type: 'string'
}
]
};
// Group by tags
const folders = {};
Object.entries(openApiSpec.paths).forEach(([path, methods]) => {
Object.entries(methods).forEach(([method, operation]) => {
const tag = operation.tags?.[0] || 'General';
if (!folders[tag]) {
folders[tag] = {
name: tag,
item: []
};
}
const request = {
name: operation.summary,
request: {
method: method.toUpperCase(),
header: [],
url: {
raw: `{{baseUrl}}${path}`,
host: ['{{baseUrl}}'],
path: path.split('/').filter(p => p)
},
description: operation.description
}
};
// Add path parameters
if (operation.parameters) {
const pathParams = operation.parameters.filter(p => p.in === 'path');
const queryParams = operation.parameters.filter(p => p.in === 'query');
if (pathParams.length > 0) {
request.request.url.variable = pathParams.map(p => ({
key: p.name,
value: '',
description: p.description
}));
}
if (queryParams.length > 0) {
request.request.url.query = queryParams.map(p => ({
key: p.name,
value: '',
description: p.description,
disabled: !p.required
}));
}
}
// Add request body
if (operation.requestBody) {
const content = operation.requestBody.content['application/json'];
if (content) {
request.request.header.push({
key: 'Content-Type',
value: 'application/json'
});
request.request.body = {
mode: 'raw',
raw: JSON.stringify(
content.examples?.default?.value || {},
null,
2
)
};
}
}
// Add auth if required
if (operation.security) {
request.request.auth = {
type: 'bearer',
bearer: [{
key: 'token',
value: '{{access_token}}'
}]
};
}
folders[tag].item.push(request);
});
});
collection.item = Object.values(folders);
return collection;
}
// Generate HTML documentation
async generateHTMLDocs(spec, outputDir) {
// Swagger UI HTML
const swaggerHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${spec.info.title} - Swagger UI</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css">
<style>
body { margin: 0; padding: 0; }
.swagger-ui .topbar { display: none; }
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: './openapi.json',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "BaseLayout",
deepLinking: true,
showExtensions: true,
showCommonExtensions: true,
tryItOutEnabled: true
});
};
</script>
</body>
</html>`;
// Redoc HTML
const redocHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${spec.info.title} - ReDoc</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { margin: 0; padding: 0; }
</style>
</head>
<body>
<redoc spec-url='./openapi.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0/bundles/redoc.standalone.js"></script>
</body>
</html>`;
await fs.writeFile(path.join(outputDir, 'index.html'), swaggerHtml);
await fs.writeFile(path.join(outputDir, 'redoc.html'), redocHtml);
}
// Generate README documentation
async generateREADME(spec, outputDir) {
const readme = `# ${spec.info.title}
${spec.info.description}
**Version:** ${spec.info.version}
## Base URL
\`\`\`
${spec.servers.map(s => s.url).join('\n')}
\`\`\`
## Authentication
This API uses the following authentication methods:
${spec.components.securitySchemes ? Object.entries(spec.components.securitySchemes).map(([name, scheme]) =>
`- **${name}**: ${scheme.type} ${scheme.scheme || ''}`
).join('\n') : '- No authentication required'}
## Available Endpoints
${Object.entries(spec.paths).map(([path, methods]) =>
Object.entries(methods).map(([method, op]) =>
`- \`${method.toUpperCase()} ${path}\` - ${op.summary}`
).join('\n')
).join('\n')}
## Quick Start
### Installation
\`\`\`bash
# Using npm
npm install axios
# Using curl
curl -X GET "${spec.servers[0].url}/endpoint"
\`\`\`
### Example Request
\`\`\`javascript
const axios = require('axios');
const config = {
method: 'get',
url: '${spec.servers[0].url}${Object.keys(spec.paths)[0]}',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
}
};
axios(config)
.then(response => {
console.log(JSON.stringify(response.data));
})
.catch(error => {
console.error(error);
});
\`\`\`
## Documentation
- [Swagger UI](./index.html) - Interactive API documentation
- [ReDoc](./redoc.html) - Alternative documentation format
- [OpenAPI Spec](./openapi.json) - Raw OpenAPI specification
- [Postman Collection](./collections/postman_collection.json) - Import to Postman
## SDKs
Generate client SDKs using:
\`\`\`bash
# JavaScript/TypeScript
npx @openapitools/openapi-generator-cli generate -i openapi.yaml -g typescript-axios -o ./sdk/typescript
# Python
openapi-generator-cli generate -i openapi.yaml -g python -o ./sdk/python
# Go
openapi-generator-cli generate -i openapi.yaml -g go -o ./sdk/go
\`\`\`
## Support
${spec.info.contact?.email ? `For support, contact ${spec.info.contact.email}` : 'For support, please refer to the documentation.'}
## License
${spec.info.license?.name || 'See LICENSE file'}
`;
await fs.writeFile(path.join(outputDir, 'README.md'), readme);
}
// Setup interactive documentation routes
setupDocumentationRoutes(basePath = '/api-docs') {
// Serve OpenAPI spec
this.app.get(`${basePath}/openapi.json`, (req, res) => {
res.json(this.generateOpenAPISpec());
});
// Serve Swagger UI
this.app.use(
`${basePath}`,
swaggerUi.serve,
swaggerUi.setup(this.generateOpenAPISpec(), {
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: this.config.title
})
);
console.log(`API documentation available at ${basePath}`);
}
}
// Usage example
const app = express();
const docGenerator = new APIDocumentationGenerator(app, {
title: 'E-commerce API',
version: '2.0.0',
description: 'Complete e-commerce platform API with user management, products, and orders',
servers: [
{ url: 'https://api.example.com/v2', description: 'Production' },
{ url: 'https://staging-api.example.com/v2', description: 'Staging' },
{ url: 'http://localhost:3000', description: 'Development' }
],
contact: {
name: 'API Support',
email: 'api@example.com',
url: 'https://support.example.com'
}
});
// Generate and export documentation
docGenerator.exportDocumentation('./api-documentation');
// Setup interactive docs
docGenerator.setupDocumentationRoutes('/docs');
module.exports = APIDocumentationGenerator;
# api_doc_enhancer.py
from fastapi import FastAPI, APIRouter, Depends, HTTPException
from fastapi.openapi.utils import get_openapi
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
import json
import yaml
from pathlib import Path
import httpx
from datetime import datetime
class OpenAPIEnhancer:
"""Enhance FastAPI's auto-generated OpenAPI with additional features"""
def __init__(self, app: FastAPI):
self.app = app
self.examples = {}
self.additional_schemas = {}
self.external_docs = {}
def enhance_openapi(self):
"""Enhance the OpenAPI spec with additional information"""
if self.app.openapi_schema:
return self.app.openapi_schema
openapi_schema = get_openapi(
title=self.app.title,
version=self.app.version,
description=self.app.description,
routes=self.app.routes,
)
# Add custom enhancements
self._add_api_versioning(openapi_schema)
self._add_rate_limiting_info(openapi_schema)
self._add_webhook_definitions(openapi_schema)
self._add_code_samples(openapi_schema)
self._add_security_schemes(openapi_schema)
self._add_server_variables(openapi_schema)
self._enhance_schemas(openapi_schema)
self._add_tags_metadata(openapi_schema)
self.app.openapi_schema = openapi_schema
return self.app.openapi_schema
def _add_api_versioning(self, spec: Dict[str, Any]):
"""Add API versioning information"""
spec["info"]["x-api-versioning"] = {
"strategy": "uri",
"current": "v2",
"supported": ["v1", "v2"],
"deprecated": ["v0"],
"sunset": {
"v0": "2024-01-01",
"v1": "2025-01-01"
}
}
def _add_rate_limiting_info(self, spec: Dict[str, Any]):
"""Add rate limiting documentation"""
spec["info"]["x-rate-limiting"] = {
"default": {
"requests": 1000,
"window": "1h"
},
"authenticated": {
"requests": 5000,
"window": "1h"
},
"endpoints": {
"/api/search": {
"requests": 100,
"window": "1m"
}
},
"headers": {
"limit": "X-RateLimit-Limit",
"remaining": "X-RateLimit-Remaining",
"reset": "X-RateLimit-Reset"
}
}
def _add_webhook_definitions(self, spec: Dict[str, Any]):
"""Add webhook definitions to OpenAPI spec"""
spec["webhooks"] = {
"orderCreated": {
"post": {
"requestBody": {
"description": "Order creation notification",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OrderWebhook"
}
}
}
},
"responses": {
"200": {
"description": "Webhook processed successfully"
}
}
}
},
"paymentProcessed": {
"post": {
"requestBody": {
"description": "Payment processing notification",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PaymentWebhook"
}
}
}
},
"responses": {
"200": {
"description": "Webhook acknowledged"
}
}
}
}
}
# Add webhook schemas
if "components" not in spec:
spec["components"] = {}
if "schemas" not in spec["components"]:
spec["components"]["schemas"] = {}
spec["components"]["schemas"]["OrderWebhook"] = {
"type": "object",
"required": ["event", "data", "timestamp"],
"properties": {
"event": {
"type": "string",
"enum": ["order.created", "order.updated", "order.cancelled"]
},
"data": {
"type": "object",
"properties": {
"orderId": {"type": "string", "format": "uuid"},
"customerId": {"type": "string", "format": "uuid"},
"amount": {"type": "number", "format": "float"},
"status": {"type": "string"}
}
},
"timestamp": {
"type": "string",
"format": "date-time"
}
}
}
def _add_code_samples(self, spec: Dict[str, Any]):
"""Add code samples to each endpoint"""
for path, methods in spec.get("paths", {}).items():
for method, operation in methods.items():
if method in ["get", "post", "put", "delete", "patch"]:
operation["x-code-samples"] = self._generate_code_samples(
method, path, operation
)
def _generate_code_samples(self, method: str, path: str, operation: Dict) -> List[Dict]:
"""Generate code samples for multiple languages"""
samples = []
# cURL example
curl_sample = f"curl -X {method.upper()} '{self.app.servers[0]['url'] if hasattr(self.app, 'servers') else 'https://api.example.com'}{path}'"
if method in ["post", "put", "patch"]:
curl_sample += """ \\
-H 'Content-Type: application/json' \\
-H 'Authorization: Bearer YOUR_TOKEN' \\
-d '{
"name": "example",
"value": 123
}'"""
else:
curl_sample += " \\\n -H 'Authorization: Bearer YOUR_TOKEN'"
samples.append({
"lang": "Shell",
"source": curl_sample,
"label": "cURL"
})
# Python example
python_sample = f"""import requests
url = "https://api.example.com{path}"
headers = {{
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
}}"""
if method in ["post", "put", "patch"]:
python_sample += """
data = {
"name": "example",
"value": 123
}
response = requests.""" + method + """(url, json=data, headers=headers)"""
else:
python_sample += f"""
response = requests.{method}(url, headers=headers)"""
python_sample += """
print(response.json())"""
samples.append({
"lang": "Python",
"source": python_sample,
"label": "Python (requests)"
})
# JavaScript example
js_sample = f"""const axios = require('axios');
const config = {{
method: '{method}',
url: 'https://api.example.com{path}',
headers: {{
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json'
}}"""
if method in ["post", "put", "patch"]:
js_sample += """,
data: {
name: 'example',
value: 123
}"""
js_sample += """
};
axios(config)
.then(response => {
console.log(JSON.stringify(response.data));
})
.catch(error => {
console.error(error);
});"""
samples.append({
"lang": "JavaScript",
"source": js_sample,
"label": "Node.js (axios)"
})
# Go example
go_sample = f"""package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {{
url := "https://api.example.com{path}"
"""
if method in ["post", "put", "patch"]:
go_sample += """ payload := map[string]interface{}{
"name": "example",
"value": 123,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest(""" + f'"{method.upper()}", url, bytes.NewBuffer(jsonData))'
else:
go_sample += f""" req, _ := http.NewRequest("{method.upper()}", url, nil)"""
go_sample += """
req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}"""
samples.append({
"lang": "Go",
"source": go_sample,
"label": "Go (net/http)"
})
return samples
def _add_security_schemes(self, spec: Dict[str, Any]):
"""Add comprehensive security schemes"""
if "components" not in spec:
spec["components"] = {}
spec["components"]["securitySchemes"] = {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "JWT token authentication"
},
"apiKey": {
"type": "apiKey",
"name": "X-API-Key",
"in": "header",
"description": "API key authentication"
},
"oauth2": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "https://auth.example.com/oauth/authorize",
"tokenUrl": "https://auth.example.com/oauth/token",
"refreshUrl": "https://auth.example.com/oauth/refresh",
"scopes": {
"read": "Read access to protected resources",
"write": "Write access to protected resources",
"admin": "Admin access to all resources"
}
},
"clientCredentials": {
"tokenUrl": "https://auth.example.com/oauth/token",
"scopes": {
"api": "API access"
}
}
}
},
"cookieAuth": {
"type": "apiKey",
"in": "cookie",
"name": "session_id",
"description": "Session cookie authentication"
}
}
def _add_server_variables(self, spec: Dict[str, Any]):
"""Add server variables for environment switching"""
spec["servers"] = [
{
"url": "https://{environment}.api.example.com/{version}",
"description": "API Server",
"variables": {
"environment": {
"enum": ["production", "staging", "development"],
"default": "production",
"description": "Server environment"
},
"version": {
"enum": ["v1", "v2", "v3"],
"default": "v2",
"description": "API version"
}
}
},
{
"url": "http://localhost:{port}",
"description": "Local development server",
"variables": {
"port": {
"enum": ["3000", "8000", "8080"],
"default": "8000",
"description": "Server port"
}
}
}
]
def _enhance_schemas(self, spec: Dict[str, Any]):
"""Enhance schemas with examples and additional properties"""
for schema_name, schema in spec.get("components", {}).get("schemas", {}).items():
if "properties" in schema:
# Add examples to properties
for prop_name, prop in schema["properties"].items():
if "example" not in prop:
prop["example"] = self._generate_example_for_type(prop)
# Add schema-level example
if "example" not in schema:
schema["example"] = {
prop_name: prop.get("example")
for prop_name, prop in schema["properties"].items()
}
def _generate_example_for_type(self, prop: Dict) -> Any:
"""Generate example based on property type"""
prop_type = prop.get("type", "string")
prop_format = prop.get("format", "")
examples = {
("string", "email"): "user@example.com",
("string", "date"): "2024-01-15",
("string", "date-time"): "2024-01-15T10:30:00Z",
("string", "uuid"): "550e8400-e29b-41d4-a716-446655440000",
("string", "uri"): "https://example.com/resource",
("string", "hostname"): "api.example.com",
("string", "ipv4"): "192.168.1.1",
("string", "ipv6"): "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
("integer", ""): 42,
("number", ""): 123.45,
("boolean", ""): True,
("array", ""): ["item1", "item2"],
("object", ""): {"key": "value"}
}
return examples.get((prop_type, prop_format), "example")
def _add_tags_metadata(self, spec: Dict[str, Any]):
"""Add detailed tag descriptions"""
spec["tags"] = [
{
"name": "Authentication",
"description": "Authentication and authorization endpoints",
"externalDocs": {
"description": "Authentication guide",
"url": "https://docs.example.com/auth"
}
},
{
"name": "Users",
"description": "User management operations",
"externalDocs": {
"description": "User API documentation",
"url": "https://docs.example.com/users"
}
},
{
"name": "Products",
"description": "Product catalog management",
"externalDocs": {
"description": "Product API documentation",
"url": "https://docs.example.com/products"
}
},
{
"name": "Orders",
"description": "Order processing and management",
"externalDocs": {
"description": "Order API documentation",
"url": "https://docs.example.com/orders"
}
}
]
def export_documentation(self, output_dir: str = "./api-docs"):
"""Export documentation in various formats"""
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
# Get enhanced OpenAPI spec
spec = self.enhance_openapi()
# Export as JSON
with open(output_path / "openapi.json", "w") as f:
json.dump(spec, f, indent=2)
# Export as YAML
with open(output_path / "openapi.yaml", "w") as f:
yaml.dump(spec, f, default_flow_style=False, sort_keys=False)
# Generate AsyncAPI spec if websockets are used
asyncapi_spec = self._generate_asyncapi_spec()
if asyncapi_spec:
with open(output_path / "asyncapi.yaml", "w") as f:
yaml.dump(asyncapi_spec, f, default_flow_style=False)
# Generate Postman collection
postman_collection = self._convert_to_postman(spec)
with open(output_path / "postman_collection.json", "w") as f:
json.dump(postman_collection, f, indent=2)
# Generate README
self._generate_readme(spec, output_path)
print(f"Documentation exported to {output_path}")
def _generate_asyncapi_spec(self) -> Optional[Dict]:
"""Generate AsyncAPI spec for WebSocket/event-driven APIs"""
# Check if app has WebSocket routes
has_websocket = any(
hasattr(route, "endpoint") and "websocket" in str(route.endpoint)
for route in self.app.routes
)
if not has_websocket:
return None
return {
"asyncapi": "2.6.0",
"info": {
"title": f"{self.app.title} WebSocket API",
"version": self.app.version,
"description": f"WebSocket API for {self.app.title}"
},
"servers": {
"production": {
"url": "wss://api.example.com",
"protocol": "ws",
"description": "Production WebSocket server"
}
},
"channels": {
"/ws": {
"subscribe": {
"message": {
"$ref": "#/components/messages/notification"
}
},
"publish": {
"message": {
"$ref": "#/components/messages/command"
}
}
}
},
"components": {
"messages": {
"notification": {
"payload": {
"type": "object",
"properties": {
"type": {"type": "string"},
"data": {"type": "object"}
}
}
},
"command": {
"payload": {
"type": "object",
"properties": {
"action": {"type": "string"},
"params": {"type": "object"}
}
}
}
}
}
}
def _convert_to_postman(self, spec: Dict) -> Dict:
"""Convert OpenAPI to Postman Collection"""
# Implementation similar to JavaScript version
# Simplified for brevity
return {
"info": {
"name": spec["info"]["title"],
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": []
}
def _generate_readme(self, spec: Dict, output_path: Path):
"""Generate comprehensive README"""
readme_content = f"""# {spec['info']['title']}
{spec['info'].get('description', '')}
## Version
{spec['info']['version']}
## Documentation
- [OpenAPI Specification](./openapi.json)
- [Postman Collection](./postman_collection.json)
- [AsyncAPI Specification](./asyncapi.yaml) (if applicable)
## Quick Start
```python
import requests
# Example API call
response = requests.get(
"https://api.example.com/endpoint",
headers={{"Authorization": "Bearer YOUR_TOKEN"}}
)
print(response.json())
See the OpenAPI specification for detailed authentication information.
{spec['info'].get('contact', {}).get('email', 'support@example.com')} """
with open(output_path / "README.md", "w") as f:
f.write(readme_content)
app = FastAPI( title="E-commerce API", version="2.0.0", description="Comprehensive e-commerce platform API" )
enhancer = OpenAPIEnhancer(app)
@app.get("/openapi.json") async def get_open_api_endpoint(): return enhancer.enhance_openapi()
if name == "main": enhancer.export_documentation()
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| "No routes found" | Empty Express/FastAPI app | Ensure routes are defined before generation |
| "Invalid OpenAPI spec" | Malformed specification | Validate with online validators |
| "Schema extraction failed" | TypeScript/Pydantic issues | Check type definitions are correct |
| "Swagger UI not loading" | CORS or path issues | Check CORS settings and base path configuration |
| "Examples not generated" | Missing schema definitions | Ensure all models have proper schemas |
## Configuration Options
**Basic Usage:**
```bash
/generate-api-docs \
--framework=express \
--output=./api-docs \
--format=openapi3 \
--include-examples
Available Options:
--framework <type> - Web framework to analyze
express - Express.js applicationsfastapi - FastAPI Python applicationsspring - Spring Boot applicationsrails - Ruby on Rails APIsdjango - Django REST framework--format <spec> - Output specification format
openapi3 - OpenAPI 3.0.3 (default)openapi2 - OpenAPI 2.0 (Swagger)asyncapi - AsyncAPI for event-drivengraphql - GraphQL schema--output <path> - Output directory for documentation
./api-docs--include-examples - Generate request/response examples
--interactive-ui <type> - Interactive documentation UI
swagger - Swagger UI (default)redoc - ReDoc documentationboth - Both UIsnone - Static docs only--auth-docs - Include authentication documentation
--sdk-generation - Generate client SDKs
--languages - Comma-separated list (js,python,go,java)--sdk-output - SDK output directory--postman - Generate Postman collection
--validate - Validate generated specification
DO:
DON'T:
Issue: Routes not detected
// Ensure routes are registered before doc generation
app.use('/api', apiRouter);
// Then generate docs
const docs = new APIDocumentationGenerator(app);
Issue: TypeScript types not extracted
# Install required packages
npm install --save-dev typescript ts-json-schema-generator
# Ensure tsconfig.json includes all source files
Issue: Swagger UI CORS errors
// Enable CORS for documentation
app.use('/api-docs', cors({
origin: '*',
credentials: true
}));
/api-contract-generator - Design-first API specification/api-mock-server - Create mock server from OpenAPI spec/api-sdk-generator - Generate client libraries/api-versioning-manager - Manage API versions/api-testing-framework - Generate tests from spec