DEV Community

Alex Chen
Alex Chen

Posted on

REST API Design: Building APIs Developers Love (2026)

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)