Designing a REST API is much less about writing endpoints and much more about designing a contract. Once the contract is bad, after a while everything turns into backwards-compat hacks “so the clients don’t break.”
In this post I’ve collected the principles I use for sustainable REST APIs in production: resource modelling, HTTP semantics, idempotency, pagination, the error model, and versioning.
1) Resource model: nouns, not verbs
URIs should describe “things,” not “actions”:
- ✅
/users/{id} - ❌
/getUser
HTTP methods carry semantics:
GET: read (idempotent)POST: create/command (may not be idempotent)PUT: replace (idempotent)PATCH: partial update (typically designed to be idempotent)DELETE: delete (idempotent)
2) Idempotency: surviving retries
Retries happen in production. That’s why an idempotency key — particularly for POST — makes a serious difference.
3) Pagination: cursor over OFFSET
As OFFSET grows, costs rise and consistency degrades. Cursor-based pagination is healthier in production.
4) Error contract: one format, actionable message
Use a single error model:
{
"error": {
"code": "INVALID_ARGUMENT",
"message": "email is required",
"requestId": "..."
}
}
5) Versioning: evolve without breaking
The rule: if there’s a breaking change, bump the version. Where possible, move forward through “additive change” — add new fields and deprecate the old ones.
Conclusion
The goal in REST API design: make correct usage easy for the client and incorrect usage hard. Details like idempotency, the error contract, and pagination are the places that cause the most problems in production.