REST API Design Best Practices

Core Principles
Design APIs that are:
Easy to read and work with
Hard to misuse
Complete and concise
Naming and URL Structure
Resource Naming
Use nouns to represent resources, not verbs
Good:
/items,/employeesBad:
/createItems,/getEmployees
Use plural nouns for collections (e.g.,
/ordersnot/order)Use hyphens for readability (e.g.,
inventory-management) instead of underscores
URL Structure
Implement logical grouping for nested resources
- Example:
/customers/{id}/orders
- Example:
Avoid going deeper than collection/resource/collection
Don't mirror database structure in URLs to prevent exposing unnecessary information
Versioning
Always version your APIs to prevent breaking changes
Options:
Path versioning:
/v1/store,/v2/store(more common)Query parameter:
?version=2
Data Handling
Pagination
Implement pagination for large datasets
Use cursor based pagination (more efficient, similar to what stripe does)
- Edit: Offset based is better for experiences where you want pages e.g. search in a booking system.
Example:
/items?lastItemId=1000&limit=20
Filtering and Sorting
Allow filtering through query parameters
- Example:
/users?lastName=Smith&age=30
- Example:
Support field selection to limit response data
Example:
/products?fields=id,name,priceCan be faster since less data needs to be fetched from the database and serialised before sending over the network
Enable sorting with clear parameters
- Example:
/posts?sort=+author,-datePublished
- Example:
API Operations
Idempotency
Ensure operations are idempotent where appropriate
Multiple identical requests should result in the same state
Particularly important for DELETE, PUT operations
For sensitive operations, consider using idempotency keys. Stripe does this for charge operations.
Async Operations
Use status code 202 for long-running operations, says the operation is accepted but not completed
Provide status endpoint for tracking progress e.g.
GET /orders/123/statusInclude status endpoint URL in Location header (e.g.
Location: /orders/123/status), helps clients to know where to get the status of the operation. This follows the HATEOAS principle (Hypermedia as the Engine of Application State) which states that the API should tell the client what it can do next.Consider supporting operation cancellation e.g.
DELETE /orders/123/cancel
Partial Responses
Support partial content retrieval for large resources e.g. video files. This is useful for large files that you don't want to download all at once, think Netflix movies... They would be HUGE if you downloaded the whole thing at once.
- First, client can make a HEAD request to check resource size (very fast request, no body needed):
HEAD /files/big-video.mp4
Response headers:
Accept-Ranges: bytes
Content-Length: 100000000
Content-Type: video/mp4
- Then client can request specific chunks using Range header:
GET /files/big-video.mp4
Range: bytes=0-1048575 # Request first 1MB
- Server responds with partial content:
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1048575/100000000
Content-Length: 1048576
[... first 1MB of data ...]
- Client can request next chunk:
GET /files/big-video.mp4
Range: bytes=1048576-2097151 # Request second 1MB
Security
Implement SSL/TLS encryption -> Use HTTPS.
Use proper authentication and authorization -> OAuth, JWT, etc.
Apply rate limiting to prevent DoS attacks
Be careful with error messages to avoid information leakage -> Best to always craft a good error message: No inner details, clear what's wrong and what to do next.
Error Handling
Return appropriate HTTP status codes
Provide clear error messages
Include enough information for debugging without exposing sensitive details
Use 204 for successful empty responses
Documentation
Use OpenAPI (formerly Swagger) for API documentation
Document:
Endpoint structure
Request/response formats
Authentication requirements
Error codes and messages
HATEOAS (Hypermedia as the Engine of Application State)
Consider implementing HATEOAS for better API navigation
Include related resource links in responses, you can also include
selflinks, they would describe other things you can do with the resource. If you created a post, they would include links to edit the post, delete the post, etc.
Example:
{
"orderId": "12345",
"status": "pending",
"total": 99.99,
"links": {
"self": {
"href": "/orders/12345",
"method": "GET"
},
"update": {
"href": "/orders/12345",
"method": "PUT"
},
"cancel": {
"href": "/orders/12345/cancel",
"method": "POST"
},
"payment": {
"href": "/orders/12345/payment",
"method": "POST"
},
"customer": {
"href": "/customers/789",
"method": "GET"
}
}
}
For each method, you provide the type of operation PLUS href and method for each operation.
Each thing should be self documenting. For POST with body, you can include contentType and schema, example:
"create": {
"href": "/orders",
"method": "POST",
"contentType": "application/json",
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array"
}
}
}
}






