#OpenAPI Documentation
Alepha auto-generates OpenAPI 3.0 specifications from your $action definitions using the $swagger primitive.
#Setup
1import { $swagger } from "alepha/server/swagger"; 2 3class App { 4 docs = $swagger({ 5 info: { 6 title: "My API", 7 version: "1.0.0", 8 description: "Product catalog REST API", 9 },10 });11}
This serves:
- Interactive Swagger UI at
/docs - OpenAPI JSON at
/docs/json
#Configuration
The $swagger primitive accepts these options:
| Option | Default | Description |
|---|---|---|
info |
{ title: "API Documentation", version: "1.0.0" } |
OpenAPI info block |
prefix |
"/docs" |
Base path for documentation endpoints |
disabled |
false |
Disable documentation entirely |
excludeTags |
[] |
Tag names to hide from documentation |
ui |
true |
Enable/disable Swagger UI, or pass UI options (root, persistAuthorization, initOAuth) |
rewrite |
- | Function to modify the generated OpenAPI document |
servers |
server hostname | OpenAPI servers array |
ui: false serves the JSON document alone. Pass an object to configure the UI: persistAuthorization (default true) keeps the token you enter across reloads, and root points at your own Swagger UI assets. Only the first $swagger in the app is read, and on a serverless runtime only the JSON document and the UI's initializer are served, since there is no filesystem to serve the UI's assets from.
#Custom Prefix
1docs = $swagger({2 prefix: "/api-docs",3 info: { title: "My API", version: "2.0.0" },4});5// Swagger UI at /api-docs, JSON at /api-docs/json
#Excluding Tags
Tags correspond to action groups (class names by default). Hide internal endpoints:
1docs = $swagger({2 info: { title: "Public API", version: "1.0.0" },3 excludeTags: ["InternalController", "admin"],4});
#Rewriting the Document
Modify the generated OpenAPI document before it is served:
1docs = $swagger({ 2 info: { title: "My API", version: "1.0.0" }, 3 rewrite: (doc) => { 4 doc.components ??= {}; 5 doc.components.securitySchemes = { 6 apiKey: { 7 type: "apiKey", 8 in: "header", 9 name: "X-API-Key",10 },11 };12 },13});
#How Actions Map to OpenAPI
Each $action with a schema becomes an OpenAPI operation; an action without one is left out of the document:
- Operation ID: The action name (property key or explicit
name). - Tag: The action group (class name or explicit
group). Colons render as separators: groupadmin:filesbecomes tagadmin / files, whileexcludeTagsstill matches the rawadmin:files. - Summary/Description: From the
summaryanddescriptionoptions on$action. Importingalepha/server/swaggeraddssummary,hideanddeprecatedto the$actionoptions:hide: truedrops the operation from the document,deprecated: trueflags it. - Parameters: Generated from
schema.params(path) andschema.query(query). A query parameter isrequiredunless it is optional or has a default, and a schema'sexample(or firstexamplesentry) is carried over. - Request Body: Generated from
schema.body. Objects becomeapplication/json; a body with az.file()orz.stream()field becomesmultipart/form-data, the file as abinarystring. - Response: Generated from
schema.response. Objects and arrays areapplication/json, strings and scalarstext/plain(as the router serves them), az.file()application/octet-stream. A400 Bad Requestis added when the action validates a body, query or params, and an action with noresponseschema is documented as204 No Content.
A schema carrying a title in its metadata (z.object({...}).meta({ title: "Product" })) is hoisted into components/schemas and referenced with $ref, so a shared schema appears once in the document.
Schema descriptions propagate to OpenAPI field descriptions:
1schema: {2 body: z.object({3 email: z.email().describe("User's email address"),4 age: z.integer().describe("User's age in years"),5 }),6}
#Security in OpenAPI
Actions that carry the $secure middleware (use: [$secure(...)]) are documented with a Bearer JWT security requirement; the bearerAuth scheme is emitted whenever at least one such action exists. Actions without $secure carry no security requirement.
The test is not the middleware's name but its MIDDLEWARE_PROTECTED flag, so a middleware of your own that sets it is documented the same way. Every secured action also gets a 401 Unauthorized response. Only the action's own use counts: a $secure() applied through a path-scoped $middleware gates the route and documents nothing.
#Documented Errors
An action that declares errors documents the responses it can actually give:
- the statuses its middlewares declare in their
MIDDLEWARE_RESPONSESmeta ($secure()401, plus 403 with roles or permissions;$rateLimit429); - each declared error under its status, with its code and
dataschema, as aoneOfwhen two share a status; - the
500any undeclared error becomes.
An action with a timeout, its own or the global one, documents the 503 of its TimeoutError, with or without errors. Without errors, no other error response is documented beyond the 400 and the 401 of a secured action.
Keys you never want in the document can be stripped everywhere with the swaggerOptions atom: alepha.store.mut(swaggerOptions, (o) => ({ ...o, excludeKeys: ["internalNote"] })).
#OAuth Configuration
Pass OAuth initialization options for the Swagger UI:
1docs = $swagger({ 2 info: { title: "My API", version: "1.0.0" }, 3 ui: { 4 initOAuth: { 5 clientId: "my-client-id", 6 appName: "My App", 7 usePkceWithAuthorizationCodeGrant: true, 8 }, 9 },10});