This guide explains the improvements made to the OpenAPI generation system.
Previously, you had to manually register every API endpoint in scripts/generate-openapi.ts:
// Had to write this for every endpoint:
registry.registerPath({
method: 'get',
path: '/api/users',
description: 'Get all users',
summary: 'List all users',
responses: {
200: {
description: 'List of users',
content: {
'application/json': {
schema: z.array(UserSchema)
}
}
}
}
});
// And this for POST...
registry.registerPath({
method: 'post',
path: '/api/users',
// ... more manual registration
});
// And this for every other endpoint...Problems:
- ❌ Lots of duplication
- ❌ Easy to forget to update when adding new routes
- ❌ Route structure defined in two places (SvelteKit routes + OpenAPI script)
- ❌ More code to maintain
Now, routes are automatically discovered from your SvelteKit file structure:
// In your route file: src/routes/api/users/+server.ts
export const _openapi: RouteOpenAPIMetadata = {
operations: [
{
method: 'GET',
summary: 'List all users',
tags: ['Users'],
responses: [
{ status: 200, description: 'List of users', schema: z.array(UserSchema) }
]
},
{
method: 'POST',
summary: 'Create user',
tags: ['Users'],
requestBody: { schema: CreateUserSchema },
responses: [
{ status: 201, description: 'User created', schema: UserSchema },
{ status: 400, description: 'Invalid input' }
]
}
]
};Then just run npm run generate:openapi and it automatically:
- ✅ Scans all route files
- ✅ Discovers API paths from folder structure
- ✅ Converts
[id]to{id}automatically - ✅ Generates complete OpenAPI spec
Benefits:
- ✅ Single source of truth (route structure = API structure)
- ✅ No manual path registration needed
- ✅ Adding new routes is automatic
- ✅ Less code, less maintenance
Follow SvelteKit conventions:
src/routes/api/products/+server.ts → /api/products
src/routes/api/products/[id]/+server.ts → /api/products/{id}
Export an _openapi object:
import type { RouteOpenAPIMetadata } from '../../../../scripts/types.js';
import { ProductSchema, CreateProductSchema } from '@zod-schema-example/shared';
export const _openapi: RouteOpenAPIMetadata = {
operations: [
{
method: 'GET',
summary: 'List products',
tags: ['Products'],
responses: [
{ status: 200, description: 'List of products', schema: z.array(ProductSchema) }
]
}
]
};
export const GET: RequestHandler = async () => {
// Your implementation
};npm run generate:openapiThat's it! Your new route is automatically included.
The system scans src/routes for all +server.ts files:
- Discovers all API route files automatically
- Converts SvelteKit paths to OpenAPI format
- Imports each route file to read metadata
- Registers all operations found
| SvelteKit Path | OpenAPI Path |
|---|---|
api/users/+server.ts |
/api/users |
api/users/[id]/+server.ts |
/api/users/{id} |
api/posts/[postId]/comments/[cid]/+server.ts |
/api/posts/{postId}/comments/{cid} |
All metadata is fully typed:
export interface RouteMethodConfig {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
summary?: string;
description?: string;
tags?: string[];
requestBody?: {
schema: z.ZodTypeAny; // Any Zod schema
contentType?: string;
description?: string;
};
responses: ResponseDefinition[];
}export const _openapi: RouteOpenAPIMetadata = {
operations: [
{
method: 'GET',
summary: 'Health check',
responses: [
{ status: 200, description: 'Server is healthy' }
]
}
]
};export const _openapi: RouteOpenAPIMetadata = {
operations: [
{
method: 'POST',
summary: 'Create product',
tags: ['Products'],
requestBody: {
schema: CreateProductSchema,
description: 'Product data'
},
responses: [
{ status: 201, description: 'Product created', schema: ProductSchema },
{ status: 400, description: 'Invalid input' },
{ status: 409, description: 'Product already exists' }
]
}
]
};export const _openapi: RouteOpenAPIMetadata = {
operations: [
{
method: 'GET',
summary: 'Get product',
tags: ['Products'],
responses: [
{ status: 200, description: 'Product found', schema: ProductSchema },
{ status: 404, description: 'Product not found' }
]
},
{
method: 'PUT',
summary: 'Update product',
tags: ['Products'],
requestBody: { schema: UpdateProductSchema },
responses: [
{ status: 200, description: 'Product updated', schema: ProductSchema },
{ status: 404, description: 'Product not found' },
{ status: 400, description: 'Invalid input' }
]
},
{
method: 'DELETE',
summary: 'Delete product',
tags: ['Products'],
responses: [
{ status: 204, description: 'Product deleted' },
{ status: 404, description: 'Product not found' }
]
}
]
};Your existing routes have been updated to use the new system:
src/routes/api/users/+server.ts- Added_openapiexport for GET and POSTsrc/routes/api/users/[id]/+server.ts- Added_openapiexport for GET, PUT, DELETE
All route handlers remain unchanged - only metadata was added.
scripts/types.ts- TypeScript types for OpenAPI metadatascripts/route-scanner.ts- Scans and discovers route filesscripts/README.md- Documentation for the scriptsMIGRATION_GUIDE.md- This file
scripts/generate-openapi.ts- Now uses route scanning instead of manual registrationsrc/routes/api/users/+server.ts- Added_openapiexportsrc/routes/api/users/[id]/+server.ts- Added_openapiexport
SvelteKit only allows specific exports in route files (like GET, POST, etc.) or exports prefixed with _. Using _openapi follows SvelteKit conventions and avoids linter warnings.
No! Just create your route file with the _openapi export and run npm run generate:openapi. The scanner will automatically discover it.
Simply don't export _openapi from that route file. The scanner will skip it and show a warning.
Yes, but it's not recommended. The new system is more maintainable and follows the "convention over configuration" principle.
Path parameters are automatically detected from the route path. For example, [id] in the filename becomes {id} in the OpenAPI spec. Parameter validation schemas are applied in the generator.
- ✅ All existing routes have been migrated
- ✅ Run
npm run generate:openapito verify it works - ✅ When adding new routes, use the new convention
- ✅ Enjoy automated OpenAPI generation!
Check out:
scripts/README.md- Detailed documentation on the generation systemscripts/types.ts- TypeScript type definitions- Existing route files for examples