DEV Community

楊東霖
楊東霖

Posted on • Originally published at devplaybook.cc

Understanding REST API Design Best Practices

A REST API is a contract. Every decision you make when designing it — URL structure, status codes, error formats, versioning — becomes a constraint your consumers must work around. Get it right upfront, and integration is smooth. Get it wrong, and you'll be maintaining backwards-compatibility hacks for years.

Here's what separates a well-designed REST API from a frustrating one.

Think in Resources, Not Actions

REST is resource-oriented. URLs identify things, HTTP methods express what you're doing to them.

# Wrong — action-based URLs
POST /createUser
GET  /getUserById?id=42
POST /deleteUser
POST /updateUserEmail

# Right — resource-based URLs
POST   /users          # create a user
GET    /users/42       # get a specific user
DELETE /users/42       # delete a user
PATCH  /users/42       # partially update a user
Enter fullscreen mode Exit fullscreen mode

The HTTP method carries the semantic meaning. Your URL should only identify what resource is being acted on.

Nested Resources

Use nesting for resources that belong to a parent:

GET  /users/42/orders         # all orders for user 42
GET  /users/42/orders/7       # specific order
POST /users/42/orders         # create order for user 42
Enter fullscreen mode Exit fullscreen mode

Limit nesting to one or two levels deep. Beyond that, use query parameters or flat resource paths.

HTTP Methods: Use Them Correctly

Each HTTP method carries a specific meaning that clients and infrastructure depend on.

Method Semantics Safe Idempotent
GET Read Yes Yes
POST Create No No
PUT Full replace No Yes
PATCH Partial update No No
DELETE Delete No Yes

Safe means the request doesn't modify state — caches and intermediaries can call it freely.

Idempotent means calling it multiple times produces the same result — important for retries on network failure.

# PUT should fully replace the resource
PUT /users/42
{ "name": "Alice Smith", "email": "alice@example.com", "role": "admin" }

# PATCH updates only the fields provided
PATCH /users/42
{ "email": "newalice@example.com" }
Enter fullscreen mode Exit fullscreen mode

Status Codes: Say What You Mean

HTTP status codes are a semantic protocol. Using them correctly means clients can handle responses without parsing error messages.

Success (2xx)

200 OK          — general success (GET, PUT, PATCH)
201 Created     — resource was created (POST), include Location header
204 No Content  — success with no body (DELETE)
Enter fullscreen mode Exit fullscreen mode

Client Errors (4xx)

400 Bad Request      — invalid input, validation failure
401 Unauthorized     — not authenticated (missing/invalid token)
403 Forbidden        — authenticated but not permitted
404 Not Found        — resource doesn't exist
409 Conflict         — state conflict (duplicate key, version mismatch)
422 Unprocessable    — valid format but semantic errors (can't process)
429 Too Many Requests — rate limited
Enter fullscreen mode Exit fullscreen mode

The most common mistake: returning 200 OK with an error in the body. That makes every client parse the response body just to check if the request succeeded.

Consistent Error Responses

Define a standard error format and use it everywhere:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email address is invalid",
    "field": "email",
    "requestId": "req_abc123"
  }
}
Enter fullscreen mode Exit fullscreen mode

Key principles:

  • Always include a machine-readable code (not just the HTTP status)
  • Include a human-readable message for debugging
  • Include a requestId so users can reference it in support tickets
  • For validation errors, include which field failed
// Multiple validation errors
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "requestId": "req_abc123",
    "details": [
      { "field": "email", "message": "Invalid email format" },
      { "field": "age", "message": "Must be 18 or older" }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Versioning

APIs change over time. Versioning lets you make breaking changes without breaking existing integrations.

URL Versioning (Most Common)

GET /v1/users/42
GET /v2/users/42
Enter fullscreen mode Exit fullscreen mode

Explicit, easy to understand, and works everywhere. The downside: technically violates REST (the version isn't a resource), but this is the pragmatic choice for most teams.

Header Versioning

GET /users/42
Accept: application/vnd.myapi.v2+json
Enter fullscreen mode Exit fullscreen mode

Cleaner URLs but harder to test in a browser. Use this if you have strong REST purists on the team or if your API is a long-lived platform product.

Rule: Once you publish a versioned API, never make breaking changes to it. Add fields (non-breaking), don't remove them. Introduce new behavior in /v2, not by silently changing /v1.

Pagination

Never return unbounded lists. Always paginate collections.

Cursor-Based (Preferred)

GET /posts?cursor=abc123&limit=20

Response:
{
  "data": [...],
  "pagination": {
    "nextCursor": "def456",
    "hasMore": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Cursor pagination is stable — inserting new records doesn't cause items to skip or duplicate across pages.

Offset-Based

GET /posts?page=3&per_page=20

Response:
{
  "data": [...],
  "pagination": {
    "page": 3,
    "perPage": 20,
    "total": 147,
    "totalPages": 8
  }
}
Enter fullscreen mode Exit fullscreen mode

Offset pagination is simpler to implement and supports random page access. It breaks under concurrent inserts, but that's often acceptable for non-real-time data.

Filtering, Sorting, and Searching

Use query parameters for these operations:

GET /users?status=active                    # filter
GET /posts?sort=created_at&order=desc       # sort
GET /users?q=alice                          # search
GET /orders?created_after=2026-01-01        # date range
GET /products?category=tools&min_price=10  # combine filters
Enter fullscreen mode Exit fullscreen mode

Keep query parameter names consistent across all your endpoints. If it's sort on one endpoint, it shouldn't be order_by on another.

Security Defaults

A few non-negotiable security practices:

Always use HTTPS. Redirect HTTP to HTTPS; don't serve APIs over plain HTTP.

Return minimal data. Don't include internal IDs, passwords, server paths, or internal state in responses. Think about what a consumer needs, not what's convenient to serialize.

Set CORS correctly. Don't use Access-Control-Allow-Origin: * for APIs that handle authenticated data.

Rate limit everything. Even internal APIs. Return 429 Too Many Requests with a Retry-After header when limits are hit:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711000000
Enter fullscreen mode Exit fullscreen mode

Documentation as Contract

The best APIs treat documentation as part of the product, not an afterthought. Use OpenAPI/Swagger to define your API schema:

paths:
  /users/{id}:
    get:
      summary: Get a user by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: User found
        '404':
          description: User not found
Enter fullscreen mode Exit fullscreen mode

An OpenAPI spec generates documentation, client SDKs, and mock servers. It's the single source of truth for your API contract.

Summary

Good REST API design comes down to consistency and respecting the HTTP protocol:

  • URLs identify resources (nouns), methods express actions (verbs)
  • Use HTTP methods and status codes as intended
  • Define a standard error format and use it everywhere
  • Version from day one — breaking changes need new versions
  • Always paginate collections
  • Document with OpenAPI

The payoff is an API that developers can integrate confidently, that's easy to maintain, and that grows without accumulating technical debt.


Level Up Your Dev Workflow

Found this useful? Explore DevPlaybook — cheat sheets, tool comparisons, and hands-on guides for modern developers.

🛒 Get the DevToolkit Starter Kit on Gumroad — 40+ browser-based dev tools, source code + deployment guide included.

Top comments (0)