Skip to content
Home
REST API Design: Best Practices and Conventions

REST API Design: Best Practices and Conventions

API Development API Development 8 min read 1571 words Beginner ExcellentWiki Editorial Team

REST (Representational State Transfer) is an architectural style for designing networked applications introduced by Roy Fielding in his 2000 doctoral dissertation. It relies on stateless, client-server communication over HTTP. A well-designed REST API is intuitive, consistent, and easy to consume. The best APIs — Stripe, GitHub, Twilio — feel natural to developers because their design conventions are predictable and follow established patterns.

This guide covers the conventions and best practices that make REST APIs successful, from resource naming to error handling, pagination, and versioning. These practices are informed by the HTTP specification (RFC 7230-7235), REST constraints from Fielding’s dissertation, and community conventions developed over two decades of API design.

Resource Naming

Resources are the core abstraction in REST. Each resource is identified by a URL. Good resource names are nouns (not verbs) and follow a consistent hierarchy. The URI identifies what the resource is, and the HTTP method identifies how to interact with it.

Use Nouns, Not Verbs

/goods    /orders   /users

Use HTTP methods to express actions: POST /orders creates an order; DELETE /orders/123 deletes it. Do not encode actions in URLs like /createOrder or /deleteUser. This separation of noun (resource) from verb (method) is fundamental to REST’s uniform interface constraint. When you find yourself wanting to add a verb to a URL, consider whether a different resource representation or a sub-resource better models the operation.

Plural Names

Use plural nouns for collections: /users, /orders, /products. Individual resources are referenced by ID: /users/42. Plural collection names are the near-universal convention in modern REST APIs. The alternative (singular, e.g., /user/42) works but is less common and less consistent when mixing collection and singleton resources.

Consistent Hierarchy

Model relationships with nested resources:

GET  /users/42/orders          # Orders for user 42
GET  /users/42/orders/5        # Specific order 5 for user 42

Avoid nesting more than three levels deep. Deep nesting (/a/1/b/2/c/3/d/4) indicates overly complex relationships that may be better handled by query parameters or separate endpoints. Consider denormalizing relationships for deeply nested hierarchies — for example, /orders?user=42 instead of /users/42/orders/5/items/7.

HTTP Methods

Each HTTP method has a defined purpose that should be respected:

MethodActionIdempotentSafe
GETRetrieve a resourceYesYes
POSTCreate a resourceNoNo
PUTReplace/update a resourceYesNo
PATCHPartial updateNoNo
DELETERemove a resourceYesNo

Idempotent means multiple identical requests produce the same server state. Safe means the request does not modify server state. These properties enable important infrastructure behaviors: GET requests are cachable and safe for retries; PUT can be safely retried if the connection fails; DELETE repeated requests return the same result (typically 404 Not Found after the first deletion).

GET

Use GET for read-only operations. GET requests should never change server state. Query parameters filter, sort, or paginate results:

GET /products?category=electronics&sort=price&page=2&limit=20

GET responses should include cache headers (Cache-Control, ETag) to enable HTTP caching. This reduces server load and improves response times. GitHub’s API uses ETag headers extensively — conditional GET requests return 304 Not Modified when resources have not changed.

POST

POST creates a new resource. The request body contains the resource data. The response should include a Location header pointing to the new resource and return status 201 Created:

POST /users
Content-Type: application/json

{
  "name": "Alice Smith",
  "email": "alice@example.com"
---

Response: 201 Created
Location: /users/42

POST is not idempotent — the same POST body submitted twice creates two resources unless your application implements idempotency keys (a common pattern for payment and order APIs). Use the Idempotency-Key header (popularized by Stripe) to allow safe retries.

PUT vs PATCH

PUT replaces the entire resource. PATCH applies a partial update (using JSON Patch RFC 6902 or JSON Merge Patch RFC 7396). PUT is idempotent — sending the same request twice produces the same state. PATCH is not idempotent because applying the same delta twice has different effects:

PUT /users/42
Content-Type: application/json

{
  "name": "Alice Smith",
  "email": "alice@example.com",
  "role": "admin"
---
PATCH /users/42
Content-Type: application/json

{
  "role": "admin"
---

Use PUT when the client is sending the complete resource state. Use PATCH for incremental updates. In practice, some APIs accept partial bodies on PUT as a convenience, but this violates the HTTP semantics of complete replacement.

Status Codes

HTTP status codes communicate the result of the request. Use the right code for each situation rather than returning 200 for every response.

Success Codes

  • 200 OK — General success for GET, PUT, PATCH responses with body
  • 201 Created — Resource created via POST, includes Location header
  • 202 Accepted — Request accepted for asynchronous processing (batch jobs, webhook triggers)
  • 204 No Content — Success with no response body (DELETE, sometimes PUT)

Client Error Codes

  • 400 Bad Request — Malformed request body or parameters; specific enough message helps debugging
  • 401 Unauthorized — Missing or invalid authentication; the client must authenticate
  • 403 Forbidden — Authenticated but not authorized for this resource
  • 404 Not Found — Resource does not exist; be careful not to leak existence information
  • 405 Method Not Allowed — HTTP method not supported for this endpoint
  • 409 Conflict — Resource state conflict (e.g., duplicate on create, version conflict on update)
  • 422 Unprocessable Entity — Validation errors beyond content type issues
  • 429 Too Many RequestsRate limiting exceeded; include Retry-After header

Server Error Codes

  • 500 Internal Server Error — Unexpected server error, no details in response
  • 502 Bad Gateway — Upstream server returned invalid response
  • 503 Service Unavailable — Temporary overload or maintenance; include Retry-After header

Error Responses

Return structured error bodies so clients can handle errors programmatically. Consistent error formats reduce integration effort significantly:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email is required",
    "details": [
      {
        "field": "email",
        "reason": "must not be null"
      }
    ]
  }
---

Follow RFC 9457 (Problem Details for HTTP APIs) for standardized error responses. The type field provides a URI identifying the error type, title is a short description, status is the HTTP status code, and detail is a human-readable explanation. Machine-readable error codes (not just human-readable messages) enable clients to implement logic based on error type.

Pagination

Collections should support pagination. Accept page and limit (or offset and limit) parameters for offset-based pagination:

GET /products?page=2&limit=20

Include pagination metadata in the response:

{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 157,
    "totalPages": 8
  }
---

For production APIs with frequent inserts or deletes, use cursor-based pagination (also called keyset pagination). Cursor pagination provides consistent results even when the underlying data changes between pages. Use Link headers (RFC 5988) to convey pagination URLs: Link: <https://api.example.com/products?cursor=abc123>; rel="next".

HATEOAS

Hypermedia as the Engine of Application State (HATEOAS) includes links in responses so clients can discover available actions dynamically:

{
  "id": 42,
  "name": "Alice Smith",
  "links": [
    { "rel": "self", "href": "/users/42" },
    { "rel": "orders", "href": "/users/42/orders" },
    { "rel": "update", "href": "/users/42", "method": "PUT" }
  ]
---

HATEOAS makes APIs self-documenting and reduces coupling between client and server. While not all REST APIs implement it, it is a defining characteristic of true REST as Roy Fielding originally defined it. See the HATEOAS Guide for implementation details.

Conclusion

Consistent REST API design reduces integration effort, makes your API predictable, and improves the developer experience. Choose conventions for naming, status codes, and error formats early, and apply them uniformly. A well-designed API is a pleasure to consume — it works as the developer expects, produces helpful errors when something goes wrong, and documents itself through consistent patterns.

Frequently Asked Questions

Should I use singular or plural resource names?

Use plural names for collections (/users, /orders). Plural is the overwhelming convention in modern REST APIs. Singular names work for singleton resources (e.g., /user/profile) but should not be mixed with collection endpoints. Consistency matters more than the specific choice.

How do I handle partial updates?

Use PATCH with JSON Merge Patch (RFC 7396) for simple updates or JSON Patch (RFC 6902) for complex operations. JSON Merge Patch sends only the fields to update. JSON Patch sends an array of operations (add, remove, replace, move, copy, test) for fine-grained control. Choose Merge Patch for simplicity and JSON Patch for bulk or complex updates.

What status code should I return for validation errors?

Return 422 Unprocessable Entity (RFC 4918) for validation errors. This is more specific than 400 Bad Request and distinguishes structural parsing errors from business logic validation. Include detailed error information in the response body indicating which fields failed validation and why.

Should my API use camelCase or snake_case for field names?

Use camelCase for JSON field names — this is the convention established by JavaScript/TypeScript ecosystems and is the most common in modern REST APIs. snake_case is traditional in older APIs (especially Python or Ruby projects). The most important rule is consistency — pick one and use it everywhere. If you must support both, use middleware to transform field names.

How do I design a search endpoint?

Use a dedicated /search endpoint or add query parameters to the collection endpoint. For simple search, query parameters work: GET /products?q=search+term. For complex search with multiple filters, sorting, and faceting, use a dedicated /products/search endpoint or POST-based search with a request body. Elasticsearch-style search APIs typically use POST because search queries are complex and may exceed URL length limits.

For more on API versioning strategies, see the API Versioning Guide. For HATEOAS and hypermedia, read HATEOAS REST APIs. For API-first development, see API-First Development Approach.

For a comprehensive overview, read our article on Api Authentication Guide.

For a comprehensive overview, read our article on Api Caching Strategies.

Section: API Development 1571 words 8 min read Beginner 756 articles in section Report inaccuracy Back to top