A well-designed API is a joy to use. It's intuitive, consistent, and well-documented. This guide teaches you how to design APIs that developers love.
What is an API?
An API (Application Programming Interface) is a contract between software systems. It defines how applications communicate with each other, typically over HTTP.
Why Good API Design Matters
- Developer experience: Easy to understand and use correctly
- Maintainability: Consistent patterns reduce bugs
- Scalability: Well-structured APIs handle growth better
- Documentation: Clear design makes docs easier to write
REST Principles
REST (Representational State Transfer) is the most common API architecture.
Core Principles
- Resources: Everything is a resource (users, posts, orders)
- HTTP Methods: Use verbs to indicate actions
- Stateless: Each request contains all needed information
- Uniform Interface: Consistent URL patterns and responses
HTTP Methods
| Method | Purpose | Example |
|---|---|---|
| GET | Retrieve resource(s) | GET /users |
| POST | Create new resource | POST /users |
| PUT | Replace entire resource | PUT /users/123 |
| PATCH | Partial update | PATCH /users/123 |
| DELETE | Remove resource | DELETE /users/123 |
Designing Endpoints
URL Structure
# Good: Noun-based, hierarchical
GET /users # List all users
GET /users/123 # Get user 123
POST /users # Create user
PUT /users/123 # Update user 123
DELETE /users/123 # Delete user 123
# Nested resources
GET /users/123/posts # Get posts by user 123
POST /users/123/posts # Create post for user 123
# Bad: Verb-based (anti-pattern)
GET /getUsers # Don't do this
POST /createUser # HTTP method already indicates action
GET /deleteUser/123 # Never use GET for mutations Query Parameters
# Filtering
GET /products?category=electronics&in_stock=true
# Sorting
GET /products?sort=price&order=desc
# Pagination
GET /products?page=2&limit=20
GET /products?cursor=abc123&limit=20
# Searching
GET /products?search=laptop
# Field selection
GET /users/123?fields=name,email,avatar Request & Response
Request Body (JSON)
// POST /users
{
"name": "Alice Johnson",
"email": "alice@example.com",
"password": "securepassword123"
}
// PATCH /users/123 (partial update)
{
"name": "Alicia Johnson"
} Response Structure
// Single resource
{
"id": 123,
"name": "Alice Johnson",
"email": "alice@example.com",
"created_at": "2024-01-15T10:30:00Z"
}
// Collection with pagination
{
"data": [
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"total_pages": 5
}
} HTTP Status Codes
| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input data |
| 401 | Unauthorized | Missing/invalid authentication |
| 403 | Forbidden | Authenticated but not allowed |
| 404 | Not Found | Resource doesn't exist |
| 422 | Unprocessable Entity | Validation failed |
| 500 | Internal Server Error | Unexpected server error |
Authentication
JWT (JSON Web Tokens)
// Login endpoint returns token
POST /auth/login
{ "email": "user@example.com", "password": "secret" }
// Response
{ "token": "eyJhbGciOiJIUzI1NiIs..." }
// Use token in subsequent requests
GET /users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs... API Keys
// API key in header
GET /api/data
X-API-Key: your-api-key-here
// Or in query parameter (less secure)
GET /api/data?api_key=your-api-key-here OAuth 2.0 Flow
- User clicks "Login with Google"
- Redirect to Google's authorization page
- User grants permission
- Google redirects back with authorization code
- Your server exchanges code for access token
- Use token to access Google APIs
Error Handling
Consistent Error Format
// Error response structure
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "password",
"message": "Must be at least 8 characters"
}
]
}
}
// Simple error
{
"error": {
"code": "NOT_FOUND",
"message": "User with ID 123 not found"
}
} Error Codes
Use specific error codes that clients can handle programmatically:
VALIDATION_ERROR- Input validation failedUNAUTHORIZED- Authentication requiredFORBIDDEN- Permission deniedNOT_FOUND- Resource doesn't existRATE_LIMITED- Too many requestsINTERNAL_ERROR- Server error
Versioning
APIs evolve. Versioning lets you make breaking changes without breaking existing clients.
URL Path Versioning (Recommended)
GET /v1/users
GET /v2/users
# Clear, visible, easy to route Header Versioning
GET /users
Accept: application/vnd.api+json; version=2 When to Version
- Breaking changes: Removing fields, changing types
- Major restructuring: Different response format
- Additive changes don't need versions: New optional fields are safe
Best Practices
Naming Conventions
- Use plural nouns for resources:
/users, not/user - Use kebab-case for multi-word resources:
/order-items - Use snake_case for JSON fields:
created_at - Be consistent across your entire API
Security
- Always use HTTPS - Never expose APIs over HTTP
- Rate limiting - Prevent abuse and DDoS
- Input validation - Validate all input server-side
- Don't expose internal IDs - Use UUIDs when possible
- Audit logging - Log all API access
Documentation
- OpenAPI/Swagger - Industry standard for API docs
- Include examples - Show request/response examples
- Error documentation - Document all error codes
- Authentication guide - Step-by-step auth instructions