Core Systems
OpenAPI Documentation
Automatic OpenAPI specs from Zod schemas. Interactive Swagger UI. Zero configuration needed.
Overview
1import { createApp, z } from '@morojs/moro';
2
3const app = await createApp();
4
5// Define your API routes with validation
6app.post('/users')
7 .body(z.object({
8 name: z.string().min(2),
9 email: z.string().email()
10 }))
11 .describe('Create a new user')
12 .tag('users')
13 .handler((req, res) => {
14 return { success: true, data: req.body };
15 });
16
17// Enable documentation
18app.enableDocs({
19 title: 'My API',
20 version: '1.0.0',
21 description: 'API documentation',
22 basePath: '/docs'
23});
24
25app.listen(3000);
26// Visit http://localhost:3000/docs for interactive documentationWithout automatic documentation, you're manually writing specs that get out of sync. With MoroJS, your docs are always up to date. Traditional OpenAPI setup requires manual spec writing and constant updates. We generate it automatically from your code.
Without Auto-Docs
- Manual spec writing
- Docs get out of sync
- No interactive UI
- Time-consuming maintenance
With MoroJS
- Automatic spec generation
- Always in sync with code
- Interactive Swagger UI
- Zero maintenance
1// .describe() - Add description to your route
2app.get('/users/:id')
3 .describe('Get user by ID')
4 .tag('users', 'read')
5 .handler((req, res) => {
6 // handler code
7 });
8
9// .tag() - Group related endpoints
10app.post('/users')
11 .tag('users', 'create')
12 .describe('Create a new user')
13 .handler((req, res) => {
14 // handler code
15 });Automatic
OpenAPI specs generated automatically from Zod schemas
Interactive
Built-in Swagger UI with Try It Out functionality
Type Safe
Full TypeScript inference from schemas to docs
Standards
OpenAPI 3.0 specification with JSON/YAML export
How It Works
MoroJS automatically generates OpenAPI specifications from your Zod schemas and route definitions. When you define routes with validation, descriptions, and tags, the OpenAPI spec is built automatically. The interactive Swagger UI is available at the configured base path.
Configuration Options
1app.enableDocs({
2 // Required fields
3 title: 'User Management API',
4 version: '1.0.0',
5
6 // Optional fields
7 description: 'Complete API for user management with authentication',
8 basePath: '/docs', // Default: '/docs'
9
10 // Multiple server environments
11 servers: [
12 { url: 'http://localhost:3000', description: 'Development' },
13 { url: 'https://staging.example.com', description: 'Staging' },
14 { url: 'https://api.example.com', description: 'Production' }
15 ],
16
17 // Contact information
18 contact: {
19 name: 'API Support Team',
20 email: 'support@example.com',
21 url: 'https://example.com/support'
22 },
23
24 // License information
25 license: {
26 name: 'MIT',
27 url: 'https://opensource.org/licenses/MIT'
28 },
29
30 // Swagger UI customization
31 swaggerUI: {
32 title: 'My API - Interactive Documentation',
33 enableTryItOut: true, // Enable "Try it out" button
34 enableFilter: true, // Enable filter/search
35 enableDeepLinking: true // Enable deep linking to operations
36 },
37
38 // Additional options
39 includeExamples: true, // Include request/response examples
40 includeSchemas: true, // Include schema definitions
41 enableAuth: true // Include authentication docs
42});1const UserSchema = z.object({
2 name: z.string().min(2).max(50),
3 email: z.string().email(),
4 age: z.number().min(18).optional(),
5 role: z.enum(['admin', 'user', 'moderator']).default('user')
6});
7
8// Schemas are automatically converted to OpenAPI format
9app.post('/users')
10 .body(UserSchema) // Automatically documented
11 .describe('Create a new user')
12 .tag('users')
13 .handler((req, res) => {
14 return { success: true, data: req.body };
15 });1app.get('/users')
2 .query(z.object({
3 limit: z.coerce.number().min(1).max(100).default(10),
4 offset: z.coerce.number().min(0).default(0),
5 search: z.string().optional(),
6 role: z.enum(['admin', 'user']).optional()
7 }))
8 .describe('Get users with pagination and filtering')
9 .tag('users')
10 .handler((req, res) => {
11 // Fully typed query parameters
12 const { limit, offset, search, role } = req.query;
13 // handler code
14 });Advanced Features
1app.get('/users/:id')
2 .params(z.object({
3 id: z.string().uuid('Invalid user ID format')
4 }))
5 .describe('Get user by UUID')
6 .tag('users')
7 .handler((req, res) => {
8 // Fully typed path parameters
9 const userId = req.params.id;
10 // handler code
11 });1app.get('/api/data')
2 .headers(z.object({
3 'x-api-key': z.string(),
4 'x-request-id': z.string().uuid().optional()
5 }))
6 .describe('Get protected data')
7 .tag('api')
8 .handler((req, res) => {
9 // Fully typed headers
10 const apiKey = req.headers['x-api-key'];
11 // handler code
12 });1const PostSchema = z.object({
2 title: z.string().min(5).max(200),
3 content: z.string().min(10),
4 author: z.object({
5 id: z.string().uuid(),
6 name: z.string().min(2)
7 }),
8 tags: z.array(z.string()).min(1).max(10),
9 metadata: z.object({
10 category: z.enum(['tech', 'lifestyle', 'business']),
11 publishAt: z.string().datetime().optional(),
12 featured: z.boolean().default(false)
13 }),
14 settings: z.object({
15 allowComments: z.boolean().default(true),
16 visibility: z.enum(['public', 'private', 'draft'])
17 }).optional()
18});
19
20app.post('/posts')
21 .body(PostSchema)
22 .describe('Create post with complex validation')
23 .tag('posts')
24 .handler((req, res) => {
25 return { success: true, data: req.body };
26 });1// Get OpenAPI spec as object
2app.get('/api/openapi.json', (req, res) => {
3 res.setHeader('Content-Type', 'application/json');
4 return app.getOpenAPISpec();
5});
6
7// Get OpenAPI spec as YAML
8app.get('/api/openapi.yaml', (req, res) => {
9 res.setHeader('Content-Type', 'text/yaml');
10 res.send(app.getDocsYAML());
11});
12
13// Get OpenAPI spec as JSON string
14app.get('/api/docs.json', (req, res) => {
15 res.setHeader('Content-Type', 'application/json');
16 res.send(app.getDocsJSON());
17});Complete Example
1import { createApp, z } from '@morojs/moro';
2
3const app = await createApp();
4
5// Define schemas
6const UserSchema = z.object({
7 name: z.string().min(2).max(50),
8 email: z.string().email(),
9 age: z.number().min(18).optional(),
10 role: z.enum(['admin', 'user']).default('user')
11});
12
13const UserParamsSchema = z.object({
14 id: z.string().uuid()
15});
16
17const UserQuerySchema = z.object({
18 limit: z.coerce.number().min(1).max(100).default(10),
19 offset: z.coerce.number().min(0).default(0),
20 search: z.string().optional()
21});
22
23// Create user
24app.post('/users')
25 .body(UserSchema)
26 .rateLimit({ requests: 20, window: 60000 })
27 .describe('Create a new user')
28 .tag('users', 'create')
29 .handler((req, res) => {
30 res.status(201);
31 return { success: true, data: req.body };
32 });
33
34// List users
35app.get('/users')
36 .query(UserQuerySchema)
37 .cache({ ttl: 60 })
38 .describe('Get users with pagination')
39 .tag('users', 'list')
40 .handler((req, res) => {
41 return {
42 success: true,
43 data: [],
44 pagination: {
45 limit: req.query.limit,
46 offset: req.query.offset
47 }
48 };
49 });
50
51// Get user by ID
52app.get('/users/:id')
53 .params(UserParamsSchema)
54 .describe('Get user by UUID')
55 .tag('users', 'detail')
56 .handler((req, res) => {
57 return {
58 success: true,
59 data: { id: req.params.id }
60 };
61 });
62
63// Update user
64app.put('/users/:id')
65 .params(UserParamsSchema)
66 .body(UserSchema.partial())
67 .auth({ roles: ['admin', 'user'] })
68 .describe('Update user (requires auth)')
69 .tag('users', 'update')
70 .handler((req, res) => {
71 return {
72 success: true,
73 data: { id: req.params.id, ...req.body }
74 };
75 });
76
77// Delete user
78app.delete('/users/:id')
79 .params(UserParamsSchema)
80 .auth({ roles: ['admin'] })
81 .describe('Delete user (admin only)')
82 .tag('users', 'delete', 'admin')
83 .handler((req, res) => {
84 return { success: true };
85 });
86
87// Enable comprehensive documentation
88app.enableDocs({
89 title: 'User Management API',
90 version: '1.0.0',
91 description: 'Complete API with authentication and validation',
92 basePath: '/docs',
93 servers: [
94 { url: 'http://localhost:3000', description: 'Development' },
95 { url: 'https://api.example.com', description: 'Production' }
96 ],
97 contact: {
98 name: 'API Support',
99 email: 'support@example.com'
100 },
101 license: {
102 name: 'MIT',
103 url: 'https://opensource.org/licenses/MIT'
104 },
105 swaggerUI: {
106 enableTryItOut: true,
107 enableFilter: true,
108 enableDeepLinking: true
109 }
110});
111
112app.listen(3000, () => {
113 console.log('Server: http://localhost:3000');
114 console.log('Docs: http://localhost:3000/docs');
115});Accessing Documentation
Interactive UI
Swagger UI at /docs
JSON Export
OpenAPI JSON spec
YAML Export
OpenAPI YAML spec
Available Methods
app.getOpenAPISpec()Returns the OpenAPI specification as a JavaScript object
app.getDocsJSON()Returns the OpenAPI specification as a JSON string
app.getDocsYAML()Returns the OpenAPI specification as a YAML string
Installation
Interactive OpenAPI documentation requires the optional swagger-ui-dist package. In an existing MoroJS project:
1npm install swagger-ui-distDon't have a project yet? Scaffold one with npx @morojs/cli init my-api and enable the docs feature. Swagger UI will be wired up for you.