API Design Best Practices
Những nguyên tắc vàng trong thiết kế API RESTful và GraphQL
API Design Best Practices
Thiết kế API tốt là nền tảng cho việc xây dựng các ứng dụng scalable và maintainable. Dưới đây là những best practices quan trọng cần tuân thủ.
RESTful API Principles
1. Use Proper HTTP Methods
GET /api/users # Get all users
GET /api/users/123 # Get specific user
POST /api/users # Create new user
PUT /api/users/123 # Update entire user
PATCH /api/users/123 # Partial update
DELETE /api/users/123 # Delete user
2. Consistent Naming Conventions
- Use plural nouns for resources:
/users,/products - Use kebab-case for multi-word resources:
/user-profiles - Be consistent across all endpoints
3. Proper Status Codes
// Success codes
200 OK - Request succeeded
201 Created - Resource created successfully
204 No Content - Request succeeded, no content to return
// Error codes
400 Bad Request - Invalid request data
401 Unauthorized - Authentication required
403 Forbidden - Insufficient permissions
404 Not Found - Resource not found
409 Conflict - Resource conflict
500 Internal Error - Server error
Response Format Best Practices
1. Consistent JSON Structure
{
"success": true,
"data": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
},
"message": "User retrieved successfully",
"timestamp": "2024-01-20T10:30:00Z"
}
2. Error Response Format
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": {
"field": "email",
"value": "invalid-email"
}
},
"timestamp": "2024-01-20T10:30:00Z"
}
Authentication & Authorization
1. JWT Token Implementation
// Generate token
const token = jwt.sign(
{
userId: user.id,
email: user.email
},
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
// Authorization header format
Authorization: Bearer <jwt_token>
2. API Key Security
// Validate API key
const validateApiKey = (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !isValidApiKey(apiKey)) {
return res.status(401).json({
success: false,
error: { code: 'INVALID_API_KEY', message: 'Invalid API key' }
});
}
next();
};
Rate Limiting & Caching
1. Implement Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: {
success: false,
error: { code: 'RATE_LIMIT_EXCEEDED', message: 'Too many requests' }
}
});
app.use('/api/', limiter);
2. Cache Strategy
// Cache GET requests
app.get('/api/users', cache('5 minutes'), async (req, res) => {
const users = await User.find();
res.json({
success: true,
data: users,
count: users.length
});
});
Versioning Strategy
1. URL Versioning
GET /api/v1/users
GET /api/v2/users # Updated version with breaking changes
2. Header Versioning
GET /api/users
Accept: application/vnd.api+json;version=1
Documentation Standards
1. OpenAPI/Swagger Specification
openapi: 3.0.0
info:
title: User API
version: 1.0.0
description: API for managing users
paths:
/users:
get:
summary: Get all users
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: array
items:
$ref: '#/components/schemas/User'
2. Interactive Documentation
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
Security Best Practices
1. Input Validation
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required()
});
app.post('/api/users', (req, res) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
error: { code: 'VALIDATION_ERROR', message: error.details[0].message }
});
}
// Process request
});
2. CORS Configuration
const cors = require('cors');
app.use(cors({
origin: ['https://yourdomain.com', 'https://admin.yourdomain.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
Monitoring & Logging
1. Request Logging
const morgan = require('morgan');
app.use(morgan('combined', {
stream: {
write: (message) => {
logger.info(message.trim());
}
}
}));
2. Health Check Endpoint
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage()
});
});
GraphQL Considerations
1. Schema Design
type User {
id: ID!
name: String!
email: String!
createdAt: DateTime!
}
type Query {
users(limit: Int, offset: Int): [User!]!
user(id: ID!): User
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
}
2. Resolvers Best Practices
const resolvers = {
Query: {
users: async (_, { limit = 10, offset = 0 }, { db }) => {
return await db.user.findMany({
take: limit,
skip: offset,
orderBy: { createdAt: 'desc' }
});
}
}
};
Performance Optimization
1. Database Query Optimization
// Bad: N+1 query problem
const users = await User.find();
for (const user of users) {
user.posts = await Post.find({ userId: user.id });
}
// Good: Use eager loading
const users = await User.find().populate('posts');
2. Pagination Implementation
app.get('/api/users', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
User.find().skip(skip).limit(limit),
User.countDocuments()
]);
res.json({
success: true,
data: users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
});
Testing Strategies
1. Unit Tests
describe('User API', () => {
test('should create a new user', async () => {
const response = await request(app)
.post('/api/users')
.send({
name: 'John Doe',
email: 'john@example.com',
password: 'Password123'
})
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data.name).toBe('John Doe');
});
});
2. Integration Tests
test('should handle user authentication flow', async () => {
// Register user
await request(app)
.post('/api/users/register')
.send(userData);
// Login
const loginResponse = await request(app)
.post('/api/users/login')
.send({ email: userData.email, password: userData.password });
// Access protected route
await request(app)
.get('/api/users/profile')
.set('Authorization', `Bearer ${loginResponse.body.data.token}`)
.expect(200);
});
Kết luận
Thiết kế API tốt không chỉ làm cho application dễ sử dụng mà còn đảm bảo scalability, security, và maintainability. Bằng cách tuân thủ các best practices trên, bạn có thể xây dựng APIs chất lượng cao và professional.