OpenAPI Specification: Design APIs That Scale
Welcome to TopperBlog! 👋
I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.
🎯 What I Write About:
• AI/ML Engineering & LLMs
• Web3 & Blockchain Development
• System Design & Architecture
• Interview Preparation (FAANG)
• Freelancing & Remote Work
• Modern Tech Stacks (Next.js, React, Rust, TypeScript)
• Performance Optimization & Best Practices
💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.
📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.
🌐 Let's connect and grow together in this amazing tech journey!
#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering
OpenAPI Specification: Design APIs That Scale
The API That Broke Everything (And How Documentation Could Have Saved Us)
Our undocumented API changes broke 50 client apps. Here's what we learned the hard way.
Table of Contents
- API Development 2026
- Design Principles
- 5 Essential Patterns
- Documentation Strategy
- Testing Approach
- Security Considerations
- Monitoring
- FAQ
- Best Practices
API Development in 2026
APIs are the backbone of modern apps.
API-First Approach
# OpenAPI specification first
openapi: 3.1.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
summary: List users
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
Design-First Benefits
// Generated TypeScript types
interface User {
id: string;
email: string;
name: string;
createdAt: string;
}
// Auto-generated client
const users = await api.users.list();
// TypeScript knows the response type!
Business Impact
Good APIs = happy developers = more integrations.
Design Principles
Build APIs that last.
RESTful Conventions
// Standard HTTP methods
app.get('/api/users', listUsers); // Read all
app.get('/api/users/:id', getUser); // Read one
app.post('/api/users', createUser); // Create
app.put('/api/users/:id', updateUser); // Update (full)
app.patch('/api/users/:id', patchUser); // Update (partial)
app.delete('/api/users/:id', deleteUser); // Delete
Resource Naming
// ✅ Good: Plural nouns
GET /api/users
GET /api/orders/123/items
// ❌ Bad: Verbs or singular
GET /api/getUser
GET /api/user
Error Responses
// Consistent error format
interface APIError {
error: {
code: string;
message: string;
details?: any;
requestId: string;
};
}
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
error: {
code: err.code || 'INTERNAL_ERROR',
message: err.message,
requestId: req.id
}
});
});
Pattern 1: Request Validation
Schema Validation
// Validate with Zod
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
age: z.number().int().min(13).max(120).optional()
});
app.post('/api/users', async (req, res) => {
try {
const data = createUserSchema.parse(req.body);
const user = await createUser(data);
res.status(201).json(user);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid input',
details: error.errors
}
});
}
}
});
Type Safety
Generate types from schema.
Pattern 2: Pagination
Cursor-Based
// Efficient pagination
interface PaginatedResponse<T> {
data: T[];
pagination: {
nextCursor?: string;
prevCursor?: string;
hasMore: boolean;
};
}
app.get('/api/users', async (req, res) => {
const { cursor, limit = 20 } = req.query;
const users = await db.users
.where('id', '>', cursor || '')
.limit(limit + 1)
.orderBy('id');
const hasMore = users.length > limit;
const data = hasMore ? users.slice(0, -1) : users;
res.json({
data,
pagination: {
nextCursor: hasMore ? data[data.length - 1].id : undefined,
hasMore
}
});
});
Offset-Based
// Simple but can be slow
app.get('/api/users', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const offset = (page - 1) * limit;
const [users, total] = await Promise.all([
db.users.limit(limit).offset(offset),
db.users.count()
]);
res.json({
data: users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
});
Pattern 3: Authentication
JWT Implementation
// Secure JWT handling
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
export async function createToken(userId: string) {
return await new SignJWT({ sub: userId })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('2h')
.sign(secret);
}
// Middleware
async function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({
error: { code: 'UNAUTHORIZED', message: 'No token' }
});
}
try {
const { payload } = await jwtVerify(token, secret);
req.userId = payload.sub;
next();
} catch {
res.status(401).json({
error: { code: 'INVALID_TOKEN', message: 'Token invalid' }
});
}
}
API Keys
// Simple API key auth
async function apiKeyAuth(req, res, next) {
const apiKey = req.headers['x-api-key'];
const key = await db.apiKeys.findOne({
key: apiKey,
active: true
});
if (!key) {
return res.status(401).json({
error: { code: 'INVALID_KEY' }
});
}
// Track usage
await db.apiKeys.update(key.id, {
lastUsed: new Date(),
usageCount: key.usageCount + 1
});
req.apiKey = key;
next();
}
Pattern 4: Rate Limiting
Implementation
// Redis-based rate limiting
import { RateLimiterRedis } from 'rate-limiter-flexible';
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 100, // requests
duration: 60 // per minute
});
async function rateLimitMiddleware(req, res, next) {
const key = req.apiKey?.id || req.ip;
try {
await rateLimiter.consume(key);
// Add rate limit headers
const remaining = await rateLimiter.get(key);
res.setHeader('X-RateLimit-Limit', 100);
res.setHeader('X-RateLimit-Remaining', remaining?.remainingPoints || 100);
next();
} catch {
res.status(429).json({
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests'
}
});
}
}
Tiered Limits
Different limits per plan.
Pattern 5: Versioning
URL Versioning
// Version in path
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// v1Router
v1Router.get('/users', (req, res) => {
// Old response format
res.json({ users: [...] });
});
// v2Router
v2Router.get('/users', (req, res) => {
// New response format with pagination
res.json({
data: [...],
pagination: {...}
});
});
Header Versioning
// Accept-Version header
app.use((req, res, next) => {
const version = req.headers['accept-version'] || '1';
req.apiVersion = version;
next();
});
Documentation Strategy
Interactive Docs
// Swagger UI setup
import swaggerUi from 'swagger-ui-express';
import { readFileSync } from 'fs';
import { load } from 'js-yaml';
const spec = load(readFileSync('./openapi.yaml', 'utf8'));
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(spec));
Code Examples
## Create User
POST /api/users
### Request
```json
{
"email": "user@example.com",
"name": "John Doe"
}
Response
{
"id": "usr_123",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2026-02-12T10:00:00Z"
}
## Testing Approach
### Integration Tests
```typescript
// Test API endpoints
import { describe, it, expect } from 'vitest';
describe('Users API', () => {
it('creates user', async () => {
const response = await fetch('http://localhost:3000/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'test@example.com',
name: 'Test User'
})
});
expect(response.status).toBe(201);
const user = await response.json();
expect(user.email).toBe('test@example.com');
});
});
Contract Testing
Ensure API matches spec.
Performance
| Endpoint | Avg Response | p95 | p99 |
| GET /users | 50ms | 80ms | 120ms |
| POST /users | 100ms | 150ms | 200ms |
| GET /users/:id | 20ms | 30ms | 50ms |
FAQ
Q1: REST vs GraphQL?
REST for public APIs, GraphQL for frontend-driven apps.
Q2: How to version APIs?
URL versioning is clearest for clients.
Q3: Rate limiting strategy?
Per-user or per-API-key with tiered limits.
Q4: Documentation tool?
OpenAPI + Swagger UI or ReadMe.io.
Q5: API key vs OAuth?
API keys for server-to-server, OAuth for user apps.
Best Practices
Checklist
- [ ] OpenAPI spec written
- [ ] Input validation
- [ ] Error handling
- [ ] Authentication
- [ ] Rate limiting
- [ ] Documentation
- [ ] Tests written
- [ ] Monitoring setup
Conclusion
Great APIs drive adoption.
Key takeaways:
- Design first
- Document thoroughly
- Validate inputs
- Version carefully
- Monitor constantly
Build APIs developers love.
Resources:
- OpenAPI Specification
- API Design Patterns
- Testing Strategies
- Security Guidelines
Next Steps:
- Write OpenAPI spec
- Implement validation
- Add authentication
- Generate documentation
- Deploy with monitoring
Build better APIs today.