REST API Design: Best Practices, Naming Conventions, and Error Handling
A comprehensive guide to designing clean, consistent REST APIs — URL structure, HTTP methods, status codes, pagination, filtering, versioning, error responses, and authentication patterns.
What Makes a Good REST API?
A well-designed REST API is:
- Predictable — Developers can guess endpoints without reading docs
- Consistent — Same patterns everywhere
- Self-documenting — URLs describe the resource
- Versioned — Changes don't break existing clients
URL Structure
Use Nouns, Not Verbs
✅ Good:
GET /api/users — List users
GET /api/users/123 — Get user 123
POST /api/users — Create user
PUT /api/users/123 — Update user 123
DELETE /api/users/123 — Delete user 123
❌ Bad:
GET /api/getUsers
POST /api/createUser
PUT /api/updateUser/123
DELETE /api/deleteUser/123
The HTTP method already tells you the action. The URL should describe the resource.
Use Plural Nouns
✅ /api/users
✅ /api/posts
✅ /api/orders
❌ /api/user
❌ /api/post
Nested Resources
GET /api/users/123/orders — Orders for user 123
GET /api/users/123/orders/456 — Order 456 for user 123
POST /api/users/123/orders — Create order for user 123
Rule: Don't nest more than 2 levels deep. Beyond that, use query parameters or top-level endpoints.
Use Kebab-Case
✅ /api/user-profiles
✅ /api/order-items
❌ /api/userProfiles
❌ /api/order_items
HTTP Methods
| Method | Purpose | Idempotent | Request Body |
|---|---|---|---|
| GET | Read resource(s) | Yes | No |
| POST | Create resource | No | Yes |
| PUT | Replace resource | Yes | Yes |
| PATCH | Partial update | Yes | Yes |
| DELETE | Delete resource | Yes | No |
PUT vs PATCH
// PUT — Replace the entire resource
PUT /api/users/123
{
"name": "Alice Updated",
"email": "alice@new.com",
"age": 31,
"role": "admin"
}
// PATCH — Update only specified fields
PATCH /api/users/123
{
"age": 31
}
Response Format
Consistent JSON Structure
// Success response
{
"data": {
"id": 123,
"name": "Alice",
"email": "alice@example.com"
}
}
// List response with pagination
{
"data": [
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 150,
"total_pages": 8
}
}
// Error response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "age", "message": "Must be between 0 and 150" }
]
}
}
Status Codes
Use the right status code for every response:
200 OK — Successful GET, PUT, PATCH
201 Created — Successful POST (include Location header)
204 No Content — Successful DELETE
400 Bad Request — Invalid input
401 Unauthorized — Not authenticated
403 Forbidden — Not authorized
404 Not Found — Resource doesn't exist
409 Conflict — Duplicate resource
422 Unprocessable — Validation errors
429 Too Many Reqs — Rate limited
500 Server Error — Something broke
Look up any status code with our HTTP Status Code Lookup tool.
Pagination
Offset-Based (Simple)
GET /api/users?page=2&per_page=20
Response:
{
"data": [...],
"pagination": {
"page": 2,
"per_page": 20,
"total": 150,
"total_pages": 8,
"has_next": true,
"has_prev": true
}
}
Cursor-Based (Better for Large Datasets)
GET /api/users?cursor=eyJpZCI6MTAwfQ&limit=20
Response:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}
}
Filtering, Sorting, and Searching
# Filtering
GET /api/users?status=active&role=admin
# Sorting
GET /api/users?sort=created_at&order=desc
GET /api/users?sort=-created_at (prefix - for descending)
# Searching
GET /api/users?search=alice
# Field selection (sparse fieldsets)
GET /api/users?fields=id,name,email
# Date ranges
GET /api/orders?created_after=2024-01-01&created_before=2024-12-31
# Combined
GET /api/users?status=active&sort=-created_at&page=1&per_page=20&fields=id,name
Versioning
URL Versioning (Most Common)
GET /api/v1/users
GET /api/v2/users
Header Versioning
GET /api/users
Accept: application/vnd.myapi.v2+json
Recommendation: Use URL versioning. It's the most visible and easiest to implement.
Authentication
See our detailed guide: API Authentication Methods Compared
Quick summary:
- Public APIs: API keys in header (
X-API-Key) - User-facing APIs: JWT Bearer tokens (
Authorization: Bearer <token>) - Third-party access: OAuth 2.0
Decode and inspect tokens with our JWT Decoder.
Rate Limiting
Always include rate limit headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000
HTTP/1.1 429 Too Many Requests
Retry-After: 60
CORS
If your API is called from browsers, configure CORS properly. See our guide: Understanding CORS Errors
Related Tools
- HTTP Status Code Lookup — Look up any status code
- JWT Decoder — Decode and create JWT tokens
- JSON Formatter — Format API responses
- cURL to Code — Convert API calls to code
- JSON to TypeScript — Generate types from API responses
- Fake Data Generator — Generate test data for APIs
- JSON to SQL — Convert API data to SQL