Designing REST APIs That Developers Actually Love Using
Good APIs don't just work — they're a joy to use.
URL Design
# ✅ Good: Nouns, not verbs
GET /api/users # List users
POST /api/users # Create user
GET //api/users/123 # Get specific user
PUT /api/users/123 # Update (full)
PATCH /api/users/123 # Update (partial)
DELETE /api/users/123 # Delete user
# ❌ Bad: Verbs in URLs, inconsistent naming
GET /api/getUsers
POST /api/createUser
GET /api/user/get?id=123
DELETE /api/removeUser/123
Resource Naming Conventions
# Plural nouns for collections
/api/orders # Not /api/order or /api/orderList
# Nested resources show relationships
/api/users/123/orders # Orders for user 123
/api/users/123/orders/456 # Specific order of specific user
# Keep URLs shallow (max 2-3 levels)
# Too deep: /api/orgs/5/depts/3/teams/2/members/1
# Better: /api/members/1?org=5&dept=3&team=2
Query Parameters
# Pagination
GET /api/users?page=1&limit=20
# Filtering
GET /api/products?category=electronics&price_min=50&in_stock=true
# Sorting
GET /api/users?sort=created_at&order=desc
GET /api/users?sort=-created_at,name # - prefix = descending
# Field selection (reduce payload)
GET /api/users?fields=id,name,email
# Search
GET /api/users?q=alex&search_by=name,email
# Include related resources
GET /api/posts/123?include=author,comments,tags
Response Format
// Standard success response
{
"data": {
"id": "usr_abc123",
"name": "Alex Chen",
"email": "alex@example.com",
"role": "admin",
"created_at": "2026-01-15T00:00:00Z",
"updated_at": "2026-05-16T07:00:00Z"
},
"meta": {
"request_id": "req_xyz789"
}
}
// List response with pagination
{
"data": [/* ... items ... */],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"total_pages": 8,
"has_next": true,
"has_prev": false
},
"links": {
"first": "/api/users?page=1",
"prev": null,
"next": "/api/users?page=2",
"last": "/api/users?page=8"
}
}
// Error response (ALWAYS consistent!)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "password", "message": "Must be at least 8 characters" }
],
"request_id": "req_xyz789"
}
}
HTTP Methods and Status Codes
| Action | Method | Success | Created | Not Found | Error |
|---|---|---|---|---|---|
| List | GET | 200 + array | — | 404 (collection) | 400/500 |
| Read | GET | 200 + object | — | 404 | 400/500 |
| Create | POST | — | 201 + object | 409 (duplicate) | 400/422 |
| Update | PUT/PATCH | 200 + object | — | 404 | 400/422 |
| Delete | DELETE | 200/204 | — | 404 | 500 |
Versioning
# Option 1: URL path (most common)
/api/v1/users
/api/v2/users
# Option 2: Header (cleaner URLs)
Accept: application/vnd.api.v1+json
GET /api/users
# Option 3: Query parameter (simplest)
GET /api/users?version=v1
Authentication
// Bearer token (JWT)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// API key (for server-to-server)
X-API-Key: sk_live_abc123
// Basic auth (rarely used for APIs)
Authorization: Basic YWxleDpwYXNzd29yZA==
Rate Limiting Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1715841200
Retry-After: 60
Idempotency Keys (For Safe Retries)
// Client generates unique key
const idempotencyKey = crypto.randomUUID();
const response = await fetch('/api/charges', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({ amount: 1000 }),
});
// If request fails and client retries with same key:
// Server returns the original result instead of creating a duplicate!
Quick Checklist
□ Use plural nouns for resource names
□ Consistent naming (snake_case or camelCase, pick one!)
□ Paginate all list endpoints
□ Return proper HTTP status codes
□ Include error details in error responses
□ Support filtering, sorting, field selection
□ Add rate limiting headers
□ Document with OpenAPI/Swagger
□ Use HTTPS everywhere
□ Validate ALL input server-side
□ Include request IDs for debugging
□ Make operations idempotent where possible
What's your biggest API pet peeve?
Follow @armorbreak for more API content.
Top comments (0)