How to Design REST APIs Properly

REST API design is easy to start and hard to master. This guide covers the decisions that matter in production: resources, HTTP semantics, pagination, filtering, errors, authentication, versioning, idempotency, and documentation.

By Sandaruwan Jayasundara — Senior Software Engineer | Full Stack Developer

If you're building a backend for a web or mobile app, chances are you need a RESTful API. A well-designed REST API improves developer experience, makes frontends faster to build, reduces bugs, and prevents breaking changes later.

What “proper” REST API design means

  • Resources are modeled cleanly and consistently
  • HTTP methods and status codes have predictable meaning
  • Errors are structured and actionable
  • Pagination/filtering are standardized across endpoints
  • Auth, rate limiting, validation, and security are built-in
  • APIs evolve without breaking clients (versioning strategy)

1) Model your API around resources (not actions)

In REST API design, your URLs should represent nouns (resources), not verbs (actions). The behavior comes from the HTTP method.

# Good (resources)
GET    /users
GET    /users/{userId}
POST   /users
PATCH  /users/{userId}
DELETE /users/{userId}

# Avoid (verbs/actions)
POST /createUser
POST /updateUser
POST /deleteUser

When your API reads like a clean data model, it becomes easier to extend. For example, a user’s posts can become:

GET  /users/{userId}/posts
POST /users/{userId}/posts

2) Use HTTP methods correctly

  • GET fetches data (no side effects)
  • POST creates something or triggers server-side processing
  • PUT replaces a resource (full update)
  • PATCH partially updates a resource
  • DELETE removes a resource (or marks it deleted)

A simple rule: if you can replay a request safely, design it to be idempotent (more on that later).

3) Standardize status codes and responses

Status codes are part of your API contract. Keep them consistent:

Success

  • 200 OK (read/update)
  • 201 Created (new resource)
  • 204 No Content (delete)

Client/Server Errors

  • 400 Bad Request (validation)
  • 401 Unauthorized (no auth)
  • 403 Forbidden (no permission)
  • 404 Not Found
  • 409 Conflict
  • 429 Too Many Requests
  • 500 Internal Error

A good error format (actionable + consistent)

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email is invalid",
    "details": [
      { "field": "email", "reason": "Must be a valid email address" }
    ],
    "requestId": "req_9f8d2a..."
  }
}

Include a requestId so you can trace issues quickly in logs. This is a senior-level API design move that pays off in production.

4) Pagination, filtering, sorting: make it universal

Lists are where APIs often become inconsistent. Pick one approach and use it everywhere.

Offset pagination (simple)

GET /users?limit=20&offset=40

Cursor pagination (better for large datasets)

GET /users?limit=20&cursor=eyJpZCI6IjEwMDAifQ==

Cursor pagination avoids duplicates and missing items when data changes rapidly. If you’re building for scale, cursor-based pagination is usually the right long-term choice.

Filtering + sorting pattern

GET /orders?status=paid&sort=-createdAt

5) Authentication and authorization (don’t mix them)

Authentication answers: “Who are you?” Authorization answers: “What can you do?”

  • Use secure cookie sessions for browser apps (often best)
  • Use OAuth2 / JWT for external integrations and mobile apps
  • Implement role-based access control (RBAC) or policy-based rules

Keep authorization in a centralized layer (middleware/policies), not scattered across endpoints. That’s how you prevent privilege bugs.

6) Validation and contracts: treat your API like a product

Input validation is a security feature and a developer experience feature. Validate at the edge (request layer) and return structured errors.

Also: define an API contract using OpenAPI (Swagger). Contracts make it easier to generate clients, test, and document.

7) Versioning strategy: evolve without breaking clients

Versioning is not optional if your API will be used by more than one client. Common approaches:

  • URL versioning: /v1/users (simple, explicit)
  • Header versioning: Accept: application/vnd.myapi.v1+json (clean URLs)

Prefer backward-compatible changes whenever possible: add fields (never remove), add endpoints, and keep default behavior stable.

8) Idempotency: make retries safe

In real systems, clients retry. Networks fail. Timeouts happen. If a request can accidentally double-charge or double-create a record, you have a production incident waiting to happen.

Add an Idempotency-Key header for safe POST operations that create payments/orders:

POST /payments
Idempotency-Key: 7b1d2b8e-5c6c-4f9f-9b13-...

{ "amount": 5000, "currency": "LKR", "orderId": "ord_123" }

Store the key and response for a fixed time window. If the same key is seen again, return the same result.

9) Rate limiting and abuse prevention

Public and mobile APIs should include rate limiting:

  • Per-IP limits for anonymous traffic
  • Per-user/app limits for authenticated traffic
  • Stricter limits for expensive endpoints (search, exports)

Use 429 with clear headers: Retry-After, and optionally metadata like remaining quota.

10) Documentation: make your API easy to adopt

A REST API that’s hard to use is a slow product. Document:

  • Authentication (how to get tokens/sessions)
  • Common error codes and how to fix them
  • Examples for requests and responses
  • Pagination and filtering rules

Production REST API checklist

  • Consistent resource naming
  • Correct HTTP semantics
  • Structured errors + requestId
  • Standard pagination + sorting
  • Auth + authorization policies
  • Validation at the boundary
  • Versioning strategy
  • Idempotency for critical operations
  • Rate limiting and monitoring
  • OpenAPI documentation

Final thoughts

Designing REST APIs properly is a key skill for any backend or full stack engineer. The best APIs feel boring: predictable, consistent, safe to evolve, and easy to integrate.

I’m Sandaruwan JayasundaraSenior Software Engineer | Full Stack Developer. I write practical guides on scalable architecture, backend engineering, and cloud delivery. Explore more at sandaruwan.dev.

REST API Design API Best Practices Backend Engineering System Design