You wrote your schemas. You defined your actions. Now comes the boring part: documenting your API so other developers can use it.
Just kidding. Alepha already did it for you.
Every $action you create is automatically documented. No YAML files. No @ApiProperty() decorators. No separate documentation step.
Just navigate to /docs and you'll see a full Swagger UI:
http://localhost:3000/docs
That's it. Your entire API — every endpoint, every parameter, every request body, every response — rendered in an interactive explorer. It updates in real-time as you code.
Everything you put in your schema shows up in the docs:
1class UserController { 2 group = "users"; 3 4 list = $action({ 5 group: this.group, 6 path: "/users", 7 description: "List all users with optional filtering and pagination.", 8 schema: { 9 query: t.object({10 page: t.optional(t.integer({ minimum: 1, default: 1 })),11 limit: t.optional(t.integer({ minimum: 1, maximum: 100, default: 20 })),12 role: t.optional(t.enum(["admin", "user", "guest"])),13 }),14 response: t.object({15 users: t.array(t.object({16 id: t.uuid(),17 name: t.text(),18 email: t.email(),19 role: t.enum(["admin", "user", "guest"]),20 })),21 total: t.integer(),22 page: t.integer(),23 }),24 },25 handler: async ({ query }) => {26 // ...27 },28 });29}
In Swagger UI, this becomes:
users (from group)list (from property name)page, limit, role — with types, defaults, and constraintsThe group property organizes your endpoints into sections:
1class OrderController {2 group = "orders";3 4 list = $action({ group: this.group, /* ... */ });5 create = $action({ group: this.group, /* ... */ });6 get = $action({ group: this.group, /* ... */ });7 cancel = $action({ group: this.group, /* ... */ });8}
1class PaymentController {2 group = "payments";3 4 charge = $action({ group: this.group, /* ... */ });5 refund = $action({ group: this.group, /* ... */ });6}
In Swagger UI, you get collapsible sections: orders (4 endpoints) and payments (2 endpoints). Clean and navigable.
If you don't set a group, it defaults to the class name.
The description field adds context to your endpoints:
1createUser = $action({2 description: "Creates a new user account. Sends a welcome email upon success.",3 schema: { /* ... */ },4 handler: async ({ body }) => { /* ... */ },5});
Short and useful. Tell developers what the endpoint does, not how it does it.
You can add descriptions to individual fields too:
1const createUserSchema = t.object({ 2 email: t.email({ description: "Must be unique across all users" }), 3 password: t.text({ 4 minLength: 8, 5 description: "Minimum 8 characters. We recommend using a password manager." 6 }), 7 role: t.optional(t.enum(["user", "admin"], { 8 description: "Defaults to 'user' if not specified" 9 })),10});
These descriptions appear in the Swagger UI schema viewer. Helpful for complex fields.
Swagger UI isn't just documentation — it's an API playground.
No Postman. No curl. Just click and test. Perfect for quick debugging or demoing to stakeholders.
If your API uses authentication (via alepha/server/security), Swagger UI shows a lock icon. Click "Authorize" to add your token, and all subsequent requests include it.
1import { $issuer } from "alepha/security";2 3const apiIssuer = $issuer({4 name: "api",5 // ...6});
Protected endpoints show which issuer they require. Swagger handles the rest.
Need the raw OpenAPI spec? It's available at:
http://localhost:3000/docs/openapi.json
Use it to:
The spec follows OpenAPI 3.0 and includes everything: paths, schemas, security definitions, and more.
Configure via environment variables or the Swagger provider:
1import { Alepha, run } from "alepha"; 2import { SwaggerProvider } from "alepha/server/swagger"; 3 4const app = Alepha.create().with({ 5 provide: SwaggerProvider, 6 config: { 7 title: "My Awesome API", 8 version: "2.1.0", 9 description: "The API that powers everything.",10 },11});12 13run(app);
Some endpoints shouldn't appear in public docs. Use disabled or create separate API surfaces:
1internalHealthCheck = $action({2 group: "internal",3 path: "/internal/health",4 // This still works, just won't be in public docs if you filter by group5 handler: async () => ({ status: "ok" }),6});
API documentation is usually an afterthought. It gets outdated. It lies. Developers stop trusting it.
With Alepha, documentation is generated from the same schemas that validate your requests and serialize your responses. It cannot lie. If the docs say a field is required, it's required. If they say the response has three fields, it has three fields.
Your schemas are your documentation. Keep them accurate, and the docs stay accurate automatically.
| URL | Purpose |
|---|---|
/docs |
Interactive Swagger UI |
/docs/openapi.json |
Raw OpenAPI 3.0 specification |
| Option | Where | Purpose |
|---|---|---|
group |
$action |
Groups endpoints into tags |
description |
$action |
Endpoint description |
description |
Schema fields | Field-level documentation |
name |
$action |
Override the operation name |