API Design Principles for Modern Web Applications
Best practices for designing RESTful APIs that are intuitive, maintainable, and scalable.
Harshit Shrivastav
Contributor
API Design Principles for Modern Web Applications
A well-designed API is a joy to use. A poorly designed one causes frustration and bugs. Here's how to design APIs that developers will love.
RESTful Resource Design
Use Nouns, Not Verbs
โ
GET /users/123
โ
POST /users
โ
PUT /users/123
โ
DELETE /users/123
โ GET /getUser/123
โ POST /createUser
โ POST /updateUser/123
โ POST /deleteUser/123
Nested Resources
GET /users/123/posts # Get all posts by user 123
GET /users/123/posts/456 # Get post 456 by user 123
POST /users/123/posts # Create post for user 123
But don't nest too deep:
โ /users/123/posts/456/comments/789/likes
โ
/comments/789/likes
HTTP Methods Correctly
GET - Read Only
GET /users/123
- Idempotent: Multiple calls return same result
- No side effects
- Cacheable
POST - Create
POST /users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
Response: 201 Created
Location: /users/124
PUT - Full Update
PUT /users/123
{
"name": "John Updated",
"email": "john.updated@example.com"
}
- Idempotent
- Replaces entire resource
PATCH - Partial Update
PATCH /users/123
{
"name": "John Updated"
}
- Only updates specified fields
- More flexible than PUT
DELETE - Remove
DELETE /users/123
Response: 204 No Content
Response Status Codes
Use appropriate status codes:
Success (2xx)
- 200 OK: Successful GET, PUT, PATCH
- 201 Created: Successful POST
- 204 No Content: Successful DELETE
Client Errors (4xx)
- 400 Bad Request: Invalid input
- 401 Unauthorized: Not authenticated
- 403 Forbidden: Authenticated but no permission
- 404 Not Found: Resource doesn't exist
- 422 Unprocessable Entity: Validation failed
Server Errors (5xx)
- 500 Internal Server Error: Generic server error
- 503 Service Unavailable: Service is down
Error Responses
Provide useful error information:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
},
{
"field": "age",
"message": "Must be at least 18"
}
]
}
}
Versioning
Include version in URL:
/api/v1/users
/api/v2/users
Or use headers:
Accept: application/vnd.myapi.v1+json
Pagination
For list endpoints:
GET /users?page=2&limit=20
Response:
{
"data": [...],
"pagination": {
"total": 150,
"page": 2,
"limit": 20,
"total_pages": 8
}
}
Or cursor-based:
GET /users?cursor=abc123&limit=20
Response:
{
"data": [...],
"pagination": {
"next_cursor": "xyz789",
"has_more": true
}
}
Filtering and Sorting
GET /users?status=active&sort=-created_at&fields=id,name,email
- Filtering: ?status=active
- Sorting: ?sort=-created_at (- for descending)
- Field selection: ?fields=id,name,email
Rate Limiting
Include rate limit headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
Return 429 when exceeded:
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "API rate limit exceeded"
}
}
Authentication
Use JWT tokens:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
HATEOAS (Optional)
Include links to related resources:
{
"id": 123,
"name": "John Doe",
"links": {
"self": "/users/123",
"posts": "/users/123/posts",
"avatar": "/users/123/avatar"
}
}
Documentation
Always provide:
- Endpoint descriptions
- Request/response examples
- Authentication requirements
- Rate limits
- Error codes
Use tools like:
- Swagger/OpenAPI
- Postman Collections
- API Blueprint
Security Best Practices
- Always use HTTPS
- Validate all input
- Use parameterized queries (prevent SQL injection)
- Implement rate limiting
- Add CORS headers appropriately
- Never expose sensitive data in URLs
- Use proper authentication
Testing
Test your API:
describe('Users API', () => {
it('should create a user', async () => {
const response = await request(app)
.post('/api/v1/users')
.send({ name: 'John', email: 'john@example.com' })
.expect(201);
expect(response.body).toHaveProperty('id');
});
it('should return validation error', async () => {
await request(app)
.post('/api/v1/users')
.send({ name: 'John' })
.expect(422);
});
});
Conclusion
Good API design is about:
- Consistency
- Predictability
- Clear error messages
- Proper HTTP usage
- Good documentation
Follow these principles, and your API will be easy to use and maintain.
Comments (0)
Loading comments...