Features
Docs
CLI
Benchmarks
Examples
GitHub

© 2024 MoroJs

OpenAPI Documentation

Automatic OpenAPI 3.0 specification generation from Zod schemas with interactive Swagger UI documentation.

Zero Configuration Documentation

Automatic Generation

OpenAPI specs generated automatically from Zod schemas

Interactive UI

Built-in Swagger UI with Try It Out functionality

Type Safety

Full TypeScript inference from schemas to docs

Standards Compliant

OpenAPI 3.0 specification with JSON/YAML export

Quick Start

Enable OpenAPI Documentation

typescript

1import { createApp, z } from '@morojs/moro';
2
3const app = 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 documentation

That's it!

MoroJS automatically generates OpenAPI specs from your Zod schemas and route definitions. No manual configuration needed.

Configuration Options

Full Configuration Example

typescript

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});

Route Documentation Methods

Adding Descriptions and Tags

typescript

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 Schema Documentation

typescript

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  });

Query Parameters Documentation

typescript

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  });

Path Parameters Documentation

typescript

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  });

Headers Documentation

typescript

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  });

Accessing Documentation

Interactive UI

Swagger UI at /docs

JSON Export

OpenAPI JSON spec

YAML Export

OpenAPI YAML spec

Programmatic Access to OpenAPI Spec

typescript

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});

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

Complete Example

Full API with Automatic Documentation

typescript

1import { createApp, z } from '@morojs/moro';
2
3const app = 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});

Advanced Features

Schema-First Route Definition

typescript

1app.route({
2  method: 'GET',
3  path: '/users/search',
4  validation: {
5    query: z.object({
6      q: z.string().min(1),
7      type: z.enum(['name', 'email']).default('name'),
8      exact: z.coerce.boolean().default(false)
9    })
10  },
11  cache: { ttl: 120 },
12  description: 'Search users by name or email',
13  tags: ['users', 'search'],
14  handler: (req, res) => {
15    return {
16      success: true,
17      query: req.query
18    };
19  }
20});

Complex Nested Schemas

typescript

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  });

Authentication Documentation

typescript

1// Routes with auth are automatically documented with security requirements
2app.get('/admin/users')
3  .auth({ roles: ['admin'] })
4  .describe('Admin-only endpoint')
5  .tag('admin', 'users')
6  .handler((req, res) => {
7    return { success: true, data: [] };
8  });

Rate Limiting and Caching in Docs

typescript

1// Rate limits and caching are included in documentation
2app.post('/api/data')
3  .rateLimit({ requests: 10, window: 60000 })
4  .cache({ ttl: 300 })
5  .describe('Create data (rate limited, cached for 5min)')
6  .tag('api')
7  .handler((req, res) => {
8    return { success: true };
9  });

Best Practices

Add Descriptions

Use .describe() on all routes to make your API self-documenting

Use Tags

Group related endpoints with .tag() for better organization

Include Examples

Enable includeExamples: true for better developer experience

Document Auth

Use .auth() to automatically document security requirements

Multiple Servers

Configure servers array to help users understand different environments

Version Your API

Use semantic versioning in the version field

Installation

OpenAPI documentation requires the swagger-ui-dist package:

Install Dependencies

typescript

1npm install @morojs/moro zod swagger-ui-dist

The swagger-ui-dist dependency is optional and only needed if you want to enable interactive documentation.

Related Topics