Implementing RESTful APIs

1. Designing Resource-Based URLs

PatternGoodBad
CollectionGET /ordersGET /getOrders
ItemGET /orders/{id}GET /order?id=1
Sub-resourceGET /orders/{id}/itemsGET /orderItems?orderId=1
Action (non-CRUD)POST /orders/{id}/cancelPOST /cancelOrder
NamingPlural nouns, kebab-caseVerbs, snake_case

2. Using HTTP Methods Correctly

MethodPurposeIdempotentSafe
GETReadYesYes
POSTCreate / non-idempotent actionNoNo
PUTReplace entire resourceYesNo
PATCHPartial updateShould beNo
DELETERemove resourceYesNo
HEADHeaders onlyYesYes
OPTIONSDiscover allowed methods/CORSYesYes

3. Implementing HTTP Status Codes

CodeNameUse
200OKSuccessful GET/PUT/PATCH
201CreatedPOST that created a resource (return Location)
202AcceptedAsync work started
204No ContentSuccessful DELETE / empty body
400Bad RequestValidation failure
401UnauthorizedMissing/invalid auth
403ForbiddenAuth ok, not allowed
404Not FoundResource missing
409ConflictVersion/state conflict
422Unprocessable EntitySemantic validation failure
429Too Many RequestsRate limit hit
500Internal Server ErrorUnhandled server fault
502/503/504Bad GW / Unavailable / TimeoutUpstream issues

4. Designing Idempotent Operations

TechniqueHow
Idempotency-Key HeaderClient sends UUID; server stores result for replay
Natural KeysPUT uses client-supplied ID
Conditional WritesIf-Match with ETag for optimistic concurrency
Dedup WindowTTL store of processed keys (Redis)

Example: Idempotency-Key (Spring Boot 3)

@PostMapping("/payments")
public ResponseEntity<Payment> create(@RequestHeader("Idempotency-Key") String key,
                                       @RequestBody PaymentRequest req) {
    return idempotencyStore.findResult(key)
        .map(prev -> ResponseEntity.ok((Payment) prev))
        .orElseGet(() -> {
            Payment p = paymentService.charge(req);
            idempotencyStore.save(key, p, Duration.ofHours(24));
            return ResponseEntity.status(201).body(p);
        });
}

5. Implementing Content Negotiation

HeaderPurposeExample
AcceptClient requests media typeapplication/json, application/vnd.api+json
Content-TypeBody format being sentapplication/json; charset=utf-8
Accept-Languagei18n preferenceen-US, en;q=0.9
Accept-EncodingCompressiongzip, br
VaryTells caches which headers vary responseAccept, Accept-Encoding

6. Using HATEOAS Principles

ConceptDescription
HATEOASHypermedia As The Engine Of Application State
LinksSelf, next, related resources embedded
FormatsHAL, JSON:API, Siren
BenefitDiscoverable APIs; client doesn't hard-code URLs

Example: HAL response

{
  "id": "ord_123",
  "status": "PAID",
  "_links": {
    "self":   { "href": "/orders/ord_123" },
    "items":  { "href": "/orders/ord_123/items" },
    "cancel": { "href": "/orders/ord_123/cancel", "method": "POST" }
  }
}

7. Implementing Pagination

StyleParamsTrade-offs
Offset/Limit?page=2&size=20Easy; slow on deep pages
Cursor (Keyset)?after=xyz&limit=20O(1) deep paging; stable
Time-based?since=2026-01-01T00:00ZGood for streams
HeadersX-Total-Count, Link: rel="next"RFC 5988 navigation

8. Implementing Filtering and Sorting

NeedPattern
Filter equality?status=PAID&currency=USD
Filter range?createdAt[gte]=2026-01-01
Multi-value?status=PAID,PENDING
Sorting?sort=-createdAt,total (- = desc)
Search?q=john (free text)
StandardizedRSQL/FIQL: ?filter=status==PAID;total=gt=100

9. Handling Partial Responses

PatternExample
Field selection?fields=id,name,email
Sparse fieldsets (JSON:API)?fields[users]=name,email
GraphQLClient picks fields natively
Embed?embed=address,orders to inline relations

10. Using ETags for Caching

HeaderPurpose
ETagOpaque resource version (e.g. "a1b2")
If-None-MatchGET; server returns 304 if unchanged
If-MatchPUT/PATCH; 412 if version mismatch
Last-ModifiedTime-based alt; pair with If-Modified-Since
Strong vs Weak"abc" = byte-equal; W/"abc" = semantically equal

11. Implementing Bulk Operations

PatternDescription
Batch endpointPOST /orders/batch with array
Per-item statusReturn 207 Multi-Status with per-item codes
AtomicityAll-or-nothing flag; or partial success
Async batch202 Accepted + status URL
Bulk patchJSON Patch (RFC 6902): [{op,path,value}]

12. Implementing Request/Response Compression

AlgorithmHeaderNotes
gzipAccept-Encoding: gzipUniversal support
br (Brotli)Accept-Encoding: brBetter ratio, modern clients
zstdAccept-Encoding: zstdFast, growing support
TipSkip compression for < 1 KB or already-compressed payloads (images)