All topics
General · Learning hub

REST notes for developers

Master REST with a curated set of 4 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore General notes
REST

REST Principles

REST Principles REST (Representational State Transfer) is an architectural style defined by Roy Fielding in 2000. It is not a standard or protocol - it is a set

REST Principles

REST (Representational State Transfer) is an architectural style defined by Roy Fielding in 2000. It is not a standard or protocol - it is a set of six constraints. APIs that satisfy these constraints are called RESTful.

The 6 Constraints

# 1. Client-Server
# Separation of concerns: client handles UI/UX, server handles data/logic
# They evolve independently - you can rewrite the React frontend
# without touching the API, and vice versa

# 2. Stateless
# Each request must contain ALL information needed to process it
# Server holds NO session state between requests
# Auth must be in every request (Bearer token, not server-side session)
# Benefits: scalability (any server can handle any request),
#           reliability (no session to lose if server restarts)
# Trade-off: larger request payloads, client manages state

# 3. Cacheable
# Responses must label themselves as cacheable or non-cacheable
# Cache-Control: public, max-age=3600
# Cache-Control: no-store
# ETag: "abc123"  (for conditional requests)
# Benefits: performance (reduced latency), scalability (fewer requests)

# 4. Uniform Interface
# The defining REST constraint. Four sub-constraints:
# a) Resource identification in requests (URI identifies the resource)
# b) Resource manipulation through representations (JSON, XML, etc.)
# c) Self-descriptive messages (Content-Type, status codes explain themselves)
# d) HATEOAS - hypermedia as the engine of application state

# 5. Layered System
# Client cannot tell if it's talking directly to the server or a proxy/CDN
# Enables: load balancers, CDNs, API gateways, security layers
# All transparent to the client

# 6. Code on Demand (optional)
# Server can transfer executable code to client (JavaScript)
# The only optional constraint - rarely discussed

Resource-Oriented Design

REST is about resources (nouns), not actions (verbs). URLs identify resources; HTTP methods express what to do with them.

# WRONG: verb-based URLs (RPC-style)
GET  /getUser?id=42
POST /createUser
POST /deleteUser?id=42
GET  /getUserOrders?userId=42

# RIGHT: resource-based URLs (REST-style)
GET    /users/42          # Get user
POST   /users             # Create user
DELETE /users/42          # Delete user
GET    /users/42/orders   # Get orders for user

# Resources are nouns, plural for collections:
# /users          → collection of all users
# /users/42       → specific user with id 42
# /users/42/posts → posts belonging to user 42
# /posts/7        → specific post (can access directly too)

# Representations: the same resource can be represented differently
# Client negotiates via Accept header:
GET /users/42
Accept: application/json
→ {"id": 42, "name": "Jane"}

GET /users/42
Accept: application/xml
→ <user><id>42</id><name>Jane</name></user>

HATEOAS

HATEOAS (Hypermedia as the Engine of Application State) means responses include links to related actions. Clients navigate the API by following links, not by hardcoding URLs. Most real-world REST APIs skip HATEOAS but it is the "true" REST ideal.

// HATEOAS response example
// GET /orders/42
{
  "id": 42,
  "status": "pending",
  "total": 99.99,
  "customer": {
    "id": 7,
    "name": "Jane Doe"
  },
  "_links": {
    "self": { "href": "/orders/42" },
    "customer": { "href": "/users/7" },
    "cancel": { "href": "/orders/42/cancel", "method": "POST" },
    "invoice": { "href": "/orders/42/invoice" },
    "items": { "href": "/orders/42/items" }
  }
}

// Client follows _links.cancel to cancel the order
// Client does not need to know the cancel URL structure
// API can change URLs without breaking clients
REST

HTTP Methods & Status Codes

HTTP Methods & Status Codes Correct use of HTTP methods and status codes is the difference between a good API and a confusing one. Clients, caches, and framewor

HTTP Methods & Status Codes

Correct use of HTTP methods and status codes is the difference between a good API and a confusing one. Clients, caches, and frameworks all rely on these semantics to behave correctly.

Methods: Semantics, Safety, Idempotency

# Safe = does not modify server state (read-only)
# Idempotent = identical repeated requests produce the same result
#              (state is the same whether you call it once or ten times)

# Method     | Safe | Idempotent | Typical response
# GET        |  Yes |    Yes     | 200 OK
# HEAD       |  Yes |    Yes     | 200 OK (no body)
# OPTIONS    |  Yes |    Yes     | 200 OK
# POST       |  No  |    No      | 201 Created
# PUT        |  No  |    Yes     | 200 OK or 204 No Content
# PATCH      |  No  | Depends    | 200 OK or 204 No Content
# DELETE     |  No  |    Yes     | 204 No Content

# GET - retrieve
GET /products?category=shoes&sort=price&limit=20
# Always cacheable, never change state with GET

# POST - create (not idempotent - two identical POSTs = two resources)
POST /orders
# Request body: {"productId": 5, "qty": 2}
# Response: 201 Created + Location: /orders/99

# PUT - full replace (idempotent: same PUT = same result)
PUT /users/42
# Request body: complete user object
# If user 42 does not exist, some APIs create it (upsert)

# PATCH - partial update
PATCH /users/42
# Request body: only fields to update
# {"email": "new@example.com"}  ← only email changes
# Idempotent only if operation is absolute (not incremental like "increment by 1")

# DELETE - remove
DELETE /users/42
# Idempotent: deleting an already-deleted resource = 404 or 204 (both OK)
# Return 204 No Content on success (no body needed)

# Idempotency keys for POST (making POST idempotent)
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
# Server stores the key and returns the same response for retries

Complete Status Code Reference

# 2xx Success
200 OK               # GET/PATCH/PUT returned updated resource in body
201 Created          # POST created resource; include Location header
202 Accepted         # Async operation started (webhook/queue); return job ID
204 No Content       # DELETE/PUT succeeded; no body to return
206 Partial Content  # GET with Range header (video streaming)

# 3xx Redirection
301 Moved Permanently  # Resource URL changed forever; browser caches
302 Found              # Temporary redirect; use for login → dashboard
304 Not Modified       # Conditional GET; use cached copy
307 Temporary Redirect # Temp redirect preserving HTTP method
308 Permanent Redirect # Permanent redirect preserving HTTP method

# 4xx Client Errors (client did something wrong)
400 Bad Request        # Malformed JSON, missing required field, invalid param
401 Unauthorized       # Missing or invalid authentication credentials
                       # Include WWW-Authenticate: Bearer realm="api"
403 Forbidden          # Authenticated but not allowed to do this action
                       # Do NOT expose unauthorized resource existence
404 Not Found          # Resource with this ID does not exist
405 Method Not Allowed # Include Allow: GET, POST header
408 Request Timeout    # Client too slow; connection will be closed
409 Conflict           # State conflict: duplicate email, version mismatch, edit conflict
410 Gone               # Resource permanently deleted (stronger than 404)
412 Precondition Failed  # If-Match, If-Unmodified-Since check failed
415 Unsupported Media Type # Wrong Content-Type (sent XML, expected JSON)
422 Unprocessable Entity  # Valid JSON but fails business logic validation
429 Too Many Requests     # Rate limited; include Retry-After: 60 header

# 5xx Server Errors (server did something wrong)
500 Internal Server Error  # Catch-all for unexpected server errors
501 Not Implemented        # Method not supported by server
502 Bad Gateway            # Upstream server returned bad response
503 Service Unavailable    # Overloaded or maintenance; include Retry-After
504 Gateway Timeout        # Upstream server timed out

Consistent Error Response Format

// Consistent error response structure
// 400 Bad Request
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      { "field": "email", "message": "Invalid email format" },
      { "field": "age", "message": "Must be between 18 and 120" }
    ],
    "requestId": "7f3a9b2c-1234-5678-abcd"
  }
}

// 401 Unauthorized
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication required",
    "requestId": "7f3a9b2c-1234-5678-abcd"
  }
}

// 429 Too Many Requests
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "API rate limit exceeded. Try again in 60 seconds.",
    "retryAfter": 60,
    "limit": 100,
    "remaining": 0,
    "resetAt": "2025-03-15T10:01:00Z"
  }
}
REST

API Design Best Practices

REST API Design Best Practices A well-designed API is intuitive, consistent, and future-proof. These conventions are drawn from Stripe, GitHub, Twilio, and othe

REST API Design Best Practices

A well-designed API is intuitive, consistent, and future-proof. These conventions are drawn from Stripe, GitHub, Twilio, and other industry-standard APIs.

URL Naming & Structure

# Use nouns (resources), not verbs
# Plural nouns for collections
/users              # All users
/users/42           # User 42
/users/42/orders    # Orders for user 42
/orders/99/items    # Items in order 99

# Use kebab-case for multi-word resources
/blog-posts         # ✓
/blog_posts         # ✗
/blogPosts          # ✗

# Actions on resources that don't fit CRUD: use sub-resources or action names
POST /orders/42/cancel     # Cancel order (action sub-resource)
POST /users/42/password-reset  # Trigger password reset
POST /videos/7/publish     # Publish a video

# Filtering, sorting, searching as query params (not new endpoints)
GET /products?category=shoes&minPrice=50&maxPrice=200
GET /users?role=admin&verified=true
GET /orders?status=pending&sort=createdAt&order=desc
GET /products?q=running+shoes        # Full-text search
GET /users?fields=id,name,email      # Sparse fieldsets (GraphQL-like)

# Consistency in naming:
# id fields: "id" not "userId", "orderId" (context is the resource)
# Timestamps: ISO 8601 UTC: "2025-03-15T10:00:00Z"
# Money: integers in smallest unit (cents): 1099 = $10.99
# Booleans: "isActive" not "active", "hasOrders" not "orders"

Versioning Strategies

# Strategy 1: URL path versioning (most common, most visible)
/api/v1/users
/api/v2/users
# Pros: obvious, easy to test in browser, CDN-cacheable per version
# Cons: ugly URLs, forces breaking URL changes
# Used by: Stripe, GitHub, Twitter/X

# Strategy 2: Header versioning
GET /users
API-Version: 2025-03-01
Accept: application/vnd.myapi.v2+json
# Pros: clean URLs, flexible
# Cons: not visible in browser address bar, harder to test
# Used by: Stripe (date-based), GitHub (Accept header)

# Strategy 3: Query parameter versioning (least preferred)
GET /users?version=2
# Pros: simple
# Cons: versioning mixed with resource params, bad practice

# Date-based versioning (Stripe approach - highly recommended)
# Client pins to a version date; API changes don't break existing clients
Stripe-Version: 2023-10-16
# Old clients keep old version behavior; you can deprecate gracefully

# Version lifecycle:
# v1 → current (supported)
# v2 → new version with breaking changes
# After 6-12 months: deprecate v1 (Sunset header)
# After another 6 months: sunset v1
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: </api/v2/users>; rel="successor-version"

Pagination

// Offset-based pagination (simple, but performance degrades at large offsets)
// GET /users?limit=20&offset=40
{
  "data": [...],
  "pagination": {
    "total": 1250,
    "limit": 20,
    "offset": 40,
    "hasMore": true
  }
}
// Problem: inconsistent if items are inserted/deleted between pages

// Cursor-based pagination (preferred for feeds, infinite scroll)
// GET /posts?limit=20&after=cursor_eyJpZCI6MTAwfQ
{
  "data": [...],
  "pagination": {
    "limit": 20,
    "hasNextPage": true,
    "hasPrevPage": true,
    "nextCursor": "cursor_eyJpZCI6MTIwfQ",
    "prevCursor": "cursor_eyJpZCI6MTAxfQ"
  }
}
// Cursor is an opaque base64-encoded value (e.g., the last item's id+timestamp)
// Stable across insertions/deletions; efficient (uses indexed seek, no OFFSET)
// Used by: Facebook Graph API, GitHub, Stripe

// Page-based pagination (simple, good for small datasets)
// GET /products?page=3&perPage=20
{
  "data": [...],
  "meta": {
    "currentPage": 3,
    "totalPages": 63,
    "perPage": 20,
    "totalCount": 1250
  },
  "links": {
    "first": "/products?page=1&perPage=20",
    "prev": "/products?page=2&perPage=20",
    "next": "/products?page=4&perPage=20",
    "last": "/products?page=63&perPage=20"
  }
}

Rate Limiting & Idempotency Keys

# Rate limit response headers (expose limits to clients)
X-RateLimit-Limit: 1000       # Requests allowed per window
X-RateLimit-Remaining: 734    # Requests left in current window
X-RateLimit-Reset: 1709900400 # Unix timestamp when window resets
Retry-After: 60               # Seconds to wait (sent with 429)

# Rate limit strategies:
# Fixed window: 1000 reqs per hour
# Sliding window: 1000 reqs in any 60-minute period (smoother)
# Token bucket: tokens replenish at rate X, burst up to N
# Leaky bucket: queue requests, process at fixed rate

# Different limits per tier:
# Free: 100 req/min
# Pro: 1000 req/min
# Enterprise: custom

# Idempotency Keys (make non-idempotent operations safe to retry)
# Client generates unique key per operation
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{"amount": 9999, "currency": "usd", "customerId": "cus_123"}

# Server behavior:
# - First request: process payment, store result with key
# - Retry with same key: return same stored response (no duplicate charge)
# - Key expires after 24 hours
# - Different key = treat as new operation

# Implementation sketch (Redis-based)
const key = req.headers['idempotency-key'];
if (key) {
  const cached = await redis.get(`idempotency:${userId}:${key}`);
  if (cached) return res.json(JSON.parse(cached));
}
const result = await processPayment(req.body);
if (key) await redis.setex(`idempotency:${userId}:${key}`, 86400, JSON.stringify(result));
res.status(201).json(result);
REST

REST Interview Questions

REST Interview Questions Common REST and API design questions asked in backend and fullstack interviews. Understanding the reasoning behind REST conventions sep

REST Interview Questions

Common REST and API design questions asked in backend and fullstack interviews. Understanding the reasoning behind REST conventions separates strong candidates from those who just memorize definitions.

1. What is REST and what are its key constraints?

REST (Representational State Transfer) is an architectural style for distributed hypermedia systems, defined by Roy Fielding in his 2000 doctoral dissertation. It is defined by six constraints: client-server (separating concerns), stateless (no server-side session; each request contains all needed info), cacheable (responses must declare cacheability), uniform interface (resources identified by URI, manipulated via representations, self-descriptive messages, HATEOAS), layered system (client cannot tell if it's talking to the server directly or a proxy), and code on demand (optional: server can send executable code). An API satisfying all mandatory constraints is RESTful.

2. What is the difference between REST, SOAP, and GraphQL?

SOAP is a rigid XML-based protocol with a strict contract (WSDL). It has built-in standards for security (WS-Security), transactions, and error handling. Used in enterprise and financial systems where formal contracts matter. REST is an architectural style using HTTP and any format (usually JSON). Flexible, human-readable, leverages HTTP caching and semantics. Best for CRUD-style APIs and public APIs. GraphQL is a query language where clients specify exactly what data they need in a single request. Eliminates over-fetching and under-fetching, great for complex client-driven data requirements. Requires more backend work; no native HTTP caching. Best when clients have very diverse data needs (mobile vs web vs analytics).

3. What is the difference between PUT and PATCH?

PUT replaces the entire resource. You send the complete representation, and the server stores it as-is. If you omit a field, it gets cleared. PUT is idempotent: calling it multiple times with the same body produces the same result. PATCH applies a partial update - you only send the fields you want to change, and the server merges them into the existing resource. PATCH is usually idempotent in practice (setting name to "Alice" repeatedly = same result) but not guaranteed by the spec if the operation is relative (e.g., "increment counter by 1"). For a user API: PUT /users/42 needs the full user object; PATCH /users/42 can send just {"name": "Alice"}.

4. What does statelessness mean in REST and why does it matter?

Stateless means the server does not store any session state between requests. Every request must contain everything needed to process it - authentication credentials, context, and parameters. The server has no memory of previous requests from the same client. This matters for scalability: any server instance can handle any request, so load balancers can route freely and you can horizontally scale without sticky sessions. For reliability: if a server crashes, the client just retries with the next server - no lost state. The trade-off is that clients must manage state themselves, and auth tokens must travel with every request (typically as Authorization: Bearer headers).

5. How do you handle authentication in REST APIs?

The most common patterns: (1) JWT (JSON Web Token): client gets a signed token after login, sends it as Authorization: Bearer <token> with every request. Server verifies the signature without a DB lookup - stateless. Use short-lived access tokens (15min) + long-lived refresh tokens. (2) OAuth 2.0: authorization framework for delegated access (logging in with Google, third-party app permissions). Client gets an access token from the authorization server. (3) API Keys: simple string sent in a header (X-API-Key) or query param. Good for server-to-server. Easy to rotate. (4) Session cookies: server stores session in DB/Redis, sends session ID cookie. Simpler for traditional web apps but requires sticky sessions or shared state store.

6. What is idempotency and why is it important for API design?

An operation is idempotent if applying it multiple times produces the same result as applying it once. In HTTP: GET, PUT, DELETE are idempotent; POST is not. Idempotency matters for reliability because networks fail and clients retry. DELETE /users/42 twice should be safe - the second call can return 404 (user gone) or 204 (no change), but must NOT accidentally delete user 43. For non-idempotent operations like payments (POST), use idempotency keys - the client generates a unique UUID per operation and sends it in the Idempotency-Key header. The server stores the result and returns it on retry without re-processing. This is how Stripe prevents double-charges on network retries.

7. What is the difference between 401 and 403?

401 Unauthorized means the request lacks valid authentication credentials - the client is not identified (no token, expired token, invalid signature). The response should include WWW-Authenticate header telling the client how to authenticate. 403 Forbidden means the client IS authenticated but is not authorized to perform this action - they don't have permission. A common mistake: returning 401 for missing login, and 404 instead of 403 when you don't want to reveal the existence of a resource the user can't access. Security tip: for resources that should be hidden entirely from unauthorized users, return 404 instead of 403 to prevent information disclosure about what exists.

8. How do you implement rate limiting in a REST API?

Rate limiting prevents abuse and ensures fair usage. Common algorithms: Fixed window (count requests per minute, reset at window boundary - susceptible to burst at boundary), sliding window log (track timestamps of requests, count in last N seconds - memory intensive), token bucket (refill tokens at rate R, allow burst up to capacity B - smooth), leaky bucket (queue requests, process at fixed rate - uniform output). Implementation: use Redis with INCR + EXPIRE for fixed window, or Redis sorted sets for sliding window. Return 429 Too Many Requests with Retry-After header. Different limits per user tier. Distinguish IP-based limits (unauthenticated) from API key/user limits (authenticated). Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

9. How do you version a REST API without breaking existing clients?

First, minimize breaking changes: add new optional fields (additive changes are safe), never remove or rename fields without a version bump, never change field types. When you must break: use URL versioning (/api/v2/) or header versioning (API-Version: 2025-03-01). Announce deprecation early with Sunset and Deprecation headers. Support old versions for 6-12 months with clear migration guides. Stripe's approach is a best practice: date-based versions where each client pins to the date they integrated. New clients get the latest behavior; old clients keep old behavior. Changes are documented by date.

10. When would you choose cursor-based over offset pagination?

Offset pagination (LIMIT N OFFSET M) is simple to implement and allows jumping to any page, but has two problems: inconsistency (if items are inserted/deleted between requests, you see duplicates or skip items) and performance (large OFFSETs require the DB to scan and discard M rows). Cursor-based pagination uses an opaque cursor (usually the last item's id or a base64-encoded timestamp+id) as the starting point for the next page. It is stable under insertion/deletion (always starts from a known item) and efficient (uses indexed WHERE id > cursor). The trade-off: cannot jump to page 5 directly; only next/previous navigation is possible. Use cursor for: real-time feeds, infinite scroll, large datasets. Use offset for: admin tables with page numbers, small datasets.

Keep your REST knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever