1. Library
  2. Http and the Web
  3. Apis

Updated 10 hours ago

REST isn't a set of rules to memorize. It's a bet: if you organize your API around resources and use HTTP the way it was designed, developers will be able to guess how your API works before they read your documentation.

That bet pays off constantly. A developer who has never seen your API can guess that GET /users/123 retrieves a user, DELETE /users/123 removes them, and POST /users creates one. They didn't read your docs. They just knew.

This is the prize. Everything else—the naming conventions, the status codes, the response formats—exists to protect this predictability.

Resources, Not Procedures

The fundamental shift in REST is thinking about what things are, not what you can do to them.

Procedural thinking produces endpoints like /getAllUsers, /createNewOrder, /updateUserEmail. Resource thinking produces /users, /orders, /users/123. The HTTP method carries the verb; the URI identifies the noun.

This isn't just aesthetics. When every endpoint is a custom procedure, developers must learn each one individually. When endpoints are resources, developers learn the pattern once and apply it everywhere.

Find the nouns in your domain. Users, orders, products, comments, payments—these are your resources. If you're struggling to name something as a noun, you might be trying to expose a procedure that should be handled differently.

Hierarchies That Mean Something

Resources have relationships. A user has orders. An order has items. A post has comments.

Express these through nesting: /users/123/orders returns orders belonging to user 123. /posts/456/comments returns comments on post 456. The URI itself communicates the relationship.

But watch for a warning sign: if you're building URIs like /users/123/orders/456/items/789/modifiers/012, stop. Deep nesting isn't just ugly—it's telling you something is wrong with your model. Usually it means resources that should be accessible directly are being forced through unnecessary parents.

Ask: does someone ever need /items/789 directly? If yes, make it available at /items/789. The hierarchy in /orders/456/items can coexist with direct access.

URIs as Promises

A well-designed URI is a promise about what you'll find there.

Plural nouns for collections. /users is a collection, /users/123 is one user. This consistency means developers never wonder whether to use /user or /users.

Lowercase with hyphens. /user-profiles reads better than /UserProfiles or /user_profiles. More importantly, it avoids case-sensitivity confusion—some systems treat /Users and /users as different paths.

No implementation leakage. /users.php, /api/rest/users.json, /v1/userController/getAll—these expose implementation details that shouldn't matter to clients. When you change from PHP to Node, your URIs shouldn't change.

Short and guessable. If a developer can't guess the URI for something, your naming has failed. /api/v1/customers/123/orders works. /api/v1/customer-management-system/customer-resource/123/order-processing-module/order-collection doesn't.

HTTP Methods Mean Things

HTTP methods aren't arbitrary labels. They have semantics, and violating those semantics breaks the bet.

GET is sacred. GET must never change anything. No side effects, no state mutations, nothing. A crawler hitting your GET endpoints shouldn't delete data or send emails. If GET /users/123 sometimes creates a user, you've broken the web.

POST creates. POST /users creates a user. The server assigns the ID and returns it. POST is the one method that isn't idempotent—calling it twice creates two resources.

PUT replaces. PUT /users/123 replaces the entire user. If you send only {"name": "John"}, other fields should be cleared. PUT is idempotent—sending the same PUT repeatedly produces the same result.

PATCH modifies. PATCH /users/123 with {"email": "new@example.com"} changes only the email. Other fields stay untouched. Use PATCH when you want surgical updates.

DELETE removes. DELETE /users/123 removes the user. DELETE is idempotent—deleting something already deleted isn't an error, it's a no-op.

When you use these methods correctly, clients can make assumptions. They know GET is safe to retry. They know PUT and DELETE are safe to retry if the network fails. They know POST might not be. These assumptions let them build more robust integrations without reading your documentation.

Responses That Teach

Every response is a chance to help the client.

Return what you created. When POST creates a resource, return it. The client sent you partial data; you added an ID, timestamps, defaults. Give them back the complete picture. Include a Location header pointing to the new resource.

Return what you changed. When PUT or PATCH updates something, return the updated resource. Server-side logic might have modified other fields, normalized data, or updated timestamps. Don't make clients guess.

Be consistent. If /users/123 returns {"id": "123", "name": "John", "email": "..."}, then every user endpoint should return that structure. Inconsistency forces clients to handle special cases.

Collections need context. A list of 50 users is useless without knowing there are 1,500 total. Include pagination metadata:

{
  "data": [...],
  "total": 1500,
  "page": 2,
  "per_page": 50,
  "has_more": true
}

Errors That Help

Bad error handling is where APIs lose developer trust.

Status codes aren't optional. Returning 200 OK with {"success": false, "error": "..."} breaks HTTP. Clients check status codes first. Use 4xx for client mistakes, 5xx for server failures.

Say what went wrong. "Invalid request" helps no one. "Email address format is invalid" helps. "Email must contain @ symbol" helps more.

Provide stable codes. Human messages change. Error codes don't. INVALID_EMAIL_FORMAT lets clients handle specific errors programmatically without parsing English sentences.

Return all validation errors at once. If the email is invalid AND the age is too low, say both. Making developers fix one error, resubmit, find another error, resubmit again—that's hostile.

{
  "error": "validation_error",
  "fields": {
    "email": "Invalid email format",
    "age": "Must be at least 18"
  }
}

Never expose internals. Stack traces, SQL errors, file paths—these help attackers and confuse developers. Log them server-side; return something generic to clients.

Pagination: Pick Your Tradeoff

You can't return 100,000 users in one response. Pagination is required. The question is which kind.

Offset pagination is intuitive: ?page=2&per_page=50 or ?offset=100&limit=50. Developers understand it immediately. But it has a flaw: if items are added or deleted between page requests, you'll see duplicates or miss items. For slowly-changing data, this often doesn't matter. For real-time feeds, it's a problem.

Cursor pagination uses an opaque marker: ?cursor=abc123&limit=50. The response includes the next cursor. This handles changing datasets correctly—you'll never miss items or see duplicates. But you can't jump to page 47. You can only go forward (or backward with a previous cursor).

Neither is universally better. Offset works for admin dashboards where users jump between pages. Cursors work for infinite-scroll feeds where you're always moving forward.

Whichever you choose, set a default limit. An unlimited GET /users that returns 500,000 records will bring down your server and the client's.

Filtering Without Endpoint Sprawl

Without filtering, you end up with endpoints like /active-users, /admin-users, /users-created-this-week. This doesn't scale.

Query parameters scale:

GET /users?role=admin&active=true
GET /posts?author=123&published=true&created_after=2024-01-01

Sorting works the same way:

GET /users?sort=name           # Ascending
GET /users?sort=-created_at    # Descending (- prefix)
GET /users?sort=last_name,first_name  # Multiple fields

Field selection reduces payload size:

GET /users?fields=id,name,email

The pattern is consistent: the URI identifies the resource, query parameters modify how you want it.

Versioning: Plan for Change

APIs evolve. Clients depending on the old behavior shouldn't break when you ship the new one.

URI versioning is explicit: /v1/users and /v2/users can coexist. Developers see the version in every request. The downside is version proliferation in bookmarks, documentation, and client code.

Header versioning keeps URIs clean: Accept: application/vnd.api.v2+json. The downside is invisibility—you can't tell the version from the URL alone.

Most teams choose URI versioning for its simplicity. Whatever you choose, have a deprecation policy. Announce end-of-life dates. Include deprecation warnings in responses. Give clients time to migrate.

Security Isn't Optional

HTTPS everywhere. Not just for login—for everything. API keys, tokens, and data must be encrypted in transit. There's no excuse for plain HTTP in 2024.

Validate everything. Client data is hostile until proven otherwise. Check types, lengths, formats, ranges. Reject invalid input with clear errors.

Authenticate. Know who's calling. API keys, OAuth tokens, JWTs—pick something appropriate for your use case.

Authorize. Authentication tells you who; authorization tells you what they're allowed to do. User 123 can read their own orders but not user 456's.

Rate limit. Without limits, one misbehaving client can bring down your service for everyone. Return 429 Too Many Requests with a Retry-After header.

The Bet Restated

REST is a bet on predictability. Every principle here—resources over procedures, proper HTTP methods, consistent responses, helpful errors—serves that bet.

When you win, developers integrate with your API in hours instead of days. They guess correctly instead of searching documentation. They trust your API because it behaves like they expect.

When you violate these principles, you're not just breaking conventions. You're breaking the assumptions that let developers work efficiently. Every exception is a special case they must learn and remember.

The rules aren't arbitrary. They're the collected wisdom of what makes APIs predictable. Follow them, and you inherit that predictability for free.

Frequently Asked Questions About RESTful Design

Was this page helpful?

😔
🤨
😃
RESTful Design Principles • Library • Connected