How to Design a REST API — Best Practices That Actually Matter
A poorly designed REST API costs a team weeks of confusion down the line — inconsistent naming, unclear status codes, and error responses that differ from endpoint to endpoint. This guide covers the practices that consistently make APIs easier to use, document, and maintain, with concrete examples you can apply today.
Resource Naming
Name endpoints after nouns (resources), not verbs (actions). Use plural nouns consistently and nest sub-resources under their parent.
Bad: Good: GET /getUser?id=42 GET /users/42 POST /createOrder POST /orders GET /user-orders/42 GET /users/42/orders POST /deleteProduct/9 DELETE /products/9
The HTTP verb already expresses the action, so the URL should only describe the resource being acted on.
HTTP Verbs and Status Codes
- GET — read a resource or collection, no side effects, safe to cache
- POST — create a new resource, returns
201 Createdwith aLocationheader - PUT — replace a resource entirely, idempotent
- PATCH — partially update a resource, also expected to be idempotent
- DELETE — remove a resource, returns
204 No Contenton success
200 OK - successful GET, PUT, PATCH 201 Created - successful POST that created a resource 204 No Content - successful DELETE, no response body 400 Bad Request - malformed request or invalid input 401 Unauthorized - missing or invalid authentication 403 Forbidden - authenticated but not allowed 404 Not Found - resource does not exist 409 Conflict - request conflicts with current state (e.g. duplicate) 422 Unprocessable Entity - valid syntax, invalid semantics 429 Too Many Requests - rate limit exceeded 500 Internal Server Error - unexpected server failure
Versioning
Version your API from day one, even if you only ever ship v1. The most common and easiest to reason about is URL path versioning:
GET https://api.example.com/v1/users/42 GET https://api.example.com/v2/users/42
This makes breaking changes explicit and lets you run v1 and v2 side by side while clients migrate. See our dedicated guide on API versioning strategies for header and query-param alternatives.
Pagination
Never return an entire table in one response. Use offset-based pagination for simplicity, or cursor-based pagination when the dataset changes frequently.
// Offset-based
GET /orders?page=2&limit=25
// Cursor-based
GET /orders?after=eyJpZCI6MTIzfQ&limit=25
// Response should include pagination metadata
{
"data": [ ... ],
"meta": { "page": 2, "limit": 25, "total": 340 }
}Consistent Error Response Format
Every endpoint should return errors in the same shape so clients can handle them generically instead of writing custom parsing per endpoint.
// 422 Unprocessable Entity
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email is already registered",
"field": "email",
"requestId": "req_9f3a2c"
}
}Including a stable code field (not just a human message) lets frontend code branch on error type without string matching, and a requestId makes support tickets much easier to trace in your logs.
Frequently Asked Questions
Use plural nouns for collections, such as /users and /orders. Use the plural form even for a single resource lookup, like /users/42, to keep the pattern consistent across your entire API.
Use 400 Bad Request for malformed input or failed validation, and 422 Unprocessable Entity when the request is well-formed but semantically invalid, such as a duplicate email during signup.
Offset-based pagination (page and limit query params) is simple and fine for most admin dashboards. Cursor-based pagination is better for large, frequently changing datasets because it avoids skipped or duplicated rows when data changes between requests.