Skip to main content

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"

  1. Navigate to /api-docs
  2. Find your endpoint
  3. Click "Try it out"
  4. Fill in parameters
  5. Click "Execute"
  6. View response

2. Export to Postman

  1. Open Swagger UI
  2. Get openapi.json from docs/openapi.json
  3. In Postman: File → Import → Select file
  4. 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:

  1. File name ends with .doc.ts
  2. File is in docs/routes/ directory
  3. Exports documentation constant
  4. Ran npm run docs:build
  5. 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.ts file per route file
  • Unique operationId for each endpoint
  • Clear, descriptive summary (1-5 words)
  • Detailed description explaining 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


Questions? Check the GitHub Discussions or open an issue.