7.7 KiB
7.7 KiB
REST Design Patterns
Resource-Oriented Architecture
REST APIs are built around resources, not actions. Resources are the nouns of your API.
Resource Identification
Good Resource URIs:
GET /users # Collection
GET /users/{id} # Individual resource
GET /users/{id}/orders # Nested collection
POST /users # Create resource
PUT /users/{id} # Replace resource
PATCH /users/{id} # Update resource
DELETE /users/{id} # Delete resource
Bad Resource URIs:
POST /getUser # Verb in URI
POST /createUser # Verb in URI
GET /user?action=delete # Action as query param
Resource Naming Conventions
- Use plural nouns for collections:
/users,/orders,/products - Use lowercase and hyphens for readability:
/shipping-addresses - Avoid deep nesting (max 2-3 levels):
/users/{id}/orders/{orderId} - Use query parameters for filtering:
/users?status=active&role=admin
HTTP Method Semantics
Safe and Idempotent Methods
| Method | Safe | Idempotent | Use Case |
|---|---|---|---|
| GET | Yes | Yes | Retrieve resource(s) |
| POST | No | No | Create resource, non-idempotent operations |
| PUT | No | Yes | Replace entire resource |
| PATCH | No | No | Partial update |
| DELETE | No | Yes | Remove resource |
| HEAD | Yes | Yes | Get metadata only |
| OPTIONS | Yes | Yes | Get allowed methods |
Method Usage
GET - Retrieve Resources
GET /users/123
Accept: application/json
Response: 200 OK
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"created_at": "2024-01-15T10:30:00Z"
}
POST - Create Resources
POST /users
Content-Type: application/json
{
"name": "Jane Smith",
"email": "jane@example.com"
}
Response: 201 Created
Location: /users/124
{
"id": 124,
"name": "Jane Smith",
"email": "jane@example.com",
"created_at": "2024-01-16T14:20:00Z"
}
PUT - Replace Resource
PUT /users/123
Content-Type: application/json
{
"name": "John Doe Updated",
"email": "john.new@example.com"
}
Response: 200 OK
{
"id": 123,
"name": "John Doe Updated",
"email": "john.new@example.com",
"updated_at": "2024-01-17T09:15:00Z"
}
PATCH - Partial Update
PATCH /users/123
Content-Type: application/json
{
"email": "john.updated@example.com"
}
Response: 200 OK
{
"id": 123,
"name": "John Doe",
"email": "john.updated@example.com",
"updated_at": "2024-01-17T10:00:00Z"
}
DELETE - Remove Resource
DELETE /users/123
Response: 204 No Content
HTTP Status Codes
Success Codes (2xx)
- 200 OK - Request succeeded (GET, PUT, PATCH)
- 201 Created - Resource created (POST), include Location header
- 202 Accepted - Request accepted for async processing
- 204 No Content - Success with no response body (DELETE)
Redirection (3xx)
- 301 Moved Permanently - Resource permanently moved
- 302 Found - Temporary redirect
- 304 Not Modified - Cached version is still valid
Client Errors (4xx)
- 400 Bad Request - Invalid request syntax or validation error
- 401 Unauthorized - Authentication required or failed
- 403 Forbidden - Authenticated but not authorized
- 404 Not Found - Resource doesn't exist
- 405 Method Not Allowed - HTTP method not supported for resource
- 409 Conflict - Request conflicts with current state (e.g., duplicate)
- 422 Unprocessable Entity - Valid syntax but semantic errors
- 429 Too Many Requests - Rate limit exceeded
Server Errors (5xx)
- 500 Internal Server Error - Unexpected server error
- 502 Bad Gateway - Invalid response from upstream server
- 503 Service Unavailable - Server temporarily unavailable
- 504 Gateway Timeout - Upstream server timeout
HATEOAS (Hypermedia)
Hypermedia-Driven APIs
Include links to related resources and available actions:
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"_links": {
"self": { "href": "/users/123" },
"orders": { "href": "/users/123/orders" },
"update": { "href": "/users/123", "method": "PATCH" },
"delete": { "href": "/users/123", "method": "DELETE" }
}
}
HAL (Hypertext Application Language)
{
"id": 123,
"name": "John Doe",
"_links": {
"self": { "href": "/users/123" }
},
"_embedded": {
"orders": [
{
"id": 456,
"total": 99.99,
"_links": {
"self": { "href": "/orders/456" }
}
}
]
}
}
Content Negotiation
Accept Headers
GET /users/123
Accept: application/json
GET /users/123
Accept: application/xml
GET /users/123
Accept: application/hal+json
Response Content-Type
Content-Type: application/json; charset=utf-8
Content-Type: application/problem+json
Content-Type: application/hal+json
Idempotency
Idempotent Operations
PUT - Always idempotent: Multiple identical PUT requests produce the same result as a single request.
DELETE - Idempotent: First DELETE returns 204, subsequent DELETEs return 404 (same end state).
POST - Not idempotent by default:
Use Idempotency-Key header for idempotent POST:
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"amount": 100.00,
"currency": "USD"
}
Server stores idempotency key and returns same response for duplicate requests.
Cache Control
Cache Headers
Cache-Control: public, max-age=3600
Cache-Control: private, no-cache
Cache-Control: no-store
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 15 Jan 2024 10:30:00 GMT
Conditional Requests
GET /users/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Response: 304 Not Modified
PUT /users/123
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Content-Type: application/json
{
"name": "Updated Name"
}
Response: 412 Precondition Failed (if ETag doesn't match)
URI Patterns
Consistent URI Structure
/{version}/{resource}
/{version}/{resource}/{id}
/{version}/{resource}/{id}/{sub-resource}
/{version}/{resource}/{id}/{sub-resource}/{sub-id}
Query Parameters
Filtering:
GET /users?status=active&role=admin
GET /products?category=electronics&price_min=100&price_max=500
Sorting:
GET /users?sort=created_at
GET /users?sort=-created_at # Descending
GET /users?sort=name,created_at # Multiple fields
Field Selection:
GET /users?fields=id,name,email
GET /users?exclude=password,social_security_number
Search:
GET /users?q=john
GET /products?search=laptop
Best Practices
- Use nouns, not verbs - Resources are nouns, methods are verbs
- Plural collections - Use
/usersnot/user - Consistent naming - Choose snake_case or camelCase and stick to it
- Proper status codes - Use appropriate HTTP status codes
- Include metadata - Pagination, filtering, sorting info in responses
- Version your API - Plan for evolution from day one
- Document everything - OpenAPI specs, examples, error codes
- Security by default - HTTPS, authentication, rate limiting
- Support filtering - Enable clients to get exactly what they need
- Implement HATEOAS - Make APIs self-documenting and discoverable