Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on API Documentation with Swagger/OpenAPI! ๐ In this guide, weโll explore how to document your TypeScript APIs professionally and make them a joy to use.
Youโll discover how Swagger/OpenAPI can transform your API development experience. Whether youโre building REST APIs ๐, server-side applications ๐ฅ๏ธ, or microservices ๐, understanding API documentation is essential for creating robust, maintainable, and user-friendly APIs.
By the end of this tutorial, youโll feel confident documenting your TypeScript APIs like a pro! Letโs dive in! ๐โโ๏ธ
๐ Understanding API Documentation
๐ค What is Swagger/OpenAPI?
Swagger/OpenAPI is like a detailed blueprint for your house ๐ . Think of it as a comprehensive manual that tells everyone exactly how to interact with your API - what rooms (endpoints) exist, what furniture (parameters) they contain, and what you can do in each room (operations).
In TypeScript terms, OpenAPI provides a standardized way to describe your API endpoints, request/response models, and authentication methods ๐. This means you can:
- โจ Generate interactive documentation automatically
- ๐ Create client SDKs in multiple languages
- ๐ก๏ธ Validate requests and responses
- ๐ง Mock your API for testing
๐ก Why Use Swagger/OpenAPI?
Hereโs why API developers love Swagger/OpenAPI:
- Self-Documenting APIs ๐: Your code becomes the documentation
- Interactive Testing ๐ฎ: Test endpoints directly in the browser
- Code Generation ๐ค: Auto-generate client libraries
- Standardization ๐: Follow industry-standard specification
- Team Collaboration ๐ค: Frontend and backend teams stay in sync
Real-world example: Imagine building a pizza ordering API ๐. With OpenAPI, you can describe exactly how to order a pizza, what toppings are available, and what the response looks like - all in a format thatโs both human and machine readable!
๐ง Basic Syntax and Usage
๐ Simple OpenAPI Specification
Letโs start with a friendly example:
// ๐ Hello, OpenAPI!
import { OpenAPIV3 } from 'openapi-types';
// ๐จ Basic API specification
const apiSpec: OpenAPIV3.Document = {
openapi: '3.0.0',
info: {
title: 'Pizza Palace API ๐',
version: '1.0.0',
description: 'The most delicious API in town!'
},
paths: {
'/pizzas': {
get: {
summary: 'Get all pizzas ๐',
responses: {
'200': {
description: 'List of delicious pizzas',
content: {
'application/json': {
schema: {
type: 'array',
items: {
$ref: '#/components/schemas/Pizza'
}
}
}
}
}
}
}
}
},
components: {
schemas: {
Pizza: {
type: 'object',
properties: {
id: { type: 'string', example: 'pizza-123' },
name: { type: 'string', example: 'Margherita Supreme ๐
' },
price: { type: 'number', example: 15.99 },
toppings: {
type: 'array',
items: { type: 'string' },
example: ['๐
Tomato', '๐ง Mozzarella', '๐ฟ Basil']
}
},
required: ['id', 'name', 'price']
}
}
}
};
๐ก Explanation: Notice how we use emojis in our API descriptions to make them more engaging! The $ref
creates reusable schemas.
๐ฏ Express.js with Swagger Integration
Hereโs how to integrate with Express.js:
// ๐๏ธ Express server with Swagger
import express from 'express';
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
const app = express();
// ๐จ Swagger configuration
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: 'My Awesome API ๐',
version: '1.0.0',
description: 'A fantastic TypeScript API!'
},
servers: [
{
url: 'http://localhost:3000',
description: 'Development server ๐ ๏ธ'
}
]
},
apis: ['./src/routes/*.ts']
};
// ๐ Generate documentation
const specs = swaggerJsdoc(swaggerOptions);
// ๐ฎ Serve Swagger UI
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
// ๐ Sample route with JSDoc comments
/**
* @openapi
* /users:
* get:
* summary: Get all users ๐ฅ
* tags: [Users]
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
app.get('/users', (req, res) => {
res.json([
{ id: '1', name: 'Alice ๐ฉ', email: '[email protected]' },
{ id: '2', name: 'Bob ๐จ', email: '[email protected]' }
]);
});
๐ก Practical Examples
๐ Example 1: E-commerce API Documentation
Letโs build comprehensive API documentation for an online store:
// ๐๏ธ E-commerce API types
interface Product {
id: string;
name: string;
price: number;
category: string;
inStock: boolean;
description: string;
imageUrl: string;
emoji: string;
}
interface Order {
id: string;
userId: string;
items: OrderItem[];
total: number;
status: 'pending' | 'processing' | 'shipped' | 'delivered';
createdAt: Date;
}
interface OrderItem {
productId: string;
quantity: number;
price: number;
}
// ๐ OpenAPI specification
const ecommerceApiSpec: OpenAPIV3.Document = {
openapi: '3.0.0',
info: {
title: 'ShopMaster API ๐',
version: '2.0.0',
description: 'Your one-stop shop API for all things awesome!',
contact: {
name: 'API Support ๐ค',
email: '[email protected]'
}
},
servers: [
{
url: 'https://api.shopmaster.com/v2',
description: 'Production server ๐'
},
{
url: 'https://staging.shopmaster.com/v2',
description: 'Staging server ๐งช'
}
],
paths: {
'/products': {
get: {
summary: 'Get all products ๐๏ธ',
description: 'Retrieve a list of all available products',
tags: ['Products'],
parameters: [
{
name: 'category',
in: 'query',
description: 'Filter by category ๐ท๏ธ',
schema: {
type: 'string',
enum: ['electronics', 'clothing', 'books', 'home']
}
},
{
name: 'inStock',
in: 'query',
description: 'Filter by stock availability ๐ฆ',
schema: {
type: 'boolean'
}
}
],
responses: {
'200': {
description: 'Successful response',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
products: {
type: 'array',
items: { $ref: '#/components/schemas/Product' }
},
total: { type: 'number' },
page: { type: 'number' }
}
}
}
}
},
'400': {
description: 'Invalid query parameters',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' }
}
}
}
}
},
post: {
summary: 'Create a new product โ',
description: 'Add a new product to the inventory',
tags: ['Products'],
security: [{ bearerAuth: [] }],
requestBody: {
required: true,
content: {
'application/json': {
schema: { $ref: '#/components/schemas/ProductInput' }
}
}
},
responses: {
'201': {
description: 'Product created successfully ๐',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Product' }
}
}
},
'400': {
description: 'Invalid product data',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' }
}
}
},
'401': {
description: 'Unauthorized - Admin access required ๐'
}
}
}
},
'/orders': {
post: {
summary: 'Create a new order ๐ฆ',
description: 'Place a new order with multiple items',
tags: ['Orders'],
security: [{ bearerAuth: [] }],
requestBody: {
required: true,
content: {
'application/json': {
schema: { $ref: '#/components/schemas/OrderInput' }
}
}
},
responses: {
'201': {
description: 'Order created successfully ๐',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Order' }
}
}
}
}
}
}
},
components: {
schemas: {
Product: {
type: 'object',
properties: {
id: { type: 'string', example: 'prod-123' },
name: { type: 'string', example: 'Wireless Headphones ๐ง' },
price: { type: 'number', format: 'float', example: 99.99 },
category: {
type: 'string',
enum: ['electronics', 'clothing', 'books', 'home'],
example: 'electronics'
},
inStock: { type: 'boolean', example: true },
description: {
type: 'string',
example: 'Amazing wireless headphones with noise cancellation'
},
imageUrl: {
type: 'string',
format: 'uri',
example: 'https://example.com/headphones.jpg'
},
emoji: { type: 'string', example: '๐ง' }
},
required: ['id', 'name', 'price', 'category', 'inStock']
},
ProductInput: {
type: 'object',
properties: {
name: { type: 'string', minLength: 1 },
price: { type: 'number', minimum: 0 },
category: {
type: 'string',
enum: ['electronics', 'clothing', 'books', 'home']
},
description: { type: 'string' },
imageUrl: { type: 'string', format: 'uri' },
emoji: { type: 'string', maxLength: 2 }
},
required: ['name', 'price', 'category']
},
Order: {
type: 'object',
properties: {
id: { type: 'string', example: 'order-456' },
userId: { type: 'string', example: 'user-789' },
items: {
type: 'array',
items: { $ref: '#/components/schemas/OrderItem' }
},
total: { type: 'number', format: 'float', example: 199.98 },
status: {
type: 'string',
enum: ['pending', 'processing', 'shipped', 'delivered'],
example: 'pending'
},
createdAt: {
type: 'string',
format: 'date-time',
example: '2024-01-15T10:30:00Z'
}
}
},
OrderItem: {
type: 'object',
properties: {
productId: { type: 'string', example: 'prod-123' },
quantity: { type: 'integer', minimum: 1, example: 2 },
price: { type: 'number', format: 'float', example: 99.99 }
},
required: ['productId', 'quantity', 'price']
},
OrderInput: {
type: 'object',
properties: {
items: {
type: 'array',
items: {
type: 'object',
properties: {
productId: { type: 'string' },
quantity: { type: 'integer', minimum: 1 }
},
required: ['productId', 'quantity']
}
}
},
required: ['items']
},
Error: {
type: 'object',
properties: {
code: { type: 'string', example: 'INVALID_INPUT' },
message: { type: 'string', example: 'The provided data is invalid' },
details: { type: 'array', items: { type: 'string' } }
}
}
},
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'Enter your JWT token ๐'
}
}
}
};
๐ฏ Try it yourself: Add an endpoint for getting order history and include pagination parameters!
๐ฎ Example 2: Gaming API with Advanced Features
Letโs create documentation for a gaming leaderboard API:
// ๐ Gaming API with decorators
import { ApiProperty, ApiResponse, ApiTags } from '@nestjs/swagger';
@ApiTags('๐ฎ Players')
export class PlayerDto {
@ApiProperty({
description: 'Unique player identifier',
example: 'player-007'
})
id: string;
@ApiProperty({
description: 'Player username with emoji',
example: 'GamerPro ๐ฏ'
})
username: string;
@ApiProperty({
description: 'Player level',
minimum: 1,
maximum: 100,
example: 42
})
level: number;
@ApiProperty({
description: 'Total experience points',
example: 12500
})
xp: number;
@ApiProperty({
description: 'Player achievements',
type: [String],
example: ['๐ First Victory', '๐ฅ Streak Master', '๐ Diamond Rank']
})
achievements: string[];
@ApiProperty({
description: 'Online status',
enum: ['online', 'offline', 'away'],
example: 'online'
})
status: 'online' | 'offline' | 'away';
}
@ApiTags('๐ Leaderboard')
export class LeaderboardController {
@ApiResponse({
status: 200,
description: 'Top players retrieved successfully ๐',
type: [PlayerDto]
})
@ApiResponse({
status: 429,
description: 'Rate limit exceeded โฐ'
})
async getTopPlayers(): Promise<PlayerDto[]> {
return [
{
id: 'player-001',
username: 'ProGamer ๐ฏ',
level: 95,
xp: 89750,
achievements: ['๐ Champion', '๐ฅ Unstoppable', '๐ Legend'],
status: 'online'
},
{
id: 'player-002',
username: 'SpeedRunner โก',
level: 87,
xp: 76230,
achievements: ['๐ Speed Demon', 'โฑ๏ธ Time Master'],
status: 'away'
}
];
}
}
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Custom Validation and Schemas
When youโre ready to level up, try advanced schema validation:
// ๐ฏ Advanced schema with custom validation
const advancedSchema: OpenAPIV3.SchemaObject = {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
pattern: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
example: '[email protected]'
},
age: {
type: 'integer',
minimum: 13,
maximum: 120,
example: 25
},
preferences: {
type: 'object',
properties: {
theme: {
type: 'string',
enum: ['light', 'dark', 'auto'],
default: 'auto'
},
notifications: {
type: 'object',
properties: {
email: { type: 'boolean', default: true },
push: { type: 'boolean', default: false },
sms: { type: 'boolean', default: false }
},
additionalProperties: false
}
}
}
},
required: ['email', 'age'],
additionalProperties: false
};
// ๐ช Conditional schemas based on discriminator
const paymentMethodSchema: OpenAPIV3.SchemaObject = {
oneOf: [
{
type: 'object',
properties: {
type: { type: 'string', enum: ['credit_card'] },
cardNumber: { type: 'string', pattern: '^[0-9]{16}$' },
expiryDate: { type: 'string', pattern: '^(0[1-9]|1[0-2])\/[0-9]{2}$' },
cvv: { type: 'string', pattern: '^[0-9]{3,4}$' }
},
required: ['type', 'cardNumber', 'expiryDate', 'cvv']
},
{
type: 'object',
properties: {
type: { type: 'string', enum: ['paypal'] },
email: { type: 'string', format: 'email' }
},
required: ['type', 'email']
}
],
discriminator: {
propertyName: 'type'
}
};
๐๏ธ Advanced Topic 2: Code Generation and Automation
For the brave developers who want to automate everything:
// ๐ Automated client generation
import { generateApi } from 'swagger-typescript-api';
const generateClient = async () => {
const { files } = await generateApi({
name: 'MyApi.ts',
url: 'http://localhost:3000/api-docs-json',
generateClient: true,
generateRouteTypes: true,
generateResponses: true,
toJS: false,
extractRequestParams: true,
extractRequestBody: true,
extractEnums: true,
prettier: {
printWidth: 120,
tabWidth: 2,
trailingComma: 'all',
singleQuote: true
}
});
console.log('๐ Generated client files:', files);
};
// ๐ง Custom code generation with templates
const customTemplate = `
// ๐จ Auto-generated API client
export class {{className}}Api {
constructor(private baseUrl: string) {}
{{#operations}}
async {{operationId}}({{#parameters}}{{paramName}}: {{dataType}}{{#hasMore}}, {{/hasMore}}{{/parameters}}): Promise<{{returnType}}> {
const response = await fetch(\`\${this.baseUrl}{{path}}\`, {
method: '{{httpMethod}}',
headers: {
'Content-Type': 'application/json',
{{#hasAuth}}'Authorization': \`Bearer \${this.token}\`{{/hasAuth}}
},
{{#hasBody}}body: JSON.stringify({{bodyParam}}){{/hasBody}}
});
if (!response.ok) {
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
}
return response.json();
}
{{/operations}}
}
`;
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Inconsistent Documentation
// โ Wrong way - outdated documentation!
/**
* @openapi
* /users:
* get:
* summary: Get all users
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* properties:
* id: { type: string }
* name: { type: string }
*/
app.get('/users', (req, res) => {
// ๐ฅ Actual response has different structure!
res.json([
{
userId: '1',
fullName: 'Alice',
email: '[email protected]',
createdAt: new Date()
}
]);
});
// โ
Correct way - keep docs in sync!
/**
* @openapi
* /users:
* get:
* summary: Get all users ๐ฅ
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/User'
* components:
* schemas:
* User:
* type: object
* properties:
* userId: { type: string, example: "user-123" }
* fullName: { type: string, example: "Alice Wonder" }
* email: { type: string, format: email }
* createdAt: { type: string, format: date-time }
*/
app.get('/users', (req, res) => {
res.json([
{
userId: 'user-123',
fullName: 'Alice Wonder',
email: '[email protected]',
createdAt: new Date().toISOString()
}
]);
});
๐คฏ Pitfall 2: Missing Error Responses
// โ Dangerous - no error documentation!
/**
* @openapi
* /users/{id}:
* get:
* summary: Get user by ID
* responses:
* 200:
* description: User found
*/
app.get('/users/:id', (req, res) => {
// ๐ฅ What if user not found? No documentation!
const user = findUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
// โ
Safe - document all possible responses!
/**
* @openapi
* /users/{id}:
* get:
* summary: Get user by ID ๐
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User identifier
* responses:
* 200:
* description: User found successfully โ
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* 404:
* description: User not found โ
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 400:
* description: Invalid user ID format โ ๏ธ
*/
app.get('/users/:id', (req, res) => {
const { id } = req.params;
if (!isValidId(id)) {
return res.status(400).json({
error: 'INVALID_ID',
message: 'User ID must be a valid format'
});
}
const user = findUserById(id);
if (!user) {
return res.status(404).json({
error: 'USER_NOT_FOUND',
message: 'No user found with the specified ID'
});
}
res.json(user);
});
๐ ๏ธ Best Practices
- ๐ฏ Keep Documentation Updated: Use automation to sync docs with code
- ๐ Write Descriptive Summaries: Make each endpointโs purpose clear
- ๐ก๏ธ Document Error Cases: Include all possible error responses
- ๐จ Use Examples Liberally: Show real data in your examples
- โจ Version Your API: Use semantic versioning for API changes
- ๐ง Validate Your Spec: Use OpenAPI validators
- ๐ Generate Clients: Automate client library generation
- ๐ Monitor Usage: Track which endpoints are actually used
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Complete Movie Database API Documentation
Create comprehensive API documentation for a movie database:
๐ Requirements:
- โ Movie CRUD operations (Create, Read, Update, Delete)
- ๐ฌ Movie search with filters (genre, year, rating)
- ๐ค User authentication and authorization
- โญ Movie rating and review system
- ๐ Statistics endpoint for admin users
- ๐จ Use fun movie-themed examples with emojis!
๐ Bonus Points:
- Add pagination for large result sets
- Include file upload for movie posters
- Create webhook endpoints for real-time updates
- Add rate limiting documentation
๐ก Solution
๐ Click to see solution
// ๐ฏ Complete Movie Database API Documentation!
const movieApiSpec: OpenAPIV3.Document = {
openapi: '3.0.0',
info: {
title: 'CinemaHub API ๐ฌ',
version: '1.0.0',
description: 'The ultimate movie database API for film enthusiasts!',
contact: {
name: 'CinemaHub Support ๐ญ',
email: '[email protected]'
},
license: {
name: 'MIT',
url: 'https://opensource.org/licenses/MIT'
}
},
servers: [
{
url: 'https://api.cinemahub.com/v1',
description: 'Production server ๐'
}
],
paths: {
'/movies': {
get: {
summary: 'Search movies ๐',
description: 'Search and filter movies in the database',
tags: ['Movies'],
parameters: [
{
name: 'query',
in: 'query',
description: 'Search query for movie titles',
schema: { type: 'string' },
example: 'Avengers'
},
{
name: 'genre',
in: 'query',
description: 'Filter by genre ๐ญ',
schema: {
type: 'string',
enum: ['action', 'comedy', 'drama', 'horror', 'sci-fi', 'romance']
}
},
{
name: 'year',
in: 'query',
description: 'Filter by release year ๐
',
schema: { type: 'integer', minimum: 1900, maximum: 2024 }
},
{
name: 'rating',
in: 'query',
description: 'Minimum rating (1-10) โญ',
schema: { type: 'number', minimum: 1, maximum: 10 }
},
{
name: 'page',
in: 'query',
description: 'Page number for pagination ๐',
schema: { type: 'integer', minimum: 1, default: 1 }
},
{
name: 'limit',
in: 'query',
description: 'Number of items per page',
schema: { type: 'integer', minimum: 1, maximum: 100, default: 20 }
}
],
responses: {
'200': {
description: 'Movies retrieved successfully ๐',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
movies: {
type: 'array',
items: { $ref: '#/components/schemas/Movie' }
},
pagination: { $ref: '#/components/schemas/Pagination' }
}
}
}
}
},
'400': {
description: 'Invalid query parameters',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' }
}
}
}
}
},
post: {
summary: 'Add a new movie ๐ฌ',
description: 'Create a new movie entry (Admin only)',
tags: ['Movies'],
security: [{ bearerAuth: [] }],
requestBody: {
required: true,
content: {
'application/json': {
schema: { $ref: '#/components/schemas/MovieInput' }
}
}
},
responses: {
'201': {
description: 'Movie created successfully ๐',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Movie' }
}
}
},
'400': {
description: 'Invalid movie data',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Error' }
}
}
},
'401': {
description: 'Unauthorized - Admin access required ๐'
}
}
}
},
'/movies/{id}': {
get: {
summary: 'Get movie details ๐ฌ',
description: 'Retrieve detailed information about a specific movie',
tags: ['Movies'],
parameters: [
{
name: 'id',
in: 'path',
required: true,
description: 'Movie ID',
schema: { type: 'string' },
example: 'movie-123'
}
],
responses: {
'200': {
description: 'Movie details retrieved successfully',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/MovieDetailed' }
}
}
},
'404': {
description: 'Movie not found ๐'
}
}
}
},
'/movies/{id}/reviews': {
get: {
summary: 'Get movie reviews ๐',
description: 'Retrieve reviews for a specific movie',
tags: ['Reviews'],
parameters: [
{
name: 'id',
in: 'path',
required: true,
description: 'Movie ID',
schema: { type: 'string' }
}
],
responses: {
'200': {
description: 'Reviews retrieved successfully',
content: {
'application/json': {
schema: {
type: 'array',
items: { $ref: '#/components/schemas/Review' }
}
}
}
}
}
},
post: {
summary: 'Add a movie review โญ',
description: 'Submit a review for a movie',
tags: ['Reviews'],
security: [{ bearerAuth: [] }],
parameters: [
{
name: 'id',
in: 'path',
required: true,
description: 'Movie ID',
schema: { type: 'string' }
}
],
requestBody: {
required: true,
content: {
'application/json': {
schema: { $ref: '#/components/schemas/ReviewInput' }
}
}
},
responses: {
'201': {
description: 'Review added successfully ๐',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Review' }
}
}
}
}
}
}
},
components: {
schemas: {
Movie: {
type: 'object',
properties: {
id: { type: 'string', example: 'movie-123' },
title: { type: 'string', example: 'The Awesome Movie ๐ฌ' },
description: {
type: 'string',
example: 'An incredible adventure that will blow your mind!'
},
genre: {
type: 'array',
items: { type: 'string' },
example: ['action', 'adventure', 'sci-fi']
},
releaseYear: { type: 'integer', example: 2024 },
rating: { type: 'number', format: 'float', example: 8.5 },
duration: { type: 'integer', example: 142 },
director: { type: 'string', example: 'Steven Spielberg ๐ญ' },
cast: {
type: 'array',
items: { type: 'string' },
example: ['Actor One ๐ญ', 'Actor Two ๐', 'Actor Three โญ']
},
posterUrl: {
type: 'string',
format: 'uri',
example: 'https://example.com/poster.jpg'
},
createdAt: { type: 'string', format: 'date-time' },
updatedAt: { type: 'string', format: 'date-time' }
},
required: ['id', 'title', 'genre', 'releaseYear', 'rating']
},
MovieDetailed: {
allOf: [
{ $ref: '#/components/schemas/Movie' },
{
type: 'object',
properties: {
reviews: {
type: 'array',
items: { $ref: '#/components/schemas/Review' }
},
averageRating: { type: 'number', format: 'float' },
reviewCount: { type: 'integer' }
}
}
]
},
MovieInput: {
type: 'object',
properties: {
title: { type: 'string', minLength: 1 },
description: { type: 'string' },
genre: {
type: 'array',
items: {
type: 'string',
enum: ['action', 'comedy', 'drama', 'horror', 'sci-fi', 'romance']
}
},
releaseYear: { type: 'integer', minimum: 1900, maximum: 2024 },
duration: { type: 'integer', minimum: 1 },
director: { type: 'string' },
cast: {
type: 'array',
items: { type: 'string' }
},
posterUrl: { type: 'string', format: 'uri' }
},
required: ['title', 'genre', 'releaseYear']
},
Review: {
type: 'object',
properties: {
id: { type: 'string', example: 'review-456' },
movieId: { type: 'string', example: 'movie-123' },
userId: { type: 'string', example: 'user-789' },
username: { type: 'string', example: 'MovieLover123 ๐ฟ' },
rating: { type: 'integer', minimum: 1, maximum: 10, example: 9 },
comment: {
type: 'string',
example: 'Absolutely amazing! A masterpiece of cinema ๐ฌโจ'
},
createdAt: { type: 'string', format: 'date-time' }
},
required: ['id', 'movieId', 'userId', 'rating']
},
ReviewInput: {
type: 'object',
properties: {
rating: { type: 'integer', minimum: 1, maximum: 10 },
comment: { type: 'string', maxLength: 1000 }
},
required: ['rating']
},
Pagination: {
type: 'object',
properties: {
page: { type: 'integer', example: 1 },
limit: { type: 'integer', example: 20 },
total: { type: 'integer', example: 150 },
pages: { type: 'integer', example: 8 }
}
},
Error: {
type: 'object',
properties: {
code: { type: 'string', example: 'VALIDATION_ERROR' },
message: { type: 'string', example: 'The provided data is invalid' },
details: {
type: 'array',
items: { type: 'string' },
example: ['Title is required', 'Genre must be valid']
}
}
}
},
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'Enter your JWT token ๐'
}
}
}
};
// ๐ฎ Express.js implementation
app.get('/movies', async (req, res) => {
const { query, genre, year, rating, page = 1, limit = 20 } = req.query;
try {
const movies = await movieService.searchMovies({
query: query as string,
genre: genre as string,
year: year ? parseInt(year as string) : undefined,
rating: rating ? parseFloat(rating as string) : undefined,
page: parseInt(page as string),
limit: parseInt(limit as string)
});
res.json({
movies: movies.data,
pagination: {
page: movies.page,
limit: movies.limit,
total: movies.total,
pages: Math.ceil(movies.total / movies.limit)
}
});
} catch (error) {
res.status(400).json({
code: 'SEARCH_ERROR',
message: 'Failed to search movies',
details: [error.message]
});
}
});
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create comprehensive API documentation with confidence ๐ช
- โ Avoid common mistakes that trip up API developers ๐ก๏ธ
- โ Apply best practices in real projects ๐ฏ
- โ Debug documentation issues like a pro ๐
- โ Build awesome APIs with TypeScript! ๐
Remember: Good API documentation is like a friendly guide that helps developers succeed with your API. Itโs not just about the code - itโs about creating a great developer experience! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered API Documentation with Swagger/OpenAPI!
Hereโs what to do next:
- ๐ป Practice documenting your own APIs
- ๐๏ธ Set up automated documentation generation
- ๐ Move on to our next tutorial: Authentication & JWT
- ๐ Share your beautiful API docs with the community!
Remember: Every great API starts with great documentation. Keep building, keep documenting, and most importantly, have fun! ๐
Happy coding! ๐๐โจ