API Documentation Guide
This guide shows you how to document your API endpoints using OpenAPI/Swagger in JifiJs.
Quick Start
1. Generate a Resource
First, generate your resource with the CLI:
npm run g resource product
This creates the controller, service, model, routes, and validation files.
2. Create Documentation File
Create a documentation file in docs/routes/:
touch docs/routes/app-product.doc.ts
3. Write the Documentation
Copy this template and customize it:
/**
* OpenAPI Documentation for Product routes
* Corresponds to: routes/app/product.route.ts
*/
import OpenAPIHelper from '../../utils/helpers/openapi.helper';
import { RouteDocumentation } from '../../src/types/openapi.types';
export const documentation: RouteDocumentation = OpenAPIHelper.createRouteDoc(
// Paths
OpenAPIHelper.mergePaths(
// List all products
OpenAPIHelper.GET('/product', {
tags: ['Product'],
summary: 'List all products',
description: 'Get a paginated list of all products',
operationId: 'listProducts',
security: OpenAPIHelper.security.both,
parameters: [
{
name: 'page',
in: 'query',
description: 'Page number (1-based)',
schema: { type: 'integer', default: 1, minimum: 1 }
},
{
name: 'limit',
in: 'query',
description: 'Items per page',
schema: { type: 'integer', default: 20, minimum: 1, maximum: 100 }
},
{
name: 'search',
in: 'query',
description: 'Search by product name',
schema: { type: 'string' }
},
],
responses: {
'200': {
description: 'Products retrieved successfully',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: { type: 'boolean', example: false },
message: { type: 'string', example: 'Products retrieved successfully' },
data: {
type: 'object',
properties: {
content: {
type: 'array',
items: { $ref: '#/components/schemas/Product' }
},
pagination: {
type: 'object',
properties: {
total: { type: 'integer', example: 100 },
page: { type: 'integer', example: 1 },
limit: { type: 'integer', example: 20 },
totalPages: { type: 'integer', example: 5 },
hasNextPage: { type: 'boolean', example: true },
hasPrevPage: { type: 'boolean', example: false },
}
}
}
}
}
}
}
}
},
'401': OpenAPIHelper.responses.Unauthorized,
}
}),
// Get single product
OpenAPIHelper.GET('/product/{id}', {
tags: ['Product'],
summary: 'Get product by ID',
description: 'Retrieve a single product by its ID',
operationId: 'getProduct',
security: OpenAPIHelper.security.both,
parameters: [
{
name: 'id',
in: 'path',
required: true,
description: 'Product ID (MongoDB ObjectId)',
schema: { type: 'string', pattern: '^[0-9a-fA-F]{24}$' }
}
],
responses: {
'200': {
description: 'Product found',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: { type: 'boolean', example: false },
message: { type: 'string', example: 'Product found' },
data: { $ref: '#/components/schemas/Product' }
}
}
}
}
},
'404': OpenAPIHelper.responses.NotFound,
'401': OpenAPIHelper.responses.Unauthorized,
}
}),
// Create product
OpenAPIHelper.POST('/product', {
tags: ['Product'],
summary: 'Create new product',
description: 'Create a new product with the provided data',
operationId: 'createProduct',
security: OpenAPIHelper.security.both,
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['name', 'price', 'category'],
properties: {
name: {
type: 'string',
minLength: 3,
maxLength: 100,
example: 'Gaming Laptop'
},
description: {
type: 'string',
maxLength: 1000,
example: 'High-performance laptop for gaming and productivity'
},
price: {
type: 'number',
minimum: 0,
example: 1299.99
},
category: {
type: 'string',
enum: ['Electronics', 'Clothing', 'Books', 'Home', 'Sports'],
example: 'Electronics'
},
stock: {
type: 'integer',
minimum: 0,
example: 50
},
tags: {
type: 'array',
items: { type: 'string' },
example: ['gaming', 'laptop', 'tech']
}
}
}
}
}
},
responses: {
'201': {
description: 'Product created successfully',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: { type: 'boolean', example: false },
message: { type: 'string', example: 'Product created successfully' },
data: { $ref: '#/components/schemas/Product' }
}
}
}
}
},
'422': OpenAPIHelper.responses.ValidationError,
'401': OpenAPIHelper.responses.Unauthorized,
}
}),
// Update product
OpenAPIHelper.PUT('/product/{id}', {
tags: ['Product'],
summary: 'Update product',
description: 'Update an existing product',
operationId: 'updateProduct',
security: OpenAPIHelper.security.both,
parameters: [
{
name: 'id',
in: 'path',
required: true,
description: 'Product ID',
schema: { type: 'string', pattern: '^[0-9a-fA-F]{24}$' }
}
],
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
name: { type: 'string', minLength: 3, maxLength: 100 },
description: { type: 'string', maxLength: 1000 },
price: { type: 'number', minimum: 0 },
category: {
type: 'string',
enum: ['Electronics', 'Clothing', 'Books', 'Home', 'Sports']
},
stock: { type: 'integer', minimum: 0 },
tags: {
type: 'array',
items: { type: 'string' }
}
}
}
}
}
},
responses: {
'200': {
description: 'Product updated successfully',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: { type: 'boolean', example: false },
message: { type: 'string', example: 'Product updated successfully' },
data: { $ref: '#/components/schemas/Product' }
}
}
}
}
},
'404': OpenAPIHelper.responses.NotFound,
'422': OpenAPIHelper.responses.ValidationError,
'401': OpenAPIHelper.responses.Unauthorized,
}
}),
// Delete product
OpenAPIHelper.DELETE('/product/{id}', {
tags: ['Product'],
summary: 'Delete product',
description: 'Soft delete a product (sets deleted_at timestamp)',
operationId: 'deleteProduct',
security: OpenAPIHelper.security.both,
parameters: [
{
name: 'id',
in: 'path',
required: true,
description: 'Product ID',
schema: { type: 'string', pattern: '^[0-9a-fA-F]{24}$' }
}
],
responses: {
'200': {
description: 'Product deleted successfully',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: { type: 'boolean', example: false },
message: { type: 'string', example: 'Product deleted successfully' }
}
}
}
}
},
'404': OpenAPIHelper.responses.NotFound,
'401': OpenAPIHelper.responses.Unauthorized,
}
})
),
// Components - shared schemas
{
schemas: {
Product: {
type: 'object',
properties: {
_id: {
type: 'string',
pattern: '^[0-9a-fA-F]{24}$',
example: '65abc123def456789012abcd'
},
name: {
type: 'string',
example: 'Gaming Laptop'
},
description: {
type: 'string',
example: 'High-performance laptop for gaming and productivity'
},
price: {
type: 'number',
example: 1299.99
},
category: {
type: 'string',
example: 'Electronics'
},
stock: {
type: 'integer',
example: 50
},
tags: {
type: 'array',
items: { type: 'string' },
example: ['gaming', 'laptop', 'tech']
},
created_at: {
type: 'string',
format: 'date-time',
example: '2024-01-15T10:30:00.000Z'
},
updated_at: {
type: 'string',
format: 'date-time',
example: '2024-01-15T10:30:00.000Z'
},
deleted_at: {
type: 'string',
nullable: true,
example: null
}
}
}
}
},
// Tags
[
{
name: 'Product',
description: 'Product catalog management endpoints'
}
]
);
4. Build the Documentation
Generate the OpenAPI JSON file:
npm run docs:build
You should see:
✅ OpenAPI document generated successfully!
Output: /path/to/docs/openapi.json
Paths: 30
Tags: 6
Schemas: 15
5. Start the Server
npm run dev
6. View in Swagger UI
Open your browser to:
http://localhost:3000/api-docs
You should see your Product endpoints in the Swagger UI!
Common Patterns
Pagination Parameters
Standard pagination parameters:
parameters: [
{
name: 'page',
in: 'query',
description: 'Page number (1-based)',
schema: { type: 'integer', default: 1, minimum: 1 }
},
{
name: 'limit',
in: 'query',
description: 'Items per page',
schema: { type: 'integer', default: 20, minimum: 1, maximum: 100 }
},
{
name: 'sort',
in: 'query',
description: 'Sort field (prefix with - for descending)',
schema: { type: 'string', example: '-created_at' }
},
]
Filter Parameters
Common filtering patterns:
parameters: [
{
name: 'status',
in: 'query',
description: 'Filter by status',
schema: {
type: 'string',
enum: ['active', 'inactive', 'pending']
}
},
{
name: 'min_price',
in: 'query',
description: 'Minimum price',
schema: { type: 'number', minimum: 0 }
},
{
name: 'max_price',
in: 'query',
description: 'Maximum price',
schema: { type: 'number', minimum: 0 }
},
{
name: 'created_after',
in: 'query',
description: 'Filter by creation date (ISO 8601)',
schema: { type: 'string', format: 'date-time' }
},
]
File Upload Endpoint
Document file upload with multipart/form-data:
OpenAPIHelper.POST('/upload', {
tags: ['Upload'],
summary: 'Upload file',
description: 'Upload a single file',
operationId: 'uploadFile',
security: OpenAPIHelper.security.both,
requestBody: {
required: true,
content: {
'multipart/form-data': {
schema: {
type: 'object',
required: ['file'],
properties: {
file: {
type: 'string',
format: 'binary',
description: 'File to upload (max 10MB)'
},
description: {
type: 'string',
description: 'Optional file description'
}
}
}
}
}
},
responses: {
'201': OpenAPIHelper.responses.Created,
'422': OpenAPIHelper.responses.ValidationError,
}
})
Array Response
Document endpoints that return arrays:
responses: {
'200': {
description: 'Categories retrieved',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: { type: 'boolean', example: false },
message: { type: 'string', example: 'Success' },
data: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
count: { type: 'integer' }
}
}
}
}
}
}
}
}
}
Nested Objects
Document complex nested structures:
schema: {
type: 'object',
properties: {
user: {
type: 'object',
properties: {
profile: {
type: 'object',
properties: {
first_name: { type: 'string' },
last_name: { type: 'string' },
avatar: {
type: 'object',
properties: {
url: { type: 'string', format: 'uri' },
size: { type: 'integer' }
}
}
}
}
}
}
}
}
Testing Your Documentation
1. Use Swagger UI "Try It Out"
- Navigate to
/api-docs - Find your endpoint
- Click "Try it out"
- Fill in parameters
- Click "Execute"
- View response
2. Export to Postman
- Open Swagger UI
- Get
openapi.jsonfromdocs/openapi.json - In Postman: File → Import → Select file
- All endpoints imported
3. Validate OpenAPI Spec
Use online validators:
# Using Swagger Editor online
# Paste content of openapi.json at:
# https://editor.swagger.io/
# Or use CLI validator
npx @apidevtools/swagger-cli validate docs/openapi.json
Advanced Techniques
Reusable Parameters
Define once, reference everywhere:
// In components
components: {
parameters: {
PageParam: {
name: 'page',
in: 'query',
description: 'Page number',
schema: { type: 'integer', default: 1 }
},
LimitParam: {
name: 'limit',
in: 'query',
description: 'Items per page',
schema: { type: 'integer', default: 20 }
}
}
}
// In operation
parameters: [
{ $ref: '#/components/parameters/PageParam' },
{ $ref: '#/components/parameters/LimitParam' }
]
Enum Values with Descriptions
Document enum values clearly:
schema: {
type: 'string',
enum: ['pending', 'processing', 'completed', 'failed'],
description: `Order status:
- pending: Order placed but not yet processed
- processing: Order is being prepared
- completed: Order has been delivered
- failed: Order processing failed`,
example: 'pending'
}
Conditional Fields
Document optional vs required fields:
schema: {
type: 'object',
required: ['email', 'password'], // Always required
properties: {
email: { type: 'string', format: 'email' },
password: { type: 'string', format: 'password' },
remember_me: { // Optional
type: 'boolean',
default: false,
description: 'Stay logged in for 30 days'
}
}
}
Response with Headers
Document response headers:
responses: {
'200': {
description: 'Success',
headers: {
'X-Rate-Limit-Remaining': {
description: 'Remaining requests in current window',
schema: { type: 'integer' }
},
'X-Rate-Limit-Reset': {
description: 'Time when rate limit resets (Unix timestamp)',
schema: { type: 'integer' }
}
},
content: {
'application/json': {
schema: { /* ... */ }
}
}
}
}
Troubleshooting
Documentation Not Showing Up
Check these steps:
- File name ends with
.doc.ts - File is in
docs/routes/directory - Exports
documentationconstant - Ran
npm run docs:build - Restarted server
Syntax Errors in OpenAPI
Common issues:
// ❌ Wrong - missing required fields
OpenAPIHelper.GET('/product', {
tags: ['Product'],
// Missing: summary, operationId, security, responses
})
// ✅ Correct - all required fields
OpenAPIHelper.GET('/product', {
tags: ['Product'],
summary: 'List products',
operationId: 'listProducts',
security: OpenAPIHelper.security.both,
responses: {
'200': OpenAPIHelper.responses.Success
}
})
Request Body Not Showing
Make sure requestBody is properly structured:
// ❌ Wrong
requestBody: {
schema: { /* ... */ } // Missing content wrapper
}
// ✅ Correct
requestBody: {
required: true,
content: {
'application/json': {
schema: { /* ... */ }
}
}
}
Best Practices Checklist
- One
.doc.tsfile per route file - Unique
operationIdfor each endpoint - Clear, descriptive
summary(1-5 words) - Detailed
descriptionexplaining purpose - All parameters documented with examples
- Request body with realistic examples
- All possible responses documented
- Proper security schemes applied
- Shared schemas in components for reusability
- Tags for logical grouping
- Validation rules match Joi schemas
Next Steps
- API Documentation Reference - Full API docs feature
- Authentication - Secure your endpoints
- Testing Guide - Test documented endpoints
- Deployment - Deploy with documentation
Questions? Check the GitHub Discussions or open an issue.