REST API Design
REST (Representational State Transfer) is an architectural style — not a protocol — defined by Roy Fielding in his 2000 doctoral dissertation1. A web API that follows REST constraints is called a RESTful API. REST uses HTTP as its transfer protocol and leverages the existing semantics of HTTP methods, status codes, and headers.
REST constraints
| Constraint | Meaning |
|---|---|
| Client-Server | The client and server are separate concerns. The server does not know about the UI; the client does not know about storage. |
| Stateless | Each request contains all information needed to process it. The server stores no session state between requests. Authentication tokens (JWT) travel with every request. |
| Cacheable | Responses must declare whether they can be cached. Caching reduces load and improves latency. |
| Uniform Interface | Resources are identified by URIs. Representations are manipulated through standard HTTP methods. Responses include links to related resources (HATEOAS). |
| Layered System | Clients cannot tell whether they are talking to the end server or an intermediary (gateway, cache, load balancer). |
Resources and URIs
A resource is a noun — a thing the API exposes. URIs identify resources, not actions.
| ✅ Good (noun) | ❌ Bad (verb) |
|---|---|
GET /orders | GET /getOrders |
POST /orders | POST /createOrder |
GET /orders/42 | GET /order?id=42 |
DELETE /orders/42 | POST /deleteOrder/42 |
URI conventions
- Use plural nouns for collections:
/orders,/products,/users - Use lowercase with hyphens:
/order-itemsnot/orderItems - Nest to express ownership:
/users/7/orders— orders belonging to user 7 - Limit nesting depth to 2–3 levels; avoid
/users/7/orders/42/items/3/reviews
HTTP methods
| Method | Idempotent | Safe | Use |
|---|---|---|---|
GET | ✅ | ✅ | Retrieve a resource or collection. Must not modify state. |
POST | ❌ | ❌ | Create a new resource. The server assigns the ID. |
PUT | ✅ | ❌ | Replace a resource completely. The client provides the full representation. |
PATCH | ❌ | ❌ | Partial update — only the provided fields are changed. |
DELETE | ✅ | ❌ | Remove a resource. |
Idempotent — calling the same request multiple times has the same effect as calling it once. PUT /orders/42 with the same body is safe to retry; POST /orders would create duplicates.
Safe — the request does not change server state. GET and HEAD are safe; all others are not.
HTTP status codes
Use the right code — clients and intermediaries rely on them.
2xx — Success
| Code | Name | When to use |
|---|---|---|
200 OK | Success | GET, PUT, PATCH, DELETE completed successfully |
201 Created | Created | POST created a new resource; include Location: /orders/42 header |
204 No Content | No content | Success with no response body (e.g., DELETE) |
4xx — Client error
| Code | Name | When to use |
|---|---|---|
400 Bad Request | Bad request | Malformed JSON, missing required fields, invalid values |
401 Unauthorized | Unauthenticated | No token provided, or token is invalid/expired |
403 Forbidden | Forbidden | Token is valid but the user lacks permission |
404 Not Found | Not found | The resource does not exist |
409 Conflict | Conflict | Duplicate resource, optimistic lock failure |
422 Unprocessable Entity | Validation error | Well-formed request, but business validation failed |
5xx — Server error
| Code | Name | When to use |
|---|---|---|
500 Internal Server Error | Unexpected error | Unhandled exception — never expose stack traces |
503 Service Unavailable | Unavailable | The service is down for maintenance or overloaded |
Request and response format
Request
POST /orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
{
"customerId": 7,
"items": [
{ "productId": 101, "quantity": 2 }
]
}
Response
HTTP/1.1 201 Created
Content-Type: application/json
Location: /orders/42
{
"id": 42,
"customerId": 7,
"status": "PENDING",
"total": { "amount": 59.90, "currency": "BRL" },
"createdAt": "2026-04-28T14:30:00Z"
}
Error response
Standardise error responses so clients can handle them generically:
{
"status": 422,
"error": "Validation failed",
"message": "Product 101 is out of stock",
"path": "/orders",
"timestamp": "2026-04-28T14:30:00Z"
}
Spring Boot's @ControllerAdvice + @ExceptionHandler is the standard way to produce consistent error responses.
Versioning
APIs evolve. When a breaking change is unavoidable, version the API so existing clients are not broken:
| Strategy | Example | Notes |
|---|---|---|
| URI path | /v1/orders, /v2/orders | Most visible; easiest to test in a browser |
| Header | Accept: application/vnd.myapi.v2+json | Cleaner URIs; harder to test manually |
| Query param | /orders?version=2 | Avoid — version is not a filter |
URI path versioning is the most common choice for microservices. Major version increments (v1 → v2) signal breaking changes; minor improvements are added without a version bump.
Spring Boot quick reference
@RestController
@RequestMapping("/orders")
public class OrderController {
@GetMapping
public List<OrderOut> listOrders() { ... }
@GetMapping("/{id}")
public OrderOut getOrder(@PathVariable Long id) { ... }
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public OrderOut createOrder(@RequestBody @Valid OrderIn in) { ... }
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOrder(@PathVariable Long id) { ... }
}
-
FIELDING, R. T. Architectural Styles and the Design of Network-based Software Architectures. Doctoral dissertation, UC Irvine, 2000. Read online ↩
-
HTTP Status Codes — MDN Web Docs ↩