RESTful API Design Best Practices: Resource Modeling, Status Codes and Versioning
REST (Representational State Transfer) is the most widely adopted style for web APIs. Microsoft's Web API Design Best Practices in the Azure Architecture Center and Google's AIP (API Improvement Proposals) design specifications together form the industry-recognized baseline. Whether you build with Node.js Express, Python FastAPI, or Go Gin, following these conventions makes APIs easier to understand and maintain.
Resource Modeling: Nouns, Not Verbs
A RESTful API is organized around resources, each identified by a unique URI. Three core conventions guide URI design:
- Use plural nouns for collections:
/ordersis the orders collection,/orders/5is a single order. - Avoid verbs in URIs: HTTP methods already express the action —
POST /orderscreates an order, not/create-order. - Keep relationships simple: prefer a
collection/item/collectionhierarchy such as/customers/1/orders, and avoid deep nesting like/customers/1/orders/99/products.
Microsoft also advises against mirroring the internal database schema. REST models business entities and operations, not every table exposed as a resource — that increases the attack surface and risks data leakage. Add a mapping layer between the database and the API when needed.
HTTP Methods and Status Codes: Align Semantics
Each HTTP method has a defined meaning, and status codes should accurately reflect the outcome:
| Method | Semantics | Common Status Codes |
|---|---|---|
| GET | Read a resource | 200, 204, 404 |
| POST | Create a resource | 201 (Location header with new URI), 400, 405 |
| PUT | Full update, must be idempotent | 200, 201, 204, 409 |
| PATCH | Partial update | 200, 400, 409, 415 |
| DELETE | Delete a resource | 204, 404 |
For PATCH, use JSON Patch (RFC 6902) or JSON Merge Patch (RFC 7396). PUT must be idempotent — repeated submissions yield the same result — while POST and PATCH are not guaranteed to be. For long-running operations such as report exports, return 202 Accepted with a status endpoint the client can poll.
Pagination, Filtering and Sorting
Never return large datasets in full. Use query parameters:
GET /orders?limit=25&offset=0
GET /orders?status=shipped&sort=price
- limit / offset: control page size and start position, with an upper bound to prevent DoS.
- Filtering: pass conditions in the query string, e.g.
?status=shipped. - Sorting: use
sort=priceto pick the sort field. - Field selection: let clients request only the fields they need with
fields=id,name.
Note that sorting can reduce cache hit rates because query strings participate in the cache key.
A complete, well-formed endpoint
Putting the rules together, a clean order-lookup endpoint looks like this:
GET /v1/orders?status=paid&limit=10&offset=0
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
"items": [
{ "id": "ord_8f2a", "customer_id": "cus_31", "total": 1250, "status": "paid" }
],
"pagination": { "limit": 10, "offset": 0, "total": 42 }
}
Note the details: the resource uses the plural noun orders; the version lives in the URI prefix /v1; pagination parameters appear explicitly in the query string; and the response wraps a single pagination object instead of making the client guess the total count.
A unified error response format
Error handling is where APIs most often diverge. Standardize on a single error body so clients only have to parse it once:
{
"error": {
"code": "ORDER_NOT_FOUND",
"message": "Order ord_9x1b was not found",
"detail": { "resource": "orders", "id": "ord_9x1b" }
}
}
code is a machine-readable enum for client branching; message is for humans; detail carries optional context. Combined with status codes like 404, 409, and 422, both frontends and monitoring can pinpoint problems quickly.
Idempotency in practice
PUT must be idempotent, but a full replace does not cover every business need. A more robust approach is to accept an Idempotency-Key request header: the client generates a random key for each operation that should logically run once, and the server deduplicates by key. Payment and checkout endpoints especially benefit — if the network times out, the client can safely retry without double-charging or double-ordering.
Versioning: Keep Old Clients Working
APIs evolve. Microsoft documents four versioning approaches:
- URI versioning:
/v2/customers/3— simple and cache-friendly, but harder to maintain across many versions. - Query string versioning:
/customers/3?version=2— same resource, same URI. - Header versioning: a custom header such as
api-version: 2keeps the URI clean. - Media type versioning:
Accept: application/vnd.contoso.v2+json— most RESTful and most complex.
For most small and mid-sized projects, URI versioning is the simplest and most cache-friendly choice.
Versioning is not something you do on release day; it is a contract you plan in advance. A common situation: your API already has third-party clients, and you need to change the /customers/3 response from a flat structure to a nested one. Change it directly and old clients break immediately. A safer path is to ship a /v2 version, keep /v1 running through a transition period (say six months), mark the deprecation in the docs, and announce it to clients with a Deprecation response header — then take /v1 down only after its call volume clearly drops. That cadence is far less disruptive than a single big-bang migration, and it gives your partners a realistic window to move.
OpenAPI: Contract-First Design
Microsoft recommends adopting OpenAPI (OAS) for contract-first design: define the interface contract before implementing the code. Swagger/OpenAPI tooling can generate documentation and client libraries from the contract. Google's AIP program also provides an API Linter (linter.aip.dev) to automatically check design rules.
If you are still deciding between REST and GraphQL, read our REST vs GraphQL Comparison and Selection Guide first, then look at Website API Integration Basics for the consumer perspective.
Reference: Microsoft's Web API Design Best Practices https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
Reference: Google AIP design specifications https://google.aip.dev/
16IDC perspective
For indie developers and small teams, API design conventions are a one-time investment with long-term returns: consistent naming smooths frontend integration, automated testing, and future iterations. Before launch, plan your API Error Handling and Retry Strategy and fold API documentation into CI. More backend engineering practices live in the Backend Integration category.
Source: https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design