AINode.jsAutomation

API Documentation Generator from Code

TT
TopicTrick Team
API Documentation Generator from Code

API Documentation Generator from Code

Writing API documentation manually takes almost as long as writing the API itself. This tool scans your Express.js route files, analyses the endpoints, parameters, and response patterns, then generates a complete OpenAPI 3.0 specification and serves it via a built-in Swagger UI.

This is Tool 24 of the Build 50 AI Automation Tools course.


What You'll Build

  • CLI tool that scans a directory of route files
  • GPT-4o analyses each route and generates OpenAPI schema
  • Merges all routes into a single OpenAPI 3.0 spec
  • Serves documentation via Swagger UI at /docs

Setup

bash
mkdir api-doc-generator && cd api-doc-generator
npm init -y
npm install express swagger-ui-express openai glob dotenv
bash
# .env
OPENAI_API_KEY=sk-your-key-here
PORT=3000

Documentation Generator Service

js
// src/services/docgenService.js
import { readFile, readdir } from 'fs/promises';
import { join, extname } from 'path';
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function analyzeRouteFile(filePath, fileContent) {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: `You are an API documentation expert. Analyze this Express.js route file and extract all API endpoints.
Return ONLY a JSON object — no markdown:
{
  "basePath": "inferred base path for this router (e.g. '/api/users')",
  "endpoints": [
    {
      "method": "GET | POST | PUT | PATCH | DELETE",
      "path": "route path (e.g. '/:id')",
      "summary": "short one-line description",
      "description": "longer description of what this endpoint does",
      "tags": ["tag for grouping in docs"],
      "requiresAuth": true/false,
      "pathParams": [
        { "name": "string", "type": "string | integer | uuid", "description": "string", "required": true }
      ],
      "queryParams": [
        { "name": "string", "type": "string | integer | boolean", "description": "string", "required": false }
      ],
      "requestBody": {
        "required": true/false,
        "contentType": "application/json",
        "schema": {
          "type": "object",
          "required": ["field names"],
          "properties": {
            "fieldName": { "type": "string | number | boolean | array | object", "description": "string", "example": "value" }
          }
        }
      },
      "responses": {
        "200": { "description": "success response description", "schema": { "type": "object", "properties": {} } },
        "400": { "description": "validation error" },
        "401": { "description": "unauthorized (if requiresAuth)" },
        "404": { "description": "not found (for :id routes)" }
      }
    }
  ]
}
If the file has no route handlers, return {"basePath": null, "endpoints": []}`,
      },
      {
        role: 'user',
        content: `File: ${filePath}\n\n${fileContent.slice(0, 15_000)}`,
      },
    ],
    temperature: 0.1,
    response_format: { type: 'json_object' },
  });

  return JSON.parse(response.choices[0].message.content);
}

function buildOpenAPISpec(apiName, version, serverUrl, routeAnalyses) {
  const paths = {};
  const tags = new Set();

  for (const { basePath, endpoints } of routeAnalyses) {
    if (!basePath || !endpoints.length) continue;

    for (const ep of endpoints) {
      const fullPath = `${basePath}${ep.path}`.replace(/\/+/g, '/');
      if (!paths[fullPath]) paths[fullPath] = {};

      ep.tags?.forEach(t => tags.add(t));

      const parameters = [
        ...(ep.pathParams?.map(p => ({
          in: 'path', name: p.name, required: p.required, schema: { type: p.type || 'string' },
          description: p.description,
        })) || []),
        ...(ep.queryParams?.map(p => ({
          in: 'query', name: p.name, required: p.required, schema: { type: p.type || 'string' },
          description: p.description,
        })) || []),
      ];

      if (ep.requiresAuth) {
        parameters.push({
          in: 'header', name: 'Authorization', required: true,
          schema: { type: 'string', example: 'Bearer <token>' },
          description: 'JWT access token',
        });
      }

      paths[fullPath][ep.method.toLowerCase()] = {
        summary:     ep.summary,
        description: ep.description,
        tags:        ep.tags,
        parameters:  parameters.length > 0 ? parameters : undefined,
        requestBody: ep.requestBody?.required ? {
          required: true,
          content: { 'application/json': { schema: ep.requestBody.schema } },
        } : undefined,
        responses: Object.fromEntries(
          Object.entries(ep.responses || {}).map(([code, resp]) => [
            code,
            { description: resp.description, content: resp.schema ? { 'application/json': { schema: resp.schema } } : undefined },
          ])
        ),
      };
    }
  }

  return {
    openapi: '3.0.3',
    info: { title: apiName, version, description: `Generated by AI API Doc Generator` },
    servers: [{ url: serverUrl }],
    tags: [...tags].map(name => ({ name })),
    paths,
    components: {
      securitySchemes: {
        BearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
      },
    },
  };
}

export async function generateDocs(routesDir, apiName = 'My API', serverUrl = 'http://localhost:3000') {
  const files = await readdir(routesDir);
  const jsFiles = files.filter(f => extname(f) === '.js' || extname(f) === '.ts');

  const analyses = await Promise.all(
    jsFiles.map(async file => {
      const content = await readFile(join(routesDir, file), 'utf-8');
      return analyzeRouteFile(file, content);
    })
  );

  return buildOpenAPISpec(apiName, '1.0.0', serverUrl, analyses);
}

Server with Swagger UI

js
// src/server.js
import 'dotenv/config';
import express from 'express';
import swaggerUi from 'swagger-ui-express';
import { generateDocs } from './services/docgenService.js';
import { resolve } from 'path';

const app = express();
app.use(express.json());

let openApiSpec = null;

app.get('/generate', async (req, res, next) => {
  try {
    const { routesDir, apiName, serverUrl } = req.query;
    const dir = routesDir ? resolve(routesDir) : resolve('./example-routes');

    openApiSpec = await generateDocs(dir, apiName || 'Generated API', serverUrl || 'http://localhost:3000');
    res.json({ success: true, spec: openApiSpec });
  } catch (err) { next(err); }
});

app.use('/docs', swaggerUi.serve, (req, res, next) => {
  if (!openApiSpec) return res.send('<h1>No docs yet — call GET /generate first</h1>');
  swaggerUi.setup(openApiSpec)(req, res, next);
});

app.get('/docs/json', (req, res) => {
  if (!openApiSpec) return res.status(404).json({ error: 'No spec generated yet' });
  res.json(openApiSpec);
});

app.use((err, _req, res, _next) => res.status(500).json({ error: err.message }));
app.listen(process.env.PORT ?? 3000, () => console.log('API Doc Generator running at /docs'));

Testing

bash
# Point at your API's routes directory
curl "http://localhost:3000/generate?routesDir=../my-api/src/routes&apiName=My+API"

# Open in browser
open http://localhost:3000/docs

CLI Version

js
// src/cli.js
import 'dotenv/config';
import { writeFile } from 'fs/promises';
import { generateDocs } from './services/docgenService.js';

const [, , routesDir, outputFile] = process.argv;
if (!routesDir) { console.error('Usage: node cli.js <routes-dir> [output.json]'); process.exit(1); }

const spec = await generateDocs(routesDir);
const output = outputFile || 'openapi.json';
await writeFile(output, JSON.stringify(spec, null, 2));
console.log(`OpenAPI spec written to ${output}`);
bash
node src/cli.js ./src/routes openapi.json

Build 50 AI Automation Tools — Tool 24 of 50

API docs generator is live. Continue to Tool 25 to build a SQL query builder from natural language.


    Summary

    • File-by-file analysis breaks the problem into chunks — each route file is a manageable prompt
    • OpenAPI 3.0 spec is the industry standard — compatible with Swagger UI, Postman, and API gateways
    • swagger-ui-express serves a polished, interactive documentation UI with zero configuration
    • The CLI version integrates into CI/CD to auto-update docs on every push to main
    • For the highest accuracy, run the generator, then let developers annotate corrections as JSDoc comments

    Continue to Tool 25: SQL Query Builder from Natural Language →