REST API Design

Updated June 3, 2026
M
Magic Magnets Team
9 min read

The Anatomy of a Great REST API

REST (Representational State Transfer) was defined in 2000 by Roy Fielding. Despite newer alternatives like GraphQL and gRPC, it remains the default for public-facing web services.

A well-designed REST API is self-describing. Developers can guess how it works without reading the documentation.

algobase.dev
A well-designed REST API maps HTTP verbs to CRUD operations on nouns (resources). GET /users/123 reads — idempotent and cacheable at the CDN or browser layer. POST /users creates a new resource and returns 201 with the created object. DELETE is idempotent — deleting something that doesn't exist returns 404, but the system state is unchanged. The rule: URLs are nouns (/users, /orders), HTTP methods are verbs (GET, POST, PATCH, DELETE). Never put the verb in the URL (/getUser, /createOrder).
1 / 1

CRUD operations — GET/POST/PUT/PATCH/DELETE on /users and /users/:id

Nouns, Not Verbs

In REST, your URLs (endpoints) should represent Resources (things), not actions. You let the HTTP methods (GET, POST, PUT, DELETE) define the action.

Prefer /users over /getAllUsers, /createUser, or /deleteUser?id=5. The URL names the resource; the HTTP method names the operation.

Quiz Time

Which URL design follows REST conventions for deleting a specific user?

The Standard CRUD Mapping:

  • GET /users — Read all users
  • POST /users — Create a new user
  • GET /users/123 — Read user #123
  • PUT /users/123 — Completely replace user #123
  • PATCH /users/123 — Partially update user #123 (e.g., just change their email)
  • DELETE /users/123 — Delete user #123
algobase.dev
Nested resources express ownership: GET /users/123/orders returns orders belonging to user 123. Keep nesting to two levels maximum. Query parameters handle filtering (?role=admin), sorting (?sort=-createdAt, minus for descending), and pagination. Avoid offset-based pagination at scale — it forces the database to scan and discard thousands of rows. Cursor-based pagination (WHERE id > cursor) uses an index scan and stays fast regardless of how deep you page.
1 / 1

Nested resource paths — /users/123/orders and cursor-based pagination

Nesting for Relationships

If resources are related, your URLs should reflect that hierarchy. Keep it logical, but don't nest too deeply. Cap it at two levels. /users/123/orders and /users/123/orders/456 are fine. /users/123/orders/456/items/789/reviews is not. Once you go that deep, reference the inner resource directly with /items/789/reviews.

Quiz Time

What is the correct HTTP status code to return after a successful POST request that creates a new resource?

Play by the HTTP Status Code Rules

HTTP has built-in status codes. Don't reinvent the wheel by returning a 200 OK with an error message buried in the JSON payload. Use the standards:

Success:

  • 200 OK: Standard success.
  • 201 Created: Used for successful POST requests.

Client Errors (The frontend messed up):

  • 400 Bad Request: Invalid input (e.g., malformed JSON or missing required fields).
  • 401 Unauthorized: The user isn't logged in / bad API key.
  • 403 Forbidden: Logged in, but trying to access something they don't own.
  • 404 Not Found: Resource doesn't exist.
  • 429 Too Many Requests: Rate limiting kicks in.

Server Errors (The backend messed up):

  • 500 Internal Server Error: The backend crashed.
  • 503 Service Unavailable: Servers are down or overloaded.
Quiz Time

A client sends a request with a valid token but tries to access another user's private data. What status code should the server return?

Quiz Time

Returning a 200 OK response with an error message inside the JSON body is an acceptable REST practice.

Filtering, Sorting, and Pagination

When a client hits GET /users, you don't want to return 10 million rows from your database. Use query parameters to tame lists: filtering via /users?role=admin&status=active, sorting via /users?sort=-createdAt (a leading minus conventionally means descending), and pagination via /users?limit=20&offset=40 to skip the first 40 rows and return the next 20.

Pro tip: For massive datasets, use cursor-based pagination (/users?after=cursor_xyz) instead of offsets. Offsets degrade significantly as you page deeper into large tables.

Quiz Time

Why is cursor-based pagination preferred over offset-based pagination for large datasets?

Versioning

APIs evolve. If you change the shape of your JSON payload, you might break thousands of clients that rely on the old format.

Version your API from day one. The most common approach is embedding it in the URL:

  • https://api.stripe.com/v1/customers

Alternatively, companies like Stripe use headers to specify API versions based on dates (e.g., Stripe-Version: 2023-10-16), keeping the URL clean.

Quiz Time

REST APIs should be versioned from day one, even if no breaking changes are planned yet.

Summary

URLs map to resources (use /articles, not /getArticles) and HTTP methods map to actions (GET, POST, PUT, DELETE). Show relationships in the URL hierarchy (e.g., /authors/12/articles) but avoid nesting beyond two levels. Use HTTP status codes correctly: 400 for bad client input, 404 for missing resources, 500 for backend failures. Control list responses with ?limit=10&sort=asc query parameters, and prefer cursor-based pagination for large datasets. Always version your API from the start (e.g., /v1/) so you can evolve the contract without breaking existing clients. Stripe and Twilio are the real-world gold standards worth studying.

GraphQL Deep Dive

How helpful was this content?

Comments

0/2000

Sign in to join the discussion

Saved on this device only

Sign in to sync progress across devices