API Design Best Practices
A well-designed API is intuitive, consistent, and developer-friendly. This guide covers the principles and patterns that make APIs a pleasure to use.
Use RESTful Conventions
REST APIs use standard HTTP methods and URL structures:
GET /users # list all users
GET /users/123 # get single user
POST /users # create new user
PUT /users/123 # update entire user
PATCH /users/123 # partial update
DELETE /users/123 # delete user
Key principles:
- Use nouns in URLs, not verbs (
/usersnot/getUsers) - Use plural nouns consistently
- Use nested resources for relationships (
/users/123/orders) - Keep URLs lowercase with hyphens
Proper HTTP Status Codes
Always return the appropriate status code:
200 OK # successful GET, PUT, PATCH
201 Created # successful POST creating resource
204 No Content # successful DELETE
400 Bad Request # invalid input data
401 Unauthorized # not authenticated
403 Forbidden # authenticated but not authorized
404 Not Found # resource doesn't exist
422 Unprocessable Entity # validation errors
429 Too Many Requests # rate limit exceeded
500 Internal Server Error # server error
Consistent Error Format
Return errors in a consistent structure:
{
"error": "validation_error",
"message": "The request body contains invalid data",
"details": [
{
"field": "email",
"message": "Email format is invalid"
},
{
"field": "password",
"message": "Password must be at least 8 characters"
}
]
}
Include enough information for developers to debug, but don't expose sensitive internals.
Pagination
Don't return unlimited results. Use cursor or offset pagination:
Offset Pagination
GET /users?page=2&per_page=20
# Response
{
"data": [...],
"pagination": {
"page": 2,
"per_page": 20,
"total": 150,
"total_pages": 8
}
}
Cursor Pagination (Better for Large Datasets)
GET /users?cursor=abc123&limit=20
# Response
{
"data": [...],
"pagination": {
"next_cursor": "def456",
"has_more": true
}
}
Cursor pagination is more efficient for large tables and real-time data.
API Versioning
Version your API to allow breaking changes without breaking clients:
# URL path (most common)
GET /v1/users
GET /v2/users
# Header approach
Accept: application/vnd.api.v2+json
Tip: Start with v1 even if you don't think you'll need versions. Adding versioning later is harder than starting with it.
Filtering, Sorting, and Field Selection
Give clients control over their queries:
# Filtering
GET /orders?status=pending&created_after=2024-01-01
# Sorting
GET /products?sort=price&order=desc
# Field selection (sparse fieldsets)
GET /users?fields=id,name,email
# Combining
GET /orders?status=completed&sort=created_at&order=desc&limit=50
Authentication
Use standard authentication methods:
# API Keys (for server-to-server)
X-API-Key: your-api-key
# Bearer Tokens (for user authentication)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# Rate Limiting Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
Performance Tips
Response Compression
# Client sends
Accept-Encoding: gzip, deflate, br
# Server responds
Content-Encoding: gzip
Conditional Requests
# Client sends
If-None-Match: "etag-from-previous-response"
# Server checks, returns 304 if unchanged
HTTP/1.1 304 Not Modified
Cache Control
Cache-Control: public, max-age=3600
ETag: "abc123"
Last-Modified: Wed, 01 Jan 2024 00:00:00 GMT
Documentation
Good API documentation includes:
- Authentication instructions
- All endpoints with request/response examples
- Error codes and messages
- Rate limits and quotas
- Try-it-out sandbox
Use OpenAPI/Swagger for machine-readable specs that generate documentation automatically.