REST API interviews test your knowledge of HTTP fundamentals, resource design, authentication patterns, and real-world trade-offs. This guide covers 50 of the most common questions — with concise answers, examples, and comparison tables.
Quick reference
| Topic | Most asked questions |
|---|---|
| REST fundamentals | Stateless, uniform interface, constraints |
| HTTP methods | GET/POST/PUT/PATCH/DELETE, idempotency |
| Status codes | 2xx/3xx/4xx/5xx, common codes |
| URI design | Resource naming, versioning, hierarchy |
| Authentication | JWT, OAuth 2.0, API keys, Basic Auth |
| Request/Response | Headers, content negotiation, pagination |
| Caching | Cache-Control, ETag, conditional requests |
| Security | HTTPS, CORS, rate limiting, input validation |
| Performance | Compression, connection pooling, async |
| Advanced | HATEOAS, GraphQL vs REST, REST vs gRPC |
REST fundamentals
1. What does REST stand for, and what are its six architectural constraints?
REST stands for Representational State Transfer. Roy Fielding defined six constraints in his 2000 dissertation:
| Constraint | What it means |
|---|---|
| Client–Server | UI and data storage concerns are separated |
| Stateless | Each request contains all information needed; server holds no session state |
| Cacheable | Responses must label themselves cacheable or non-cacheable |
| Uniform Interface | Consistent interface between components (resources, representations, self-descriptive messages, HATEOAS) |
| Layered System | Client cannot tell whether it is connected directly to the server or to a middleman |
| Code on Demand (optional) | Server can send executable code (e.g., JavaScript) to clients |
An API that satisfies the first five constraints is RESTful.
2. What is the difference between REST and HTTP?
HTTP is a transfer protocol. REST is an architectural style that uses HTTP. A REST API leverages HTTP methods (GET, POST, PUT, DELETE), status codes (200, 404, 500), and headers — but REST could theoretically run over other protocols. In practice, "REST API" always means HTTP-based REST.
3. What does "stateless" mean in REST?
The server stores no client session state between requests. Every request must include all necessary information (credentials, resource identifiers, pagination state). This means:
- Pros: Any server in a cluster can handle any request; horizontal scaling is easy.
- Cons: Requests can be larger; no server-side session invalidation without extra infrastructure (e.g., token blacklists).
4. What is a resource in REST?
A resource is any named information that can be addressed with a URL — a user, an order, a product listing. Resources are nouns, not actions.
# Resources
GET /users/42
GET /orders/99/items
# Not REST (action in URL)
GET /getUser?id=42
POST /createOrder
5. What is the difference between a resource and a representation?
A resource is the concept (e.g., "the user with ID 42"). A representation is a specific encoding of that resource at a point in time — could be JSON, XML, or HTML. The same resource can have multiple representations selected via Accept headers.
HTTP methods
6. What are the main HTTP methods and when do you use each?
| Method | Purpose | Body | Idempotent | Safe |
|---|---|---|---|---|
| GET | Retrieve a resource | No | Yes | Yes |
| POST | Create a new resource or trigger action | Yes | No | No |
| PUT | Replace a resource entirely | Yes | Yes | No |
| PATCH | Partially update a resource | Yes | No* | No |
| DELETE | Remove a resource | Optional | Yes | No |
| HEAD | Like GET but response body omitted | No | Yes | Yes |
| OPTIONS | Describe communication options | No | Yes | Yes |
*PATCH is not guaranteed idempotent — a PATCH that increments a counter is not idempotent.
7. What is idempotency? Which HTTP methods are idempotent?
An operation is idempotent if calling it multiple times produces the same result as calling it once.
- Idempotent: GET, HEAD, PUT, DELETE, OPTIONS
- Not idempotent: POST (creates a new resource each time), PATCH (context-dependent)
Practical implication: it's safe to retry idempotent requests on network failure without risk of duplicate side-effects.
8. What is the difference between PUT and PATCH?
| PUT | PATCH | |
|---|---|---|
| Scope | Replace the entire resource | Update specific fields only |
| Missing fields | Omitted fields are set to null/default | Omitted fields are left unchanged |
| Idempotent | Yes | Not guaranteed |
| Body size | Full resource representation | Only changed fields |
// Current: { "name": "Alice", "email": "alice@example.com", "role": "admin" }
// PUT (must send full object)
PUT /users/42
{ "name": "Alice B.", "email": "alice@example.com", "role": "admin" }
// PATCH (only changed field)
PATCH /users/42
{ "name": "Alice B." }
9. When should you use POST vs PUT?
- POST to a collection to create a resource when the server assigns the ID:
POST /users→ server returns201 Createdwith/users/42. - PUT to a specific URL when the client knows the ID or is creating/replacing at a known location:
PUT /users/42.
10. Can GET requests have a body?
The HTTP specification does not forbid a body on GET, but most servers and proxies ignore it. Sending data in a GET body is non-standard and breaks caching. Use query parameters for filtering.
Status codes
11. What do the five HTTP status code classes mean?
| Class | Range | Meaning |
|---|---|---|
| 1xx | 100–199 | Informational — request received, continuing |
| 2xx | 200–299 | Success — request received and accepted |
| 3xx | 300–399 | Redirection — further action required |
| 4xx | 400–499 | Client error — bad request |
| 5xx | 500–599 | Server error — server failed to fulfil a valid request |
12. What are the most important HTTP status codes to know?
| Code | Name | Use it when |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST that created a resource |
| 204 | No Content | Successful DELETE or action with no response body |
| 301 | Moved Permanently | URL changed permanently |
| 304 | Not Modified | Conditional GET — cached version still valid |
| 400 | Bad Request | Malformed syntax, invalid parameters |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource does not exist |
| 405 | Method Not Allowed | HTTP method not supported on this endpoint |
| 409 | Conflict | State conflict (duplicate email, optimistic lock) |
| 410 | Gone | Resource permanently deleted |
| 422 | Unprocessable Entity | Syntactically valid but semantically wrong |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected server failure |
| 502 | Bad Gateway | Upstream server returned invalid response |
| 503 | Service Unavailable | Server temporarily overloaded or in maintenance |
13. What is the difference between 401 and 403?
- 401 Unauthorized: The request lacks valid authentication credentials. Client should authenticate and retry.
- 403 Forbidden: Authentication succeeded but the authenticated user lacks permission. Retrying with the same credentials will not help.
14. When should you return 404 vs 410?
- 404 Not Found: Resource doesn't exist (or you deliberately hide existence from unauthorized users).
- 410 Gone: Resource existed but has been permanently deleted. Useful to signal search engines to remove cached pages.
URI design
15. What are best practices for REST API URI design?
# Good
GET /users # list users
GET /users/42 # get user 42
POST /users # create user
PUT /users/42 # replace user 42
PATCH /users/42 # update user 42 fields
DELETE /users/42 # delete user 42
GET /users/42/orders # user's orders (sub-resource)
# Bad
GET /getUser?id=42
POST /users/create
GET /users/42/getOrders
DELETE /deleteUser/42
Rules:
- Use nouns, not verbs — the HTTP method is the verb
- Use lowercase and hyphens for readability (
/blog-posts, not/blogPosts) - Use plural for collection endpoints (
/users,/orders) - Keep hierarchy shallow — avoid more than 3 levels
- No trailing slashes (
/users/42, not/users/42/)
16. How do you handle actions that don't map to CRUD?
Options:
- Sub-resource action:
POST /orders/42/cancel - Controller resource:
POST /payments/42/refund - RPC-style endpoint (last resort):
POST /actions/send-email
Favour option 1 or 2 — they stay RESTful by treating the action as a resource creation.
17. What are the main strategies for API versioning?
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URI path | /v1/users |
Obvious, cacheable | URL changes, multiple paths to maintain |
| Query parameter | /users?version=1 |
Easy to add | Can be ignored by caches |
| Custom header | API-Version: 1 |
Clean URLs | Less visible, harder to test in browser |
| Content negotiation | Accept: application/vnd.api+json;version=1 |
Fully RESTful | Complex to implement |
Most common in practice: URI path versioning because it's explicit, easy to cache, and easy to test.
Authentication
18. What are the main API authentication methods?
| Method | How it works | Best for |
|---|---|---|
| API Key | Secret token in header or query param | Server-to-server, simple integrations |
| Basic Auth | Authorization: Basic base64(user:pass) |
Simple, internal APIs over HTTPS only |
| Bearer token / JWT | Authorization: Bearer <token> |
Stateless, scalable user auth |
| OAuth 2.0 | Token exchange protocol with scopes | Third-party access delegation |
| mTLS | Mutual TLS certificate validation | High-security service-to-service |
19. How does JWT authentication work in a REST API?
- Client sends credentials (username/password) to
POST /auth/login. - Server validates and returns a signed JWT (JSON Web Token).
- Client stores the JWT (memory or
httpOnlycookie). - On each request, client sends
Authorization: Bearer <jwt>. - Server verifies the signature without a database lookup — the token is self-contained.
A JWT has three Base64URL-encoded parts: header.payload.signature.
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiIsImV4cCI6MTcwMDAwMH0.abc123
Expiry: short-lived access tokens (15 min) + long-lived refresh tokens (7 days).
20. What is OAuth 2.0 and how does it differ from JWT?
OAuth 2.0 is an authorization framework — a protocol for a user to grant a third-party limited access to their resources without sharing credentials.
JWT is a token format — a compact, self-contained way to represent claims. OAuth 2.0 often uses JWTs as its access tokens, but they solve different problems.
OAuth 2.0 main flows:
| Flow | Use case |
|---|---|
| Authorization Code | Web apps acting on behalf of a user |
| Authorization Code + PKCE | Mobile/SPA apps |
| Client Credentials | Machine-to-machine (no user) |
| Device Code | TV/IoT devices with limited input |
Request and response design
21. What is content negotiation?
Content negotiation lets the client specify what representation format it wants:
GET /users/42
Accept: application/json
# Server responds
HTTP/1.1 200 OK
Content-Type: application/json
{ "id": 42, "name": "Alice" }
If the client sends Accept: application/xml and the server doesn't support XML, it should return 406 Not Acceptable.
22. What are the most important HTTP request headers?
| Header | Purpose |
|---|---|
Authorization |
Authentication credentials |
Content-Type |
Media type of the request body |
Accept |
Media type(s) the client can handle |
Accept-Language |
Preferred response language |
Accept-Encoding |
Accepted compression (gzip, br) |
Cache-Control |
Caching directives |
If-None-Match |
Conditional request (ETag) |
If-Modified-Since |
Conditional request (date) |
X-Request-ID |
Correlation ID for distributed tracing |
23. What is a good error response format?
Consistent error responses help clients handle failures programmatically:
{
"status": 422,
"error": "Unprocessable Entity",
"message": "Validation failed",
"details": [
{ "field": "email", "message": "must be a valid email address" },
{ "field": "age", "message": "must be at least 18" }
],
"requestId": "req_abc123",
"timestamp": "2026-07-15T10:00:00Z"
}
Standards to consider: RFC 7807 Problem Details (application/problem+json), which defines type, title, status, detail, instance.
24. How do you implement pagination in a REST API?
Three common approaches:
| Strategy | Example | Best for |
|---|---|---|
| Offset/limit | GET /users?offset=20&limit=10 |
Admin UIs, random access |
| Page/size | GET /users?page=3&size=10 |
Simple UIs |
| Cursor/keyset | GET /users?after=eyJ...&limit=10 |
Infinite scroll, high-traffic |
Cursor pagination is preferred for large datasets — it is stable (no "page drift" when records are inserted/deleted) and more efficient (no OFFSET scan).
Response should include metadata:
{
"data": [...],
"pagination": {
"total": 1500,
"limit": 10,
"nextCursor": "eyJpZCI6NTB9"
}
}
25. How do you design filtering, sorting, and searching endpoints?
# Filtering
GET /products?category=shoes&inStock=true&minPrice=50&maxPrice=200
# Sorting (prefix with - for descending)
GET /products?sort=-price,name
# Searching
GET /products?q=running+shoes
# Field selection (sparse fieldsets)
GET /users?fields=id,name,email
Keep these as query parameters on collection endpoints. Avoid building separate search endpoints unless full-text search complexity justifies it.
Caching
26. What is the Cache-Control header and what are its key directives?
Cache-Control: public, max-age=3600
| Directive | Meaning |
|---|---|
public |
Any cache (browser, CDN, proxy) may cache |
private |
Only the end-user's browser may cache |
no-cache |
Must revalidate with server before using cached copy |
no-store |
Never cache this response |
max-age=N |
Cache is fresh for N seconds |
s-maxage=N |
Like max-age but for shared caches (CDN) only |
must-revalidate |
Expired cache must be revalidated before use |
immutable |
Response will never change (safe for long max-age) |
27. What are ETags and how do conditional requests work?
An ETag (entity tag) is a version token for a resource. The flow:
# First request
GET /products/7
→ 200 OK
ETag: "v3-abc123"
Cache-Control: max-age=300
# After cache expires, conditional request
GET /products/7
If-None-Match: "v3-abc123"
→ 304 Not Modified (body omitted — client uses cached version)
# OR
→ 200 OK (new ETag if resource changed)
ETag: "v4-def456"
If-Modified-Since works similarly using dates instead of tokens.
28. What is the difference between client-side and server-side caching?
| Client-side | Server-side | |
|---|---|---|
| Location | Browser, mobile app | Redis, Memcached, CDN, reverse proxy |
| Control | Cache-Control, ETag headers |
Application code, cache middleware |
| Scope | Per-user | Shared across all users |
| Use case | Static assets, user-specific data | Database query results, computed aggregates |
Security
29. Why is HTTPS mandatory for REST APIs?
Without HTTPS (TLS):
- Credentials are sent in plaintext (Basic Auth, JWT in headers) — trivially interceptable on open networks.
- Responses can be tampered (man-in-the-middle attacks).
- Request bodies are exposed — POST payloads including passwords and PII.
Always serve APIs over HTTPS, redirect HTTP to HTTPS, and use HSTS (Strict-Transport-Security).
30. What is CORS and how do you configure it correctly?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism. Browsers block cross-origin requests unless the server explicitly allows them via headers.
# Server adds these headers
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Never use Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true — this is a security vulnerability.
31. What is rate limiting and why is it important?
Rate limiting caps how many requests a client can make in a time window. Without it:
- Abusive clients can overload your server (DoS).
- Scrapers can extract all your data.
- Buggy clients can generate runaway request loops.
Common response for exceeded limit:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1720000000
Common algorithms: token bucket, sliding window, fixed window.
32. What is an idempotency key?
An idempotency key is a client-generated unique token that makes non-idempotent operations (POST) safe to retry.
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{ "amount": 100, "currency": "USD" }
The server stores the key and, on retry, returns the original response instead of processing again. Stripe and many payment APIs use this pattern.
33. What are common REST API security vulnerabilities?
| Vulnerability | Example | Mitigation |
|---|---|---|
| Broken Object Level Auth (BOLA/IDOR) | GET /users/43 returns another user's data |
Validate ownership on every request |
| Broken Function Level Auth | Regular user calls DELETE /admin/users/42 |
Role checks on every endpoint |
| Excessive Data Exposure | Returning full DB row with password hashes | Serialize only needed fields |
| Injection | SQL in query parameter | Parameterized queries, input validation |
| Mass Assignment | PUT /users/42 sets role: admin |
Allowlist writable fields |
| Missing Rate Limiting | Brute-force on /auth/login |
Rate limit by IP and account |
These map to the OWASP API Security Top 10.
Performance
34. How does HTTP compression work in APIs?
Client signals accepted encodings:
GET /large-list
Accept-Encoding: gzip, br
Server compresses response body and signals the encoding:
200 OK
Content-Encoding: gzip
Gzip typically reduces JSON responses by 70–90%. Enable at the reverse proxy (nginx, Caddy) level rather than in application code.
35. What is the N+1 query problem in APIs?
When an API fetches a list and then makes a separate database query per item:
GET /orders → fetch 20 orders
→ SELECT * FROM users WHERE id = 1
→ SELECT * FROM users WHERE id = 2
... (20 queries)
Fix: Use JOIN or IN queries to fetch related data in bulk. In GraphQL, use DataLoader. For REST, consider compound documents or embedded related resources.
36. What is connection pooling and why does it matter for APIs?
Opening a new database connection per request is expensive (TLS handshake, auth, memory). A connection pool maintains a fixed set of open connections and lends them to requests.
Without pooling, a sudden traffic spike creates hundreds of DB connections simultaneously, often causing DB overload or rejection. Popular pools: PgBouncer (PostgreSQL), HikariCP (Java), pg-pool (Node.js).
API design patterns
37. What is HATEOAS?
HATEOAS (Hypermedia as the Engine of Application State) is the fullest implementation of REST's uniform interface constraint. Responses include links to related actions, so the client navigates the API by following links rather than constructing URLs.
{
"id": 42,
"status": "pending",
"total": 99.99,
"_links": {
"self": { "href": "/orders/42" },
"cancel": { "href": "/orders/42/cancel", "method": "POST" },
"payment": { "href": "/orders/42/payment" }
}
}
Few real-world APIs implement full HATEOAS — it adds complexity that most clients don't use.
38. What is the difference between REST and GraphQL?
| REST | GraphQL | |
|---|---|---|
| Endpoints | One per resource | Single /graphql endpoint |
| Data fetching | Fixed response shape | Client specifies exact fields needed |
| Over/under-fetching | Common | Eliminated |
| Versioning | URL or header | Schema evolution with deprecation |
| Caching | Easy (GET + URL) | Harder (POST, custom tooling) |
| Learning curve | Low | Higher |
| Best for | Simple CRUD, public APIs | Complex data graphs, mobile (bandwidth) |
39. What is the difference between REST and gRPC?
| REST | gRPC | |
|---|---|---|
| Protocol | HTTP/1.1 or HTTP/2 | HTTP/2 |
| Format | JSON (text) | Protocol Buffers (binary) |
| Schema | Optional (OpenAPI) | Required (.proto files) |
| Streaming | Limited (SSE, WebSocket) | Native bidirectional streaming |
| Performance | Slower (text serialization) | Faster (binary, HTTP/2 multiplexing) |
| Browser support | Native | Limited (grpc-web) |
| Best for | Public APIs, web apps | Internal microservices, low-latency |
40. What is OpenAPI (Swagger) and why does it matter?
OpenAPI (formerly Swagger) is a language-agnostic specification for describing REST APIs in YAML/JSON. Benefits:
- Documentation: Auto-generate Swagger UI / Redoc interactive docs
- Client SDKs: Generate typed clients in any language
- Mocking: Create mock servers for frontend development
- Validation: Validate requests and responses against schema
- Contract testing: Verify API matches spec in CI
openapi: 3.1.0
info:
title: Users API
version: 1.0.0
paths:
/users/{id}:
get:
summary: Get a user by ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
Real-world design questions
41. How would you design an API endpoint for bulk operations?
Three common patterns:
# Option 1: Batch endpoint
POST /users/batch
[
{ "name": "Alice" },
{ "name": "Bob" }
]
# Option 2: Individual calls in parallel (client-side)
# Let client call POST /users multiple times concurrently
# Option 3: Async job
POST /import/users
→ 202 Accepted
{ "jobId": "job_abc123" }
GET /import/jobs/job_abc123
→ { "status": "processing", "progress": 45 }
Return per-item results in batch responses so the client knows which items succeeded and which failed.
42. How would you handle long-running operations?
Use the async job pattern:
POST /reports/generate→202 Acceptedwith{ "jobId": "..." }- Client polls
GET /jobs/{id}or receives webhook when done - Job endpoint returns
{ "status": "completed", "resultUrl": "/reports/42" }
Alternative: Server-Sent Events (SSE) for real-time progress on long operations.
43. How do you design a REST API for search?
# Simple search
GET /products?q=blue+sneakers
# Faceted search with filters
GET /products?q=sneakers&brand=Nike&color=blue&sort=-rating&page=1&limit=20
# Complex search (POST acceptable when query is too complex for URL)
POST /products/search
{
"query": "blue sneakers",
"filters": {
"brand": ["Nike", "Adidas"],
"priceRange": { "min": 50, "max": 200 }
},
"sort": [{ "field": "rating", "order": "desc" }]
}
For complex queries that exceed URL length limits, POST /search is acceptable even though it creates no resource.
44. How do you implement webhooks?
Webhooks let the server push events to a client URL:
- Client registers:
POST /webhooks { "url": "https://app.io/hook", "events": ["order.paid"] } - Server sends HTTP POST to the client URL when event occurs
- Server signs the payload:
X-Signature: sha256=<hmac_of_body> - Client verifies signature before processing
- Client returns
200 OK— any other status triggers retry with exponential backoff
45. How do you document REST APIs?
- OpenAPI spec as the source of truth (code-first with decorators or spec-first)
- Interactive docs: Swagger UI, Redoc, or Scalar
- Changelog: document breaking vs non-breaking changes
- Examples: include request/response examples in spec
- Authentication guide: separate narrative doc for auth flows
Testing
46. How do you test REST APIs?
| Test type | Tool | What it checks |
|---|---|---|
| Unit tests | Jest, pytest | Individual functions, validation logic |
| Integration tests | Supertest, httpx | Endpoint behavior with real DB |
| Contract tests | Pact, Dredd | API matches OpenAPI spec |
| Load tests | k6, Locust | Performance under concurrent load |
| Security tests | OWASP ZAP, Burp Suite | Auth, injection, CORS |
| Manual/exploratory | Postman, Bruno, curl | Ad-hoc debugging |
47. What is contract testing?
Contract testing verifies that a provider (API) and consumer (client) agree on the interface. Unlike integration tests that deploy both sides together, contract tests run independently:
- Consumer records a contract (expected requests and responses) from unit tests
- Provider verifies the real API satisfies all consumer contracts in CI
Tools: Pact (most popular), Dredd (validates against OpenAPI spec).
Advanced
48. What is the difference between synchronous and asynchronous APIs?
| Synchronous | Asynchronous | |
|---|---|---|
| Response timing | Immediately | Later (polling or webhook) |
| HTTP code | 200 OK |
202 Accepted |
| Use case | Fast operations (<500ms) | Long-running jobs, batch processing |
| Timeout risk | High for slow ops | Low — client gets job ID and moves on |
49. What are some common REST API anti-patterns to avoid?
| Anti-pattern | Example | Better approach |
|---|---|---|
| Verbs in URLs | POST /createUser |
POST /users |
| Inconsistent naming | /usersList, /get_orders |
/users, /orders |
| Everything is a GET | GET /users/42/delete |
DELETE /users/42 |
| Hardcoded version in domain | api-v2.example.com |
/v2/... path |
| Returning 200 with error body | 200 { "error": "not found" } |
Proper 4xx status codes |
| Ignoring HTTP caching | No Cache-Control on read endpoints |
Add cache headers |
| Giant catch-all endpoint | POST /api with action in body |
Separate endpoint per resource |
50. What would you include in a REST API checklist before go-live?
Authentication & authorisation
✓ All endpoints require auth (except explicitly public ones)
✓ Authorization checked for every resource access (ownership validation)
✓ Tokens have appropriate expiry
✓ Sensitive endpoints rate-limited
Input & output
✓ All inputs validated and sanitized
✓ Responses don't expose internal fields (password hashes, internal IDs)
✓ Consistent error format (RFC 7807 or custom)
Performance
✓ Database queries indexed
✓ Connection pooling configured
✓ Cache-Control headers on read endpoints
✓ Gzip compression enabled
Security
✓ HTTPS enforced (HTTP → HTTPS redirect + HSTS)
✓ CORS configured for your domains only
✓ No secrets hardcoded or leaked in responses
✓ SQL parameterized (no raw concatenation)
Observability
✓ Request ID on every response
✓ Structured logging with status codes and latency
✓ Distributed tracing headers forwarded
✓ Health check endpoint (GET /health → 200)
Documentation
✓ OpenAPI spec complete and up to date
✓ Authentication flow documented
✓ Changelog for breaking changes
Common mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Using 200 for all responses | Clients can't detect errors programmatically | Use correct 4xx/5xx codes |
| No pagination on collection endpoints | Memory exhaustion, slow responses | Default to limit=20, max limit=100 |
| Leaking stack traces in 500 errors | Reveals internal architecture | Log internally, return generic message |
| Storing JWTs in localStorage | XSS can steal tokens | Use httpOnly cookies |
| Not validating JWT signature | Accepting forged tokens | Always verify signature and expiry |
| Using GET for state-changing operations | Browsers/crawlers trigger side effects | Use POST/PUT/DELETE |
| No idempotency on payments | Double charges on retry | Require Idempotency-Key header |
| Blocking I/O in request handler | Thread exhaustion under load | Use async/non-blocking I/O |
REST vs alternatives
| REST | GraphQL | gRPC | SOAP | |
|---|---|---|---|---|
| Transport | HTTP | HTTP | HTTP/2 | HTTP/SMTP |
| Format | JSON/XML | JSON | Protobuf | XML |
| Schema | Optional | Required | Required | Required (WSDL) |
| Caching | Built-in (HTTP) | Custom | Custom | Custom |
| Browser support | Full | Full | Limited | Full |
| Maturity | Very high | High | High | Very high (legacy) |
| Tooling | Excellent | Excellent | Good | Moderate |
| Learning curve | Low | Medium | Medium-high | High |
FAQ
Q: Is REST the same as JSON API? REST is an architectural style; JSON is one possible data format. REST APIs can use XML, MessagePack, or any format. JSON became the default because it maps naturally to JavaScript and is human-readable.
Q: Should I always follow REST strictly? Pragmatism matters. Strict HATEOAS is rarely worth the complexity. Focus on: correct HTTP methods, meaningful status codes, consistent resource naming, and proper authentication. Those give you 90% of the benefit.
Q: What does "RESTful" vs "REST" mean? Technically, "RESTful" means an API that follows all REST constraints. In practice, the terms are used interchangeably. Most real-world "REST APIs" violate at least one constraint (usually HATEOAS).
Q: How do I handle versioning when breaking changes are needed?
Add a new version (/v2/) alongside /v1/. Deprecate /v1/ with a Sunset header and give clients at least 6–12 months notice. Never break /v1/ without warning.
Q: Can POST be idempotent?
By convention POST is not idempotent, but you can make it idempotent using an Idempotency-Key header — the server deduplicates requests with the same key. This is common in payment APIs (Stripe, Braintree).
Q: What is the best way to handle file uploads in REST?
Use multipart/form-data for direct uploads. For large files, use a two-step flow: POST /upload-url (server generates a pre-signed S3 URL), then the client uploads directly to S3. This offloads bandwidth from your API server.