| name | api-design |
| description | API Design & Integration: best practices for RESTful and GraphQL APIs, versioning, error handling, and performance. |
| license | See repository LICENSE |
API Design & Integration
Overview
Best practices for designing, implementing, and integrating RESTful and GraphQL APIs that are scalable, maintainable, and developer-friendly.
When to Use This Skill
- Designing new API endpoints
- Integrating third-party APIs
- Implementing API versioning
- Building API documentation
- Handling API errors and edge cases
- Optimizing API performance
RESTful API Design Principles
1. Resource-Based URLs
✅ Good - Resource-oriented
GET /api/users # List users
GET /api/users/123 # Get user
POST /api/users # Create user
PUT /api/users/123 # Update user
DELETE /api/users/123 # Delete user
GET /api/users/123/orders # Get user's orders
❌ Bad - Action-oriented
GET /api/getAllUsers
POST /api/createUser
POST /api/deleteUser/123
GET /api/getUserOrders/123
2. HTTP Methods & Status Codes
HTTP Methods:
GET - Retrieve resource(s) (idempotent, cacheable)
POST - Create new resource (non-idempotent)
PUT - Replace entire resource (idempotent)
PATCH - Partial update (idempotent)
DELETE - Remove resource (idempotent)
Common Status Codes:
2xx Success
200 OK - Request succeeded
201 Created - Resource created
202 Accepted - Accepted for processing
204 No Content - Success but no body
3xx Redirection
301 Moved Permanently
304 Not Modified - Use cached version
4xx Client Errors
400 Bad Request - Invalid request
401 Unauthorized - Authentication required
403 Forbidden - Authenticated but not authorized
404 Not Found - Resource doesn't exist
409 Conflict - Resource conflict
422 Unprocessable Entity - Validation errors
429 Too Many Requests - Rate limited
5xx Server Errors
500 Internal Server Error
502 Bad Gateway - Upstream error
503 Service Unavailable - Temporary downtime
504 Gateway Timeout
3. Consistent Response Format
{
"data": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
},
"meta": {
"timestamp": "2026-02-12T10:00:00Z"
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": [
{ "field": "email", "message": "Must be a valid email" }
]
},
"meta": {
"timestamp": "2026-02-12T10:00:00Z",
"requestId": "abc-123"
}
}
{ "id": 123, "name": "John" }
"Error: Invalid email"
4. Pagination
GET /api/users?page=2&limit=20
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 100,
"totalPages": 5,
"hasNext": true,
"hasPrevious": true
}
}
GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20
{
"data": [...],
"pagination": {
"limit": 20,
"nextCursor": "eyJpZCI6MTQzfQ",
"hasNext": true
}
}
5. Filtering, Sorting, and Searching
GET /api/users?status=active&role=admin
GET /api/users?sort=createdAt:desc,name:asc
GET /api/users?search=john&searchFields=name,email
GET /api/users?status=active&sort=createdAt:desc&page=1&limit=20
app.get('/api/users', async (req, res) => {
const {
status,
role,
sort = 'createdAt:desc',
page = 1,
limit = 20,
search
} = req.query;
const query = {};
if (status) query.status = status;
if (role) query.role = role;
if (search) query.$or = [
{ name: { $regex: search, $options: 'i' } },
{ email: { $regex: search, $options: 'i' } }
];
const [field, order] = sort.split(':');
const users = await User.find(query)
.sort({ [field]: order === 'desc' ? -1 : 1 })
.((page - ) * limit)
.(limit);
total = .(query);
res.({
: users,
: {
: (page),
: (limit),
total,
: .(total / limit)
}
});
});
API Versioning Strategies
1. URL Versioning (Most Common)
GET /api/v1/users
GET /api/v2/users
// Implementation
app.use('/api/v1', routesV1);
app.use('/api/v2', routesV2);
2. Header Versioning
GET /api/users
Accept: application/vnd.myapi.v1+json
// Implementation
app.use((req, res, next) => {
const version = req.headers['accept']?.match(/v(\d+)/)?.[1] || '1';
req.apiVersion = version;
next();
});
3. Deprecation Strategy
app.get('/api/v1/legacy-endpoint', (req, res) => {
res.set('Warning', '299 - "Deprecated API. Use /api/v2/new-endpoint"');
res.set('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT');
});
Error Handling
Structured Error Responses
class APIError extends Error {
constructor(statusCode, code, message, details = []) {
super(message);
this.statusCode = statusCode;
this.code = code;
this.details = details;
}
}
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const response = {
error: {
code: err.code || 'INTERNAL_ERROR',
message: err.message || 'An unexpected error occurred',
...(err.details && { details: err.details })
},
meta: {
timestamp: new Date().toISOString(),
requestId: req.id
}
};
if (statusCode === 500 && process.env.NODE_ENV === 'production') {
response.error.message = 'Internal server error';
delete response..;
}
logger.(, {
statusCode,
: err.,
: err.,
: req.,
: req.,
: err.
});
res.(statusCode).(response);
});
(, , , [
{ : , : }
]);
Authentication & Authorization
JWT Authentication
const jwt = require('jsonwebtoken');
function generateToken(user) {
return jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
}
async function authenticate(req, res, next) {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
throw new APIError(401, 'NO_TOKEN', 'Authentication required');
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId);
if (!user) {
throw new APIError(, , );
}
req. = user;
();
} (error) {
(error. === ) {
(, , );
}
(, , );
}
}
() {
{
(!roles.(req..)) {
(, , );
}
();
};
}
app.(, authenticate, (), (req, res) => {
users = .();
res.({ : users });
});
API Key Authentication
async function validateAPIKey(req, res, next) {
const apiKey = req.header('X-API-Key');
if (!apiKey) {
throw new APIError(401, 'NO_API_KEY', 'API key required');
}
const client = await APIKey.findOne({ key: hash(apiKey), isActive: true });
if (!client) {
throw new APIError(401, 'INVALID_API_KEY', 'Invalid API key');
}
await client.incrementUsage();
req.client = client;
next();
}
Rate Limiting
Basic Rate Limiting
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const limiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:'
}),
windowMs: 15 * 60 * 1000,
max: 100,
message: {
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests, please try again later'
}
},
standardHeaders: true,
legacyHeaders: false
});
app.use('/api/', limiter);
Per-User Rate Limiting
const userLimiter = rateLimit({
keyGenerator: (req) => req.user?.id || req.ip,
windowMs: 60 * 1000,
max: 60,
skipSuccessfulRequests: false
});
app.use('/api/', authenticate, userLimiter);
API Documentation
OpenAPI/Swagger
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: 'My API',
version: '1.0.0',
description: 'API documentation'
},
servers: [
{ url: 'http://localhost:3000', description: 'Development' },
{ url: 'https://api.example.com', description: 'Production' }
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
},
apis: ['./routes/*.js']
};
const swaggerSpec = swaggerJsdoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
app.(, (req, res) => {
});
Third-Party API Integration
Retry Logic with Exponential Backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, {
...options,
timeout: 10000
});
if (!response.ok) {
if (response.status >= 500 || response.status === 429) {
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
(attempt === maxRetries) {
(, , , [
{ : error. }
]);
}
}
}
}
Circuit Breaker Pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED';
this.nextAttempt = Date.now();
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
error;
}
}
() {
. = ;
. = ;
}
() {
.++;
(. >= .) {
. = ;
. = .() + .;
}
}
}
breaker = (, );
data = breaker.( ());
API Performance Optimization
Caching
const Redis = require('ioredis');
const redis = new Redis();
function cacheMiddleware(duration = 300) {
return async (req, res, next) => {
const key = `cache:${req.originalUrl}`;
try {
const cached = await redis.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
} catch (error) {
console.error('Cache error:', error);
}
const originalJson = res.json.bind(res);
res.json = (data) => {
redis.setex(key, duration, JSON.stringify(data)).catch(console.error);
return originalJson(data);
};
next();
};
}
app.get('/api/popular-products', (), (req, res) => {
products = ();
res.({ : products });
});
Compression
const compression = require('compression');
app.use(compression({
level: 6,
threshold: 1024,
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
}
}));
Response Time Monitoring
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('API Request', {
method: req.method,
path: req.path,
status: res.statusCode,
duration,
userAgent: req.get('user-agent')
});
if (duration > 1000) {
logger.warn('Slow API Request', {
method: req.method,
path: req.path,
duration
});
}
});
next();
});
GraphQL API Design
Schema Definition
type User {
id: ID!
email: String!
name: String!
orders: [Order!]!
createdAt: DateTime!
}
type Order {
id: ID!
userId: ID!
user: User!
total: Float!
status: OrderStatus!
items: [OrderItem!]!
}
enum OrderStatus {
PENDING
COMPLETED
CANCELLED
}
type Query {
user(id: ID!): User
users Int, Int UserConnection
order ID Order
createUser String, String User
updateUser ID, String User
deleteUser ID Boolean
UserConnection
UserEdge
PageInfo
PageInfo
Boolean
String
Resolver Implementation
const resolvers = {
Query: {
user: async (_, { id }, context) => {
if (!context.user) {
throw new Error('Authentication required');
}
return await User.findById(id);
},
users: async (_, { page = 1, limit = 20 }, context) => {
const users = await User.find()
.skip((page - 1) * limit)
.limit(limit);
return {
edges: users.map(user => ({ node: user })),
pageInfo: {
hasNextPage: users.length === limit,
endCursor: users[users.length - 1]?.id
}
};
}
},
Mutation: {
createUser: async (_, { email, name }, context) => {
if (!context.user || context.user.role !== 'admin') {
throw new Error();
}
.({ email, name });
}
},
: {
: (user) => {
.({ : user. });
}
}
};
API Testing
Unit Testing API Endpoints
const request = require('supertest');
const app = require('../app');
describe('User API', () => {
it('GET /api/users should return users list', async () => {
const response = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${validToken}`)
.expect(200);
expect(response.body.data).toBeInstanceOf(Array);
expect(response.body.pagination).toBeDefined();
});
it('POST /api/users should create user', async () => {
const userData = {
email: 'test@example.com',
name: 'Test User'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response...).(userData.);
});
});
API Best Practices Checklist