skip to content
luminary.blog
by Oz Akan
A minimalist black-and-white illustration of a small child sitting cross-legged on a cushion next to a friendly, round dinosaur-like creature. Both appear calm with closed or relaxed eyes. A potted plant sits on the left, and a window behind them shows a simple cloud in the sky. The scene has a peaceful, cozy, and meditative atmosphere.

REST API Development Refresher

A comprehensive refresher on REST API development covering principles, URL design, authentication, pagination, versioning, caching, security, and common interview questions.

/ 16 min read

Table of Contents

What is REST?

REST (Representational State Transfer) is an architectural style for designing networked applications. It’s not a protocol or standard, but a set of constraints that define how web services should behave. Wen followed, REST gives you APIs that are predictable, scalable, and easy to reason about. Most of the APIs you’ve built probably follow REST to some degree. This is a reminder of what the constraints actually are and why they matter.

Key Principles of REST

  1. Client-Server Architecture: Clear separation between client and server
  2. Stateless: Each request contains all information needed to process it
  3. Cacheable: Responses should be cacheable when appropriate
  4. Uniform Interface: Consistent way to interact with resources
  5. Layered System: Architecture can have multiple layers
  6. Code on Demand (optional): Server can send executable code to client

Core REST Concepts

Resources

Resources are the fundamental abstraction in REST. Everything your API exposes — users, orders, products — is a resource, identified by a URI. The key discipline: URIs should be nouns describing what the resource is, not verbs describing what you do with it. Let HTTP methods handle the verbs.

  • Everything is a resource: Users, products, orders, etc.
  • Identified by URIs: /users/123, /products/456
  • Nouns, not verbs: Use /users not /getUsers

HTTP Methods (Verbs)

The methods are the verbs of REST. The two things people forget: PUT is idempotent (calling it twice produces the same result), and PATCH is not. PUT replaces the entire resource — omit a field and it’s gone. PATCH updates only what you send.

MethodPurposeIdempotentSafe
GETRetrieve data
POSTCreate new resource
PUTUpdate/replace entire resource
PATCHPartial update
DELETERemove resource
HEADGet headers only
OPTIONSGet allowed methods

HTTP Status Codes

Use the right codes consistently. The most common mistake in the wild: returning 200 with an error message in the body. Don’t do that — let the status code communicate the outcome.

2xx Success

  • 200 OK: Request successful
  • 201 Created: Resource created successfully
  • 202 Accepted: Request accepted for processing
  • 204 No Content: Success but no content to return

3xx Redirection

  • 301 Moved Permanently: Resource has new permanent URI
  • 302 Found: Temporary redirect
  • 304 Not Modified: Resource hasn’t changed (caching)

4xx Client Errors

  • 400 Bad Request: Invalid request syntax
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Server understood but refuses
  • 404 Not Found: Resource doesn’t exist
  • 405 Method Not Allowed: HTTP method not supported
  • 409 Conflict: Request conflicts with current state
  • 422 Unprocessable Entity: Valid syntax but semantic errors

5xx Server Errors

  • 500 Internal Server Error: Generic server error
  • 501 Not Implemented: Server doesn’t support functionality
  • 502 Bad Gateway: Invalid response from upstream
  • 503 Service Unavailable: Server temporarily unavailable

URL Design Best Practices

Good URL design makes your API self-documenting. Someone should be able to guess what an endpoint does just by reading the path. Plural nouns, shallow nesting, query parameters for filtering — these conventions exist because they work.

Resource Naming

✓ Good:
/users
/users/123
/users/123/orders
/users/123/orders/456
✗ Bad:
/getUsers
/user_info
/userOrders
/users/orders/456/users/123

Guidelines

  • Use nouns for resources, not verbs
  • Use plural nouns (/users not /user)
  • Use lowercase and hyphens for readability
  • Keep URLs shallow (avoid deep nesting)
  • Use query parameters for filtering: /users?role=admin&active=true

Request/Response Design

Consistency in your request and response shapes matters more than which specific format you choose. Pick a structure, document it, and stick with it across every endpoint. Wrap responses in an envelope with data and meta so clients always know what to expect.

Request Headers

Content-Type: application/json
Accept: application/json
Authorization: Bearer <token>
User-Agent: MyApp/1.0

Response Structure

{
"data": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"version": "1.0"
}
}

Error Response Structure

Errors should be just as structured as success responses. A machine-readable code, a human-readable message, and field-level details when validation fails. Your frontend developers will thank you.

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
}
]
}
}

Authentication & Authorization

Authentication is who you are. Authorization is what you’re allowed to do. They’re separate concerns and should be implemented as such. Most APIs need both — don’t conflate them.

Authentication Methods

  1. API Keys: Simple but less secure
  2. JWT (JSON Web Tokens): Stateless, self-contained
  3. OAuth 2.0: Industry standard for authorization
  4. Basic Auth: Username/password (use with HTTPS)

Authorization Patterns

  • Role-Based Access Control (RBAC): Users have roles with permissions
  • Attribute-Based Access Control (ABAC): Fine-grained based on attributes
  • Resource-Based: Permissions tied to specific resources

Pagination

Never return unbounded collections. Pagination isn’t optional — it’s how you keep your API responsive and your database alive. Two approaches, each with trade-offs.

Offset-based is simple but can skip or duplicate rows when data changes between pages. Cursor-based is more reliable for large, changing datasets because it anchors to a specific position rather than a row number.

Offset-Based Pagination

GET /users?offset=20&limit=10

Response:

{
"data": [...],
"pagination": {
"offset": 20,
"limit": 10,
"total": 150,
"has_more": true
}
}

Cursor-Based Pagination

GET /users?cursor=eyJpZCI6MTIzfQ&limit=10

Response:

{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTMzfQ",
"has_more": true
}
}

Filtering, Sorting & Searching

Keep filtering in query parameters. Whatever convention you pick — simple key-value or bracket notation for ranges — be consistent across every endpoint. Inconsistent filtering conventions across an API are a reliability smell.

Query Parameters

GET /users?role=admin&active=true&sort=created_at&order=desc&search=john

Advanced Filtering

GET /products?price[gte]=10&price[lte]=100&category[in]=electronics,books

Versioning Strategies

You will need to version your API. The question is how. URI versioning wins for most teams — it’s the most visible, easiest to test, and cache-friendly. Header versioning is more RESTful in theory, but the developer experience trade-off rarely justifies it.

1. URI Versioning

GET /v1/users
GET /v2/users

2. Header Versioning

Custom header

GET /api/users
X-API-Version: 1

Accept header

GET /users
Accept: application/vnd.api+json;version=1

3. Parameter Versioning

GET /users?version=1
StrategyVisibilityRESTfulCaching FriendlyEase of UseCommon Use
URL PathHighEasyVery
Query ParamMediumEasyRare
HeaderLowComplexCommon in APIs needing flexibility
Accept HeaderLowComplexAdvanced APIs

Caching

Caching is where most of your performance wins live. Three headers to know: Cache-Control for duration and behavior, ETag for conditional requests, Last-Modified for timestamp-based validation. Layer them across browser, CDN, server, and database for maximum effect.

Cache Headers

Cache-Control: max-age=3600, must-revalidate
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 15 Jan 2025 10:30:00 GMT

Cache Strategies

  • Browser Caching: Client-side caching
  • CDN Caching: Geographic distribution
  • Server Caching: Redis, Memcached
  • Database Caching: Query result caching

Rate Limiting

Rate limiting protects your API from abuse and keeps your infrastructure stable under load. Always return rate limit headers so clients can self-throttle gracefully instead of slamming into a wall.

Common Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642248600
Retry-After: 3600

Implementation Strategies

  • Fixed Window: Reset counter at fixed intervals
  • Sliding Window: More granular control
  • Token Bucket: Burst capacity with refill rate
  • Leaky Bucket: Smooth rate limiting

Security Best Practices

Security isn’t a feature you bolt on — it’s a baseline. HTTPS everywhere, input validation on every endpoint, rate limiting, proper CORS, and security headers. Validate with a whitelist approach: only allow expected values rather than trying to block bad ones.

Input Validation

  • Sanitize inputs: Prevent injection attacks
  • Validate data types: Ensure correct formats
  • Check boundaries: Limit string lengths, number ranges
  • Whitelist approach: Only allow expected values

Security Headers

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'

HTTPS & CORS

  • Always use HTTPS in production
  • Configure CORS properly for cross-origin requests
  • Implement HSTS (HTTP Strict Transport Security)

Testing REST APIs

Five layers of testing, each catching different classes of bugs. Unit tests for logic, integration tests for endpoints, contract tests for spec compliance, performance tests for load handling, security tests for vulnerabilities. You need all of them — they’re not interchangeable.

Testing Types

  1. Unit Tests: Individual components
  2. Integration Tests: API endpoints
  3. Contract Tests: API specification compliance
  4. Performance Tests: Load and stress testing
  5. Security Tests: Vulnerability scanning

Testing Tools

  • Postman: API testing and documentation
  • curl: Command-line HTTP client
  • Jest/Mocha: JavaScript testing frameworks
  • JMeter: Performance testing
  • Newman: Automated Postman collection runner

Documentation

An undocumented API is an unusable API. OpenAPI/Swagger is the industry standard for a reason — it’s machine-readable, generates interactive docs, and can drive code generation. At minimum, document every endpoint, show request/response examples, explain auth, and list all error codes.

API Documentation Standards

  • OpenAPI (Swagger): Industry standard specification
  • Postman Collections: Interactive documentation
  • API Blueprint: Markdown-based documentation

What to Document

  • Endpoints: All available routes
  • Request/Response examples: Clear usage examples
  • Authentication: How to authenticate
  • Error codes: All possible error responses
  • Rate limits: Usage restrictions
  • Changelog: Version history

Performance Optimization

Optimize at two levels: the database and the API layer. On the database side, indexes and connection pooling give you the biggest returns. On the API side, compression and pagination are table stakes — field selection and batch operations are where you get the next level of efficiency.

Database Optimization

  • Indexing: Speed up queries
  • Connection pooling: Reuse connections
  • Query optimization: Efficient database queries
  • Caching: Reduce database load

API Optimization

  • Compression: Gzip responses
  • Pagination: Limit response size
  • Field selection: Only return needed fields
  • Batch operations: Reduce API calls

Monitoring & Logging

If you can’t trace a request from ingress to database and back, your observability has gaps. Track response time, error rate, request volume, and availability. Use structured logging with correlation IDs so you can follow a single request across services.

Key Metrics

  • Response time: API performance
  • Error rate: System health
  • Request volume: Traffic patterns
  • Availability: Uptime monitoring

Logging Best Practices

  • Structured logging: JSON format
  • Log levels: ERROR, WARN, INFO, DEBUG
  • Correlation IDs: Track requests across services
  • Security: Don’t log sensitive data

Common Design Patterns

These patterns separate concerns and keep your codebase maintainable as it grows. None of them are REST-specific, but they show up in every well-structured API codebase.

Repository Pattern

Abstraction layer between business logic and data access

Service Layer

Business logic separated from controllers

DTO (Data Transfer Objects)

Objects for transferring data between layers

Factory Pattern

Create objects without specifying exact classes

Quick Reference

CRUD Operations

POST /users → Create user
GET /users → Get all users
GET /users/123 → Get specific user
PUT /users/123 → Update entire user
PATCH /users/123 → Partially update user
DELETE /users/123 → Delete user

Common Response Codes

  • 200: Success
  • 201: Created
  • 400: Bad request
  • 401: Unauthorized
  • 403: Forbidden
  • 404: Not found
  • 500: Server error

This refresher should give you a solid foundation for discussing REST API development in your interview. Focus on understanding the principles rather than memorizing everything, and be ready to discuss real-world examples from your experience.

Interview Questions and Answers

Q1: What is the difference between PUT and PATCH?

A1: PUT replaces the entire resource — every field must be included or it’s removed. PATCH applies a partial update, modifying only the fields you send. PUT is idempotent (calling it twice yields the same result), while PATCH is not guaranteed to be idempotent because the outcome can depend on the current state of the resource.

Q2: What does it mean for an HTTP method to be idempotent?

A2: An idempotent method produces the same result whether you call it once or multiple times. GET, PUT, DELETE, HEAD, and OPTIONS are idempotent. POST is not — two identical POST requests create two resources. This matters for retry logic: you can safely retry idempotent methods on network failure without side effects.

Q3: What is the difference between authentication and authorization?

A3: Authentication verifies identity — who you are. Authorization determines access — what you’re allowed to do. A JWT token authenticates the user; the role or permissions encoded in that token (or looked up server-side) authorize what endpoints or resources they can access. They’re separate concerns and should be implemented independently.

Q4: When would you use a 401 versus a 403 status code?

A4: 401 Unauthorized means the request lacks valid authentication credentials — the user hasn’t logged in or the token is expired. 403 Forbidden means the user is authenticated but doesn’t have permission to access the resource. The distinction matters: 401 tells the client to authenticate, 403 tells the client that authenticating won’t help — they simply don’t have access.

Q5: How would you design pagination for a REST API?

A5: Two main approaches. Offset-based (?offset=20&limit=10) is simple and supports jumping to arbitrary pages, but it’s unreliable when data changes between requests — rows can be skipped or duplicated. Cursor-based (?cursor=abc&limit=10) anchors to a specific record and is more reliable for large, frequently changing datasets. Always include has_more in the response so clients know when to stop. For most consumer-facing APIs, cursor-based is the better default.

Q6: What is HATEOAS and do you need it?

A6: HATEOAS (Hypermedia As The Engine Of Application State) means the API response includes links to related actions and resources, so the client can navigate the API dynamically without hardcoding URLs. In theory, it makes APIs self-discoverable. In practice, most REST APIs skip it because clients are built with known endpoints. It’s part of the Richardson Maturity Model (Level 3) but rarely implemented outside of enterprise APIs.

Q7: How do you handle versioning in a REST API?

A7: Three strategies: URI path versioning (/v1/users) is the most common — visible, cache-friendly, and easy to test. Header versioning (X-API-Version: 1) is more RESTful but harder for developers to use casually. Query parameter versioning (?version=1) is rare and breaks caching. URI path wins for most teams. Regardless of strategy, maintain backward compatibility within a version and deprecate old versions with clear timelines.

Q8: What is the difference between a 200 and a 201 status code?

A8: 200 OK indicates a successful request with a response body. 201 Created specifically indicates that a new resource was successfully created, typically returned after a POST. A 201 response should include a Location header pointing to the newly created resource’s URI. Using the right code makes your API self-documenting — clients can distinguish between “operation succeeded” and “something new was created.”

Q9: How do you handle errors consistently across a REST API?

A9: Define a standard error response structure and use it everywhere. Include a machine-readable error code, a human-readable message, and field-level details for validation errors. Use appropriate HTTP status codes — don’t return 200 with an error in the body. Distinguish between client errors (4xx) and server errors (5xx). Document every possible error code so consumers know what to expect.

Q10: What is the difference between stateless and stateful APIs?

A10: A stateless API requires every request to carry all the information the server needs to process it — there’s no server-side session state between requests. This is a core REST constraint. It improves scalability because any server can handle any request without shared session storage. Stateful APIs maintain session state on the server between requests, which creates coupling and complicates horizontal scaling. JWTs are a common way to keep APIs stateless while still carrying user context.

Q11: When would you return a 204 No Content?

A11: 204 indicates the request succeeded but there’s nothing to send back. The most common use is after a DELETE — the resource is gone, there’s nothing to return. It’s also appropriate for PUT or PATCH when you don’t need to return the updated resource. The key: 204 must not include a response body. If the client needs confirmation of what changed, return 200 with the updated resource instead.

Q12: How do you secure a REST API?

A12: Start with the non-negotiables: HTTPS everywhere, input validation on every endpoint, authentication (JWT or OAuth 2.0), and authorization checks on every protected resource. Add rate limiting to prevent abuse, security headers (CSP, X-Frame-Options, X-Content-Type-Options), and CORS configuration. Validate with a whitelist approach. Never log sensitive data. Regularly audit dependencies for vulnerabilities.

Q13: What is the difference between OAuth 2.0 and JWT?

A13: They solve different problems and are often used together. OAuth 2.0 is an authorization framework — it defines how a client obtains access to a resource on behalf of a user through flows like authorization code, client credentials, or implicit grant. JWT is a token format — a self-contained, signed JSON payload that carries claims like user ID and roles. In practice, OAuth 2.0 issues a JWT as the access token. OAuth is the protocol; JWT is the envelope.

Q14: How would you implement rate limiting?

A14: Four common algorithms. Fixed window resets a counter at regular intervals — simple but allows burst traffic at window boundaries. Sliding window smooths this by tracking requests over a rolling period. Token bucket allows bursts up to a capacity, refilling tokens at a fixed rate — best balance of simplicity and fairness. Leaky bucket processes requests at a constant rate, queuing excess. Return X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers so clients can self-throttle. Return 429 Too Many Requests when the limit is hit.

Q15: What is content negotiation in REST?

A15: Content negotiation lets the client and server agree on the response format. The client sends an Accept header specifying what it can handle (e.g., application/json, application/xml), and the server responds in that format or returns 406 Not Acceptable. The Content-Type header indicates the format of the request body. Most modern APIs default to JSON and don’t support multiple formats, but the mechanism exists for APIs that need to serve different clients.

Q16: How do you handle long-running operations in a REST API?

A16: Don’t block the request. Return 202 Accepted immediately with a URL the client can poll for status. The status endpoint returns the operation’s progress and, once complete, the result or a link to it. For more sophisticated use cases, use webhooks — the client provides a callback URL and the server notifies it when the operation finishes. This keeps the API responsive and avoids timeout issues.

Q17: What is the difference between offset-based and cursor-based pagination?

A17: Offset-based pagination uses a numeric offset and limit (?offset=20&limit=10). It’s intuitive and supports random page access, but it breaks when rows are inserted or deleted between requests — items get skipped or duplicated. Cursor-based pagination uses an opaque token pointing to a specific record (?cursor=abc&limit=10). It’s stable under concurrent writes and more performant on large datasets because the database can seek directly to the cursor position rather than counting rows to skip.

Q18: What is CORS and why does it matter?

A18: CORS (Cross-Origin Resource Sharing) is a browser security mechanism that restricts web pages from making requests to a different domain than the one serving the page. When your frontend at app.example.com calls your API at api.example.com, the browser sends a preflight OPTIONS request to check if the API allows cross-origin access. The server responds with Access-Control-Allow-Origin and related headers. Misconfigured CORS either blocks legitimate requests or opens your API to unauthorized cross-origin access.

Q19: How would you design a REST API for a many-to-many relationship?

A19: Model the relationship as its own resource. For example, users and roles: GET /users/123/roles lists a user’s roles, PUT /users/123/roles/456 assigns a role, DELETE /users/123/roles/456 removes it. For richer relationships with their own attributes (like a membership with a join date), create an explicit resource: POST /memberships with user_id and group_id in the body. Avoid deeply nested URLs — if the relationship resource is queried independently, give it a top-level endpoint.

Q20: What is idempotency and how do you implement it for non-idempotent methods like POST?

A20: Idempotency means repeated identical requests produce the same outcome. GET, PUT, and DELETE are naturally idempotent. POST is not — two identical POSTs typically create two resources. To make POST idempotent, use an idempotency key: the client sends a unique key in a header (e.g., Idempotency-Key: abc-123), and the server stores the result of the first request. If the same key is sent again, the server returns the stored result without re-executing the operation. This is essential for payment APIs and any operation where duplicates cause real harm.