Why this matters
Well-designed RESTful endpoints make your API predictable, easy to use, and scalable. As a Backend Engineer, you will:
- Expose data through stable URLs clients can trust.
- Model business concepts as resources, not ad-hoc actions.
- Handle pagination, filtering, sorting, and errors consistently.
- Support idempotent operations to avoid duplicate writes.
- Evolve versions without breaking existing clients.
Who this is for
- Backend Engineers building or refactoring HTTP APIs.
- Developers moving from monolith controllers to resource-centric design.
- Anyone integrating services that must communicate over HTTP.
Prerequisites
- Basic HTTP knowledge (methods, status codes, headers).
- Familiarity with JSON and request/response flow.
- Comfortable with a server framework in any language.
Concept explained simply
RESTful endpoints treat your domain as resources (nouns), manipulated with standard HTTP methods (verbs):
- GET: retrieve resources (safe, no side effects).
- POST: create a new resource or trigger a non-idempotent action on a collection.
- PUT: replace a resource (idempotent).
- PATCH: partially update a resource (idempotent if designed so).
- DELETE: remove a resource (idempotent by convention).
Keep paths noun-based and plural for collections. Use query parameters for filtering, sorting, and pagination. Return standard status codes and a consistent error body.
Mental model
Think in two layers:
- Resource Map: a tree of collections and items (e.g., /orders, /orders/{id}, /orders/{id}/items).
- Action Rules: which methods are allowed on each node and what they mean.
Step 2: Decide which methods each resource supports and why.
Step 3: Add read-only server-driven behaviors via query params (filter, sort, pagination).
Step 4: Define response shapes, status codes, and error format.
Design principles in practice
Resource naming
- Collections: /products, /users, /orders
- Single resource: /products/{product_id}
- Nested when it clarifies ownership: /users/{id}/orders (user’s orders)
- Avoid verbs in paths: prefer POST /users over /createUser
Collections vs items
GET /tasks # list tasks
POST /tasks # create task (201 + Location)
GET /tasks/{id} # fetch one
PUT /tasks/{id} # full replace
PATCH /tasks/{id} # partial update
DELETE /tasks/{id} # deleteFiltering, sorting, pagination
GET /products?category=shoes&price_lt=100&sort=price,-rating&page=2&per_page=20- Use clear operators (e.g., price_lt, created_gte).
- Return pagination metadata in response.
{
"data": [ ... ],
"page": 2,
"per_page": 20,
"total": 134,
"total_pages": 7
}Idempotency and updates
- PUT is idempotent: same request repeated yields same state.
- PATCH is partial update; design so repeated calls do not overshoot (e.g., set values, not increment counters).
- DELETE can return 204 even if already deleted (idempotent behavior).
Status codes and responses
- 200 OK for successful GET/PUT/PATCH with body.
- 201 Created + Location header for POST creating a resource.
- 204 No Content when no body is needed (DELETE, sometimes PATCH).
- 400/422 for validation errors; 404 not found; 409 conflict; 500 server error.
{
"error": {
"code": "validation_error",
"message": "Email is invalid",
"details": { "email": ["must be valid"] }
}
}Versioning
- Common: prefix path with /v1, /v2 when breaking changes are introduced.
- Keep minor additions backward compatible to avoid version churn.
Worked examples
Example 1: To-do app
# Collections and items
GET /v1/todos
POST /v1/todos
GET /v1/todos/{id}
PATCH/PUT /v1/todos/{id}
DELETE /v1/todos/{id}
# Filtering and sorting
GET /v1/todos?status=open&due_lte=2026-01-31&sort=-priority,created_at
# Example create response
201 Created
Location: /v1/todos/123
{
"id": "123",
"title": "Ship release",
"status": "open",
"priority": 2
}Example 2: E-commerce orders
# User orders
GET /v1/users/{user_id}/orders
POST /v1/users/{user_id}/orders
GET /v1/orders/{order_id}
PATCH /v1/orders/{order_id} # update status, address
# Order items as a nested resource
GET /v1/orders/{order_id}/items
POST /v1/orders/{order_id}/items
DELETE /v1/orders/{order_id}/items/{item_id}
# Conflicts
PATCH /v1/orders/{id} # 409 if state transition is invalid
Example 3: Blog posts and comments
# Posts
GET /v1/posts?author_id=7&tag=rest
POST /v1/posts
GET /v1/posts/{id}
PATCH /v1/posts/{id}
# Comments as child resource
GET /v1/posts/{id}/comments
POST /v1/posts/{id}/comments
DELETE /v1/comments/{comment_id}
# Like as a subresource (avoid verbs)
POST /v1/posts/{id}/likes
DELETE /v1/posts/{id}/likes/{user_id}Exercises
Do these on paper or in a text editor. Then compare with the solutions.
Design endpoints for books, authors, and checkouts. Include listing, creating, fetching one, updating, deleting; relate books to authors; handle checkout/return of a book by a user; add filtering and pagination for books.
- [ ] Collections and item routes for books and authors
- [ ] Link books to authors
- [ ] Checkout as an idempotent design
- [ ] Filters: genre, author_id; pagination
Reveal sample solution for Exercise 1
GET /v1/books?genre=fantasy&author_id=42&page=1&per_page=20
POST /v1/books
GET /v1/books/{book_id}
PATCH/PUT /v1/books/{book_id}
DELETE /v1/books/{book_id}
GET /v1/authors
POST /v1/authors
GET /v1/authors/{author_id}
PATCH/PUT /v1/authors/{author_id}
DELETE /v1/authors/{author_id}
# Relationship (books of an author)
GET /v1/authors/{author_id}/books
# Checkouts as a subresource (idempotent by specific key)
POST /v1/books/{book_id}/checkouts # body includes user_id
DELETE /v1/books/{book_id}/checkouts/{user_id}
# Create responses
201 Created + Location: /v1/books/abc123Design endpoints for movies and user ratings. Decide when to use POST vs PUT vs PATCH. Add pagination and sorting for top-rated movies.
- [ ] Movies collection and items
- [ ] Ratings as a user-movie relationship
- [ ] Idempotent rating updates
- [ ] Sorting and pagination for rankings
Reveal sample solution for Exercise 2
GET /v1/movies?genre=sci-fi&year_gte=2020&sort=-rating_count,-avg_rating&page=1&per_page=50
POST /v1/movies
GET /v1/movies/{movie_id}
PATCH /v1/movies/{movie_id} # partial updates like title, year
# Ratings (one rating per user per movie)
PUT /v1/movies/{movie_id}/ratings/{user_id} # idempotent upsert of a rating
GET /v1/movies/{movie_id}/ratings # list ratings
GET /v1/users/{user_id}/ratings # user’s ratings across movies
# Delete a rating
DELETE /v1/movies/{movie_id}/ratings/{user_id}
# Top lists
GET /v1/movies?sort=-avg_rating,-rating_count&page=1&per_page=20Common mistakes and self-check
- Verb-y paths (e.g., /createUser). Self-check: Are all paths nouns? If not, move verbs into HTTP methods or subresources.
- Inconsistent naming (singular vs plural). Self-check: Collections plural, items singular identifier?
- Leaking business actions into paths (/users/{id}/activate). Self-check: Can this be a state change via PATCH or a subresource (e.g., /activations)?
- Non-idempotent PUT/PATCH. Self-check: Repeat request; does state change again unexpectedly?
- Ambiguous filtering. Self-check: Are query params documented and validated (e.g., price_lt vs priceLessThan)?
- Wrong status codes. Self-check: Are you using 201 with Location for creations, 204 when no body is returned?
Practical projects
- Build a small task API with users, tasks, and labels. Include filters (status, due date), sorting, and pagination. Add 201 + Location for creations.
- Create an orders API that enforces valid status transitions. Return 409 on invalid transitions and document the allowed states.
- Refactor a legacy API that uses verb paths into resource-based endpoints without breaking clients; add /v2 for breaking changes.
Learning path
- Start: Model resources and relationships for your domain.
- Then: Add filtering, sorting, pagination; define error formats.
- Next: Enforce idempotency and proper status codes.
- Finally: Plan versioning and deprecation strategy.
Mini challenge
Design a minimal REST API for a survey app with surveys, questions, and responses. Include:
- [ ] Create/list surveys and questions
- [ ] Submit a response to a given survey
- [ ] Prevent duplicate responses by the same user per survey (idempotency)
- [ ] Pagination on listing responses
Hint
Think of responses as a subresource of surveys; use a stable key like user_id to ensure idempotency.
Quick Test & Progress
Take the quick test below to check understanding. Anyone can take it for free; logged-in users will have progress saved automatically.
Next steps
- Implement one practical project end-to-end and write short API docs.
- Add rate limiting and request validation to harden your API.
- Explore authentication/authorization patterns to secure endpoints.