Designing RESTful APIs
1. Defining Resource-Based URLs
| Pattern | Example |
|---|---|
| Collection | /orders |
| Item | /orders/{id} |
| Sub-resource | /orders/{id}/lines |
| Action (when needed) | /orders/{id}:cancel |
| Nouns, plural | Lowercase, hyphens |
2. Using HTTP Methods Correctly
| Method | Use | Idempotent | Safe |
|---|---|---|---|
| GET | Retrieve | Yes | Yes |
| POST | Create / non-idempotent | No | No |
| PUT | Replace | Yes | No |
| PATCH | Partial update | Should be | No |
| DELETE | Remove | Yes | No |
| HEAD/OPTIONS | Metadata/CORS | Yes | Yes |
3. Implementing API Versioning
| Style | Example |
|---|---|
| URL path | /v1/orders |
| Header | API-Version: 1 |
| Accept media type | application/vnd.acme.v1+json |
| Query param | ?version=1 |
4. Implementing HATEOAS
Example: HAL response
{
"id": "ord_123",
"status": "PAID",
"_links": {
"self": { "href": "/orders/ord_123" },
"cancel": { "href": "/orders/ord_123:cancel", "method": "POST" },
"lines": { "href": "/orders/ord_123/lines" }
}
}
5. Defining Request/Response Contracts
| Aspect | Convention |
|---|---|
| JSON case | camelCase or snake_case (be consistent) |
| Date format | ISO-8601 UTC |
| IDs | String for opaque IDs |
| Money | Object: { amount: 1000, currency: "USD" } |
| Enum | UPPER_SNAKE_CASE strings |
6. Handling Query Parameters
| Parameter | Example |
|---|---|
| Filter | ?status=PAID&customerId=123 |
| Sort | ?sort=placedAt,desc |
| Pagination | ?page=0&size=20 or ?cursor=...&limit=20 |
| Sparse fields | ?fields=id,total |
| Expand | ?include=customer,lines |
7. Designing Error Responses
Example: RFC 7807 Problem Details
{
"type": "https://api.acme.com/errors/validation",
"title": "Validation failed",
"status": 400,
"detail": "Field 'currency' must be 3 letters",
"instance": "/orders",
"traceId": "abc-123",
"errors": [
{ "field": "currency", "code": "pattern", "message": "must match ^[A-Z]{3}$" }
]
}
8. Implementing Content Negotiation
| Header | Purpose |
|---|---|
Accept | Client wants this media type |
Content-Type | Declares request body type |
Accept-Language | Preferred locale |
Accept-Encoding | gzip/br compression |
9. Designing Idempotent Operations
| Method | Idempotent by Default? |
|---|---|
| GET/HEAD/PUT/DELETE | Yes |
| POST | No → use Idempotency-Key header |
| PATCH | Depends on body design |
10. Using HTTP Status Codes Appropriately
| Code | Meaning |
|---|---|
| 200 | OK with body |
| 201 | Created (include Location) |
| 202 | Accepted (async) |
| 204 | No Content |
| 400 | Bad Request (client format) |
| 401 | Unauthenticated |
| 403 | Forbidden |
| 404 | Not Found |
| 409 | Conflict (version, dup) |
| 422 | Unprocessable Entity (validation) |
| 429 | Too Many Requests |
| 500/502/503/504 | Server-side / gateway issues |
11. Implementing Partial Responses
| Pattern | Detail |
|---|---|
| Sparse fieldsets | ?fields=id,name,email |
| GraphQL-like | Field selection in query |
| Server-side projection | Map at controller |