REST API Design: Building APIs Developers Love (2026)
A great API is one developers enjoy using. Here is how to design APIs that are intuitive and consistent.
URL Design
Use nouns not verbs. Use plural nouns for collections. Use kebab-case in URLs.
Bad: GET /getUsers POST /createUser
Good: GET /users POST /users
Nested resources: GET /users/123/orders
Filtering: GET /products?category=electronics&sort=price&order=desc&page=2
HTTP Methods
GET reads data (safe, idempotent). POST creates (not idempotent). PUT replaces entirely (idempotent). PATCH partial update. DELETE removes (idempotent).
Idempotent means calling it N times has same result as calling once. POST creates new user each time so it is NOT idempotent. PUT /users/123 with same data always gives same result so it IS idempotent.
Status Codes
200 OK standard success. 201 Created include Location header. 204 No Content for DELETE. 400 Bad Request invalid input. 401 Unauthorized no token. 403 Forbidden authenticated but not allowed. 404 Not Found. 422 Unprocessable validation failed. 429 Too Many Requests rate limited. 500 Internal Server Error something broke.
Common mistake: returning 200 with error body instead of proper status code. Always match status code to the actual result.
Response Format
Success response wraps data in object:
{
"data": { "id": "usr_123", "name": "Alice" },
"meta": { "requestId": "req_456" }
}
Error response uses consistent format:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid format" }
]
}
}
List responses include pagination metadata: page, limit, totalItems, totalPages, hasNextPage.
Pagination
Cursor-based for large datasets: GET /posts?limit=20&cursor=xxx. Returns nextCursor and hasMore. No skipped items when data changes between requests.
Offset-based for small datasets: GET /posts?page=2&limit=20. Returns page, totalItems, totalPages. Simpler but can show duplicates if data changes.
Filtering and Sorting
Exact match: category=electronics. Range: min_price=10&max_price=100. Boolean: in_stock=true. Multiple values use same key twice or comma separated depending on convention.
Sorting convention: sort=createdAt ASC default. sort=-createdAt DESC with minus prefix. Multi-sort: sort=name,-createdAt.
Versioning
URL path versioning most common: /api/v1/users then /api/v2/users for breaking changes. Clear and cache-friendly per version.
Header versioning keeps URLs clean: Accept header specifies version. Harder to test in browser.
Query param versioning simplest: ?version=2. Easy to forget.
Recommendation: URL path versioning for public APIs. Most explicit and developer-friendly.
When to bump versions: breaking changes like renamed fields or removed endpoints. Within v1 additive changes are OK like new optional fields. Never remove endpoint without 6+ months deprecation notice.
Authentication Patterns
JWT Bearer token in Authorization header. Middleware validates token on protected routes. Returns 401 for missing/invalid token.
API Key in X-API-Key header for server-to-server communication. Different from user tokens.
Rate limiting per user or per IP. Return 429 with Retry-After header when exceeded. Include X-RateLimit-* headers in every response so clients know their limits.
Key Principles
Consistency is king. Same error format everywhere. Same naming convention everywhere. Same pagination pattern everywhere.
Self-documenting. URLs should be readable. Response structure should be obvious. Good docs are essential but great APIs need less documentation because they are intuitive.
Stable over time. Avoid breaking changes. Deprecate before removing. Version when you must change. Communicate changes clearly.
What makes an API delightful or frustrating to use? Share your experience.
Follow @armorbreak for more practical developer guides.
Top comments (0)