REST API Best Practices: A Guide Based On Many Books
Table of contents
REST API Best Practices: A Guide Based On Many Books
How to design a REST API? How many levels to nest resources? Relative or full URLs?
This post compiles REST API design recommendations from books and articles. Sources are at the end.
Check basic features of a REST API for context. I’ll focus on design choices here.
A REST API can return other formats, but I’ll use JSON for examples since it’s common.
Start with JSON response structure.
JSON response structure
No single structure is correct. Pick one and stay consistent across your API.
JSON:API
JSON:API standardizes responses for single and multiple resources.
{
"products": [{
"id": 1,
"title": "title"
}]
}
Twitter style API
An individual resource response looks like this:
{
"id":1,
"title":"title"
}
For multiple resources, Twitter returns an array:
[
{
"id":1,
"title":"title"
},
{
"id":2,
"title":"title"
}
]
Facebook style API
Individual resources look the same as Twitter:
{
"id": 1,
"title": "title"
}
Multiple resources go in a data array:
{
"data":[
{
"id": 1,
"title": "title"
},
{
"id": 2,
"title": "title"
}
]
}
Any of these works as long as you’re consistent and document the structure.
Relative or full URLs in HATEOAS?
HATEOAS lets clients navigate via links. There’s no consensus on relative vs full URLs. Microsoft uses full URLs, which is worth considering.
{"rel":"self",
"href":"https://adventure-works.com/customers/2"}
Nested objects in responses
When returning related resources (one-to-one, one-to-many, many-to-many), you can either include them or just return IDs.
Return IDs
Return only IDs and let clients fetch related resources separately. Good for large or rarely used data.
{
"posts": [{
"id": 1,
"title": "title",
"comments": [2,3,4]
}]
}
Watch out for n+1 queries - fetch related data in batch when possible.
GET /comments/2,3
Return full objects
Include related objects in the response to avoid extra requests. Larger responses but fewer round trips.
{
"posts":[
{
"id":1,
"title":"title",
"comments":[
{
"id":2,
"text":"..."
},
{
"id":3,
"text":"..."
},
{
"id":4,
"text":"..."
}
]
}
]
}
For flexibility, allow clients to request embeds:
GET /posts/1?embed=comments
Pagination
Don’t return entire datasets. For large collections, paginate with a fixed page size.
Include in your response:
- Total items
- Items per page
- Total pages
- Current page
- Previous page URL (if any)
- Next page URL (if any)
{
"data": [
{}
],
"pagination": {
"total": 60,
"items_per_page": 12,
"current_page": 1,
"total_pages": 5,
"next_url": "https://api.example.com/items?page=2",
}
}
API versioning
APIs change over time. Versioning helps consumers adapt.
Version unless your API is extremely simple and stable, or changes only by adding new endpoints without modifying existing ones. When in doubt, version it.
Options (not all strictly RESTful):
URL
Most common. Simple, but hard to update for clients storing URLs and harder to split across servers. Not strictly REST.
http://example.com/api/v1/
Domain
Use a versioned subdomain. Similar drawbacks. Not strictly REST.
http://apiv1.example.com
URL or body parameters
Keep the same URL, change a parameter. Not strictly REST.
http://example.com/resource?version=1
POST /places HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"version": "1.0"
}
HTTP headers
Keep URLs the same, but caching can be tricky. Add Vary: ApiVersion to avoid cache issues.
GET /resources HTTP/1.1
Host: example.com
ApiVersion: 1.0
Vary: ApiVersion
Content negotiation
Uses Accept header like format negotiation. REST-compliant since resources and representations are separate. Can confuse developers unfamiliar with HTTP.
Accept: application/vnd.github.v3+json
Resource nesting
Deep nesting gets unwieldy. Avoid it:
# Avoid
/clients/99/orders/88/products/77/variants
Limit to one or two levels. For deeper relationships, reference by ID directly:
# Better
/orders/88/products/77
DRF , White House API standards , and Microsoft all recommend keeping URIs simple. Hierarchical URLs can help SEO in some cases, but readability matters more for APIs.
Notifying about API changes
For breaking changes, notify users early and often (email, blog, etc.).
When deprecating an endpoint, return HTTP 410 (Gone) with a message linking to the new endpoint, updated docs, and an explanation if available.
Throttling
Limit requests to prevent abuse, reduce load, and control costs. Add a throttling policy for all users. You can also tie limits to paid plans.
URI characters
Follow RFC 3986 :
- Unreserved [0-9a-zA-Z - . _ ~]: no encoding
- Reserved [: / ? # [ ] @ ! $ & ’ ( ) * + , ; =]: encode if used for other than intended purpose
- Others: encode
- Unsafe [< > % { } | \ ^ `]: encode
Standards evolve for international characters. See W3C’s IDN and IRI article .
SEO in URLs
If search ranking matters, include descriptive text in URLs:
/posts/post-title ✅
/posts/99-post-title ✅
/posts/99 ❌
I hope this was useful.