feat(api): Auto-generate OpenAPI/Swagger documentation #212

Closed
opened 2026-01-24 17:15:18 +00:00 by jack · 0 comments
Owner

Problem

  • Keine API-Dokumentation vorhanden
  • Externe Entwickler können API nicht verstehen
  • Keine automatische Client-Generierung möglich
  • Keine Validierung gegen Schema

Lösung

1. OpenAPI Schema mit Zod

// packages/backend/src/schemas/index.ts
import { z } from 'zod';
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';

extendZodWithOpenApi(z);

// Observation Schema
export const ObservationSchema = z.object({
  id: z.number().openapi({ example: 1 }),
  memorySessionId: z.string().openapi({ example: 'session-abc123' }),
  project: z.string().openapi({ example: 'my-project' }),
  type: z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change', 'session-request'])
    .openapi({ example: 'discovery' }),
  title: z.string().nullable().openapi({ example: 'Found authentication pattern' }),
  text: z.string().nullable().openapi({ example: 'The codebase uses JWT tokens...' }),
  createdAt: z.string().openapi({ example: '2026-01-25T10:30:00Z' }),
}).openapi('Observation');

export const CreateObservationSchema = ObservationSchema.omit({ id: true, createdAt: true })
  .openapi('CreateObservation');

// Session Schema
export const SessionSchema = z.object({
  id: z.number(),
  contentSessionId: z.string(),
  memorySessionId: z.string().nullable(),
  project: z.string(),
  status: z.enum(['active', 'completed', 'failed']),
  startedAt: z.string(),
  completedAt: z.string().nullable(),
}).openapi('Session');

// Search Schema
export const SearchRequestSchema = z.object({
  q: z.string().min(1).openapi({ example: 'authentication' }),
  project: z.string().optional(),
  type: z.array(z.string()).optional(),
  limit: z.number().min(1).max(100).default(20),
  offset: z.number().min(0).default(0),
}).openapi('SearchRequest');

export const SearchResponseSchema = z.object({
  results: z.array(ObservationSchema),
  total: z.number(),
  took: z.number().openapi({ description: 'Search duration in ms' }),
}).openapi('SearchResponse');

2. OpenAPI Registry

// packages/backend/src/openapi/registry.ts
import { OpenAPIRegistry, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';

export const registry = new OpenAPIRegistry();

// Register endpoints
registry.registerPath({
  method: 'get',
  path: '/api/data/projects',
  tags: ['Data'],
  summary: 'List all projects',
  responses: {
    200: {
      description: 'List of projects',
      content: {
        'application/json': {
          schema: z.array(z.string()),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/api/data/observations',
  tags: ['Data'],
  summary: 'Create observation',
  request: {
    body: {
      content: {
        'application/json': {
          schema: CreateObservationSchema,
        },
      },
    },
  },
  responses: {
    201: {
      description: 'Created observation',
      content: {
        'application/json': {
          schema: ObservationSchema,
        },
      },
    },
    400: {
      description: 'Validation error',
    },
  },
});

registry.registerPath({
  method: 'get',
  path: '/api/search',
  tags: ['Search'],
  summary: 'Search observations',
  request: {
    query: SearchRequestSchema,
  },
  responses: {
    200: {
      description: 'Search results',
      content: {
        'application/json': {
          schema: SearchResponseSchema,
        },
      },
    },
  },
});

3. Generate OpenAPI Spec

// packages/backend/src/openapi/generator.ts
import { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';

export function generateOpenApiSpec() {
  const generator = new OpenApiGeneratorV3(registry.definitions);
  
  return generator.generateDocument({
    openapi: '3.0.3',
    info: {
      title: 'Claude-Mem API',
      version: '1.0.0',
      description: 'Memory management API for Claude Code sessions',
      contact: {
        name: 'Claude-Mem Team',
        url: 'https://github.com/customable/claude-mem',
      },
    },
    servers: [
      { url: 'http://localhost:3100', description: 'Local development' },
    ],
    tags: [
      { name: 'Data', description: 'Data management endpoints' },
      { name: 'Search', description: 'Search functionality' },
      { name: 'Tasks', description: 'Task queue management' },
      { name: 'Workers', description: 'Worker management' },
    ],
  });
}

4. Swagger UI Endpoint

// packages/backend/src/routes/docs.ts
import swaggerUi from 'swagger-ui-express';
import { generateOpenApiSpec } from '../openapi/generator';

const spec = generateOpenApiSpec();

router.use('/docs', swaggerUi.serve);
router.get('/docs', swaggerUi.setup(spec, {
  customCss: '.swagger-ui .topbar { display: none }',
  customSiteTitle: 'Claude-Mem API Docs',
}));

// Raw spec endpoint
router.get('/openapi.json', (req, res) => {
  res.json(spec);
});

router.get('/openapi.yaml', (req, res) => {
  res.type('yaml').send(yaml.stringify(spec));
});

5. Request Validation Middleware

// packages/backend/src/middleware/validate.ts
import { AnyZodObject } from 'zod';

export function validate(schema: AnyZodObject) {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      await schema.parseAsync({
        body: req.body,
        query: req.query,
        params: req.params,
      });
      next();
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({
          error: 'Validation failed',
          details: error.errors,
        });
      }
      next(error);
    }
  };
}

// Usage
router.post('/observations', 
  validate(z.object({ body: CreateObservationSchema })),
  async (req, res) => { /* ... */ }
);

Generierte Dokumentation

Verfügbar unter:

  • http://localhost:3100/docs - Swagger UI
  • http://localhost:3100/openapi.json - OpenAPI JSON
  • http://localhost:3100/openapi.yaml - OpenAPI YAML

Client Generation

# TypeScript Client generieren
npx openapi-typescript http://localhost:3100/openapi.json -o ./src/api-types.ts

# Oder mit openapi-generator
npx @openapitools/openapi-generator-cli generate \
  -i http://localhost:3100/openapi.json \
  -g typescript-fetch \
  -o ./src/api-client

Akzeptanzkriterien

  • Zod Schemas für alle Endpoints
  • OpenAPI Registry mit allen Routes
  • /docs Swagger UI Endpoint
  • /openapi.json Spec Endpoint
  • Request Validation Middleware
  • Dokumentation wie man Clients generiert
## Problem - Keine API-Dokumentation vorhanden - Externe Entwickler können API nicht verstehen - Keine automatische Client-Generierung möglich - Keine Validierung gegen Schema ## Lösung ### 1. OpenAPI Schema mit Zod ```typescript // packages/backend/src/schemas/index.ts import { z } from 'zod'; import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; extendZodWithOpenApi(z); // Observation Schema export const ObservationSchema = z.object({ id: z.number().openapi({ example: 1 }), memorySessionId: z.string().openapi({ example: 'session-abc123' }), project: z.string().openapi({ example: 'my-project' }), type: z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change', 'session-request']) .openapi({ example: 'discovery' }), title: z.string().nullable().openapi({ example: 'Found authentication pattern' }), text: z.string().nullable().openapi({ example: 'The codebase uses JWT tokens...' }), createdAt: z.string().openapi({ example: '2026-01-25T10:30:00Z' }), }).openapi('Observation'); export const CreateObservationSchema = ObservationSchema.omit({ id: true, createdAt: true }) .openapi('CreateObservation'); // Session Schema export const SessionSchema = z.object({ id: z.number(), contentSessionId: z.string(), memorySessionId: z.string().nullable(), project: z.string(), status: z.enum(['active', 'completed', 'failed']), startedAt: z.string(), completedAt: z.string().nullable(), }).openapi('Session'); // Search Schema export const SearchRequestSchema = z.object({ q: z.string().min(1).openapi({ example: 'authentication' }), project: z.string().optional(), type: z.array(z.string()).optional(), limit: z.number().min(1).max(100).default(20), offset: z.number().min(0).default(0), }).openapi('SearchRequest'); export const SearchResponseSchema = z.object({ results: z.array(ObservationSchema), total: z.number(), took: z.number().openapi({ description: 'Search duration in ms' }), }).openapi('SearchResponse'); ``` ### 2. OpenAPI Registry ```typescript // packages/backend/src/openapi/registry.ts import { OpenAPIRegistry, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'; export const registry = new OpenAPIRegistry(); // Register endpoints registry.registerPath({ method: 'get', path: '/api/data/projects', tags: ['Data'], summary: 'List all projects', responses: { 200: { description: 'List of projects', content: { 'application/json': { schema: z.array(z.string()), }, }, }, }, }); registry.registerPath({ method: 'post', path: '/api/data/observations', tags: ['Data'], summary: 'Create observation', request: { body: { content: { 'application/json': { schema: CreateObservationSchema, }, }, }, }, responses: { 201: { description: 'Created observation', content: { 'application/json': { schema: ObservationSchema, }, }, }, 400: { description: 'Validation error', }, }, }); registry.registerPath({ method: 'get', path: '/api/search', tags: ['Search'], summary: 'Search observations', request: { query: SearchRequestSchema, }, responses: { 200: { description: 'Search results', content: { 'application/json': { schema: SearchResponseSchema, }, }, }, }, }); ``` ### 3. Generate OpenAPI Spec ```typescript // packages/backend/src/openapi/generator.ts import { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'; export function generateOpenApiSpec() { const generator = new OpenApiGeneratorV3(registry.definitions); return generator.generateDocument({ openapi: '3.0.3', info: { title: 'Claude-Mem API', version: '1.0.0', description: 'Memory management API for Claude Code sessions', contact: { name: 'Claude-Mem Team', url: 'https://github.com/customable/claude-mem', }, }, servers: [ { url: 'http://localhost:3100', description: 'Local development' }, ], tags: [ { name: 'Data', description: 'Data management endpoints' }, { name: 'Search', description: 'Search functionality' }, { name: 'Tasks', description: 'Task queue management' }, { name: 'Workers', description: 'Worker management' }, ], }); } ``` ### 4. Swagger UI Endpoint ```typescript // packages/backend/src/routes/docs.ts import swaggerUi from 'swagger-ui-express'; import { generateOpenApiSpec } from '../openapi/generator'; const spec = generateOpenApiSpec(); router.use('/docs', swaggerUi.serve); router.get('/docs', swaggerUi.setup(spec, { customCss: '.swagger-ui .topbar { display: none }', customSiteTitle: 'Claude-Mem API Docs', })); // Raw spec endpoint router.get('/openapi.json', (req, res) => { res.json(spec); }); router.get('/openapi.yaml', (req, res) => { res.type('yaml').send(yaml.stringify(spec)); }); ``` ### 5. Request Validation Middleware ```typescript // packages/backend/src/middleware/validate.ts import { AnyZodObject } from 'zod'; export function validate(schema: AnyZodObject) { return async (req: Request, res: Response, next: NextFunction) => { try { await schema.parseAsync({ body: req.body, query: req.query, params: req.params, }); next(); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'Validation failed', details: error.errors, }); } next(error); } }; } // Usage router.post('/observations', validate(z.object({ body: CreateObservationSchema })), async (req, res) => { /* ... */ } ); ``` ## Generierte Dokumentation Verfügbar unter: - `http://localhost:3100/docs` - Swagger UI - `http://localhost:3100/openapi.json` - OpenAPI JSON - `http://localhost:3100/openapi.yaml` - OpenAPI YAML ## Client Generation ```bash # TypeScript Client generieren npx openapi-typescript http://localhost:3100/openapi.json -o ./src/api-types.ts # Oder mit openapi-generator npx @openapitools/openapi-generator-cli generate \ -i http://localhost:3100/openapi.json \ -g typescript-fetch \ -o ./src/api-client ``` ## Akzeptanzkriterien - [ ] Zod Schemas für alle Endpoints - [ ] OpenAPI Registry mit allen Routes - [ ] `/docs` Swagger UI Endpoint - [ ] `/openapi.json` Spec Endpoint - [ ] Request Validation Middleware - [ ] Dokumentation wie man Clients generiert
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
customable/claude-mem#212
No description provided.