What is REST API? Complete Guide for Developers

APIs power the modern web. Every time you check the weather on your phone, scroll through social media, or make an online payment, you're using APIs. Among all API architectures, REST has become the de facto standard for web services.
In this comprehensive guide, you'll learn everything you need to know about REST APIs—from fundamental concepts to advanced patterns used in production systems.
What You'll Learn
✅ What APIs are and why they matter
✅ REST architectural principles and constraints
✅ HTTP methods and when to use each one
✅ Status codes and their meanings
✅ URL design and resource naming conventions
✅ Authentication and authorization methods
✅ Versioning, pagination, and filtering strategies
✅ Best practices for building robust REST APIs
Prerequisites
Before diving in, you should have:
- Basic understanding of HTTP (see our HTTP Protocol Complete Guide)
- Familiarity with client-server architecture
- Basic programming knowledge in any language
Part 1: Understanding APIs
What is an API?
API stands for Application Programming Interface. It's a contract that defines how two software applications communicate with each other.
Think of an API like a restaurant menu:
- You (client): Want to order food
- Menu (API): Shows what's available and how to order
- Kitchen (server): Prepares and delivers what you ordered
- Waiter (API request/response): Carries your order and brings back food
┌─────────────┐ ┌─────────────┐
│ Client │ ──── Request ────▶ │ Server │
│ (Your App) │ │ (Backend) │
│ │ ◀─── Response ──── │ │
└─────────────┘ └─────────────┘Why Do We Need APIs?
APIs solve several critical problems:
1. Separation of Concerns
- Frontend and backend can evolve independently
- Mobile apps, web apps, and third-party services all use the same backend
2. Interoperability
- Different technologies can communicate (Java backend → JavaScript frontend)
- Services from different companies can integrate
3. Reusability
- Build once, consume everywhere
- The same API serves mobile apps, web apps, IoT devices, and partners
4. Security
- Control exactly what data and operations are exposed
- Implement authentication and authorization at the API layer
Types of APIs
Before focusing on REST, let's see the API landscape:
| Type | Format | Use Case | Example |
|---|---|---|---|
| REST | JSON/XML over HTTP | Web services, mobile backends | Twitter API, GitHub API |
| GraphQL | JSON over HTTP | Flexible queries, mobile apps | Facebook, Shopify |
| gRPC | Protocol Buffers | Microservices, real-time | Google, Netflix |
| SOAP | XML | Enterprise, legacy systems | Banking, healthcare |
| WebSocket | Binary/Text | Real-time bidirectional | Chat apps, gaming |
REST dominates web APIs because of its simplicity and ubiquity.
Part 2: REST Fundamentals
What is REST?
REST stands for Representational State Transfer. It was introduced by Roy Fielding in his 2000 doctoral dissertation.
REST is not a protocol or standard—it's an architectural style with a set of constraints that, when followed, creates scalable and maintainable web services.
The Six REST Constraints
For an API to be truly "RESTful," it must follow these constraints:
1. Client-Server
The client and server are separate concerns:
- Client: Handles user interface and user experience
- Server: Handles data storage, business logic, and security
This separation allows each to evolve independently.
┌─────────────┐ ┌─────────────┐
│ Client │◀───────▶│ Server │
│ (React UI) │ HTTP │ (Node.js) │
└─────────────┘ └─────────────┘
▲ ▲
│ │
UI Logic Business Logic
Presentation Data Storage2. Stateless
Each request must contain all information needed to process it. The server doesn't store client state between requests.
// ❌ Stateful (server remembers user)
GET /api/cart // Server: "Which user? I need to check session..."
// ✅ Stateless (request contains everything)
GET /api/users/123/cart
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Benefits of statelessness:
- Easier to scale (any server can handle any request)
- Simpler to debug (each request is self-contained)
- Better reliability (server restarts don't affect clients)
3. Cacheable
Responses must indicate whether they can be cached:
HTTP/1.1 200 OK
Cache-Control: max-age=3600, public
ETag: "abc123"
{
"products": [...]
}Caching improves performance and reduces server load.
4. Uniform Interface
This is the most important constraint. REST APIs must have a consistent, predictable interface:
- Resource identification: Use URIs to identify resources
- Resource manipulation: Use HTTP methods (GET, POST, PUT, DELETE)
- Self-descriptive messages: Include metadata in headers
- HATEOAS: Include links to related resources (more on this later)
5. Layered System
Clients can't tell whether they're connected directly to the server or through intermediaries (load balancers, caches, proxies):
Client → CDN → Load Balancer → API Gateway → ServerThis enables:
- Load balancing for scalability
- Caching at multiple levels
- Security (firewalls, WAF)
6. Code on Demand (Optional)
Servers can optionally send executable code to clients (JavaScript). This is the only optional constraint and rarely used in practice.
Resources: The Heart of REST
In REST, everything is a resource. A resource is any information that can be named:
- A user
- A blog post
- An order
- A collection of products
Each resource has:
- A unique identifier (URI)
- One or more representations (JSON, XML, HTML)
Resource: User #123
URI: /api/users/123
Representation (JSON):
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}Part 3: HTTP Methods in Depth
HTTP methods (also called "verbs") define what action to perform on a resource.
The CRUD Mapping
| HTTP Method | CRUD Operation | SQL Equivalent |
|---|---|---|
| POST | Create | INSERT |
| GET | Read | SELECT |
| PUT | Update (full) | UPDATE |
| PATCH | Update (partial) | UPDATE |
| DELETE | Delete | DELETE |
GET - Retrieve Resources
Purpose: Fetch a resource or collection of resources.
Characteristics:
- Safe (doesn't modify data)
- Idempotent (same request = same result)
- Cacheable
# Get a single user
GET /api/users/123 HTTP/1.1
Host: api.example.com
Accept: application/json
# Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"createdAt": "2024-01-15T10:30:00Z"
}# Get a collection with filtering and pagination
GET /api/users?role=admin&page=1&limit=10 HTTP/1.1
# Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "admin"}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 2,
"totalPages": 1
}
}POST - Create Resources
Purpose: Create a new resource.
Characteristics:
- Not safe (modifies data)
- Not idempotent (each call creates a new resource)
- Not cacheable
# Create a new user
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "securepassword123"
}
# Response
HTTP/1.1 201 Created
Location: /api/users/124
Content-Type: application/json
{
"id": 124,
"name": "Jane Doe",
"email": "jane@example.com",
"createdAt": "2024-01-20T14:22:00Z"
}Note: The Location header tells the client where to find the new resource.
PUT - Replace Resources
Purpose: Replace an entire resource with a new representation.
Characteristics:
- Not safe (modifies data)
- Idempotent (same request produces same result)
- Requires the complete resource
# Replace user 123 entirely
PUT /api/users/123 HTTP/1.1
Content-Type: application/json
{
"name": "John Smith",
"email": "john.smith@example.com",
"role": "admin"
}
# Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "John Smith",
"email": "john.smith@example.com",
"role": "admin",
"updatedAt": "2024-01-20T15:00:00Z"
}Important: PUT replaces the entire resource. Any fields not included in the request body may be set to null or default values.
PATCH - Partial Update
Purpose: Apply partial modifications to a resource.
Characteristics:
- Not safe (modifies data)
- Can be idempotent (depends on implementation)
- Only updates specified fields
# Update only the email
PATCH /api/users/123 HTTP/1.1
Content-Type: application/json
{
"email": "newemail@example.com"
}
# Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "John Smith",
"email": "newemail@example.com",
"role": "admin",
"updatedAt": "2024-01-20T15:30:00Z"
}PUT vs PATCH
| Aspect | PUT | PATCH |
|---|---|---|
| Payload | Complete resource | Partial updates only |
| Missing fields | Set to null/default | Unchanged |
| Idempotent | Yes | Usually yes |
| Use case | Full replacement | Targeted updates |
// PUT: Must include ALL fields
PUT /api/users/123
{
"name": "John",
"email": "john@example.com",
"role": "user",
"phone": null,
"address": null
}
// PATCH: Only include what changes
PATCH /api/users/123
{
"role": "admin"
}DELETE - Remove Resources
Purpose: Remove a resource.
Characteristics:
- Not safe (modifies data)
- Idempotent (deleting twice has same effect as once)
# Delete user 123
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# Response options:
# Option 1: 204 No Content (preferred)
HTTP/1.1 204 No Content
# Option 2: 200 OK with deleted resource
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "John Smith",
"deleted": true,
"deletedAt": "2024-01-20T16:00:00Z"
}HEAD - Check Resource Existence
Purpose: Same as GET but returns only headers (no body).
# Check if resource exists
HEAD /api/users/123 HTTP/1.1
# Response
HTTP/1.1 200 OK
Content-Length: 256
Last-Modified: Sat, 20 Jan 2024 15:30:00 GMT
ETag: "abc123"Use cases:
- Check if resource exists before downloading
- Get metadata without transferring body
- Validate cache
OPTIONS - Discover Capabilities
Purpose: Describe the communication options for a resource.
# What methods are allowed?
OPTIONS /api/users HTTP/1.1
# Response
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Access-Control-Allow-Methods: GET, POST, PUT, DELETECommonly used in CORS preflight requests.
Method Safety and Idempotency Summary
| Method | Safe | Idempotent | Request Body | Response Body |
|---|---|---|---|---|
| GET | ✅ | ✅ | ❌ | ✅ |
| HEAD | ✅ | ✅ | ❌ | ❌ |
| OPTIONS | ✅ | ✅ | ❌ | ✅ |
| POST | ❌ | ❌ | ✅ | ✅ |
| PUT | ❌ | ✅ | ✅ | ✅ |
| PATCH | ❌ | Depends | ✅ | ✅ |
| DELETE | ❌ | ✅ | ❌ | Optional |
- Safe: Doesn't modify server state
- Idempotent: Multiple identical requests have the same effect as one
Part 4: HTTP Status Codes
Status codes tell clients what happened with their request. They're grouped by the first digit:
1xx - Informational
Rarely used in REST APIs.
| Code | Name | Use Case |
|---|---|---|
| 100 | Continue | Large upload in progress |
| 101 | Switching Protocols | Upgrade to WebSocket |
2xx - Success
The request succeeded.
| Code | Name | When to Use |
|---|---|---|
| 200 | OK | GET, PUT, PATCH succeeded |
| 201 | Created | POST created new resource |
| 202 | Accepted | Async operation started |
| 204 | No Content | DELETE succeeded, no body |
# 200 OK - Resource retrieved
GET /api/users/123
→ 200 OK with user data
# 201 Created - New resource created
POST /api/users
→ 201 Created with new user + Location header
# 202 Accepted - Async job started
POST /api/reports/generate
→ 202 Accepted with job ID
# 204 No Content - Deleted successfully
DELETE /api/users/123
→ 204 No Content3xx - Redirection
The client needs to take additional action.
| Code | Name | When to Use |
|---|---|---|
| 301 | Moved Permanently | Resource URL changed forever |
| 302 | Found | Temporary redirect |
| 304 | Not Modified | Cache is still valid |
| 307 | Temporary Redirect | Temporary redirect (preserve method) |
| 308 | Permanent Redirect | Permanent redirect (preserve method) |
# 301 Moved Permanently
GET /api/v1/users
→ 301 Moved Permanently
Location: /api/v2/users
# 304 Not Modified (caching)
GET /api/users/123
If-None-Match: "abc123"
→ 304 Not Modified4xx - Client Errors
The client made an error.
| Code | Name | When to Use |
|---|---|---|
| 400 | Bad Request | Invalid request syntax/body |
| 401 | Unauthorized | Authentication required |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 405 | Method Not Allowed | HTTP method not supported |
| 409 | Conflict | Resource state conflict |
| 422 | Unprocessable Entity | Validation failed |
| 429 | Too Many Requests | Rate limit exceeded |
# 400 Bad Request - Malformed JSON
POST /api/users
Content-Type: application/json
{ "name": "John", } ← trailing comma
→ 400 Bad Request
{
"error": "Bad Request",
"message": "Invalid JSON in request body"
}
# 401 Unauthorized - Missing or invalid token
GET /api/users/123
Authorization: Bearer invalid_token
→ 401 Unauthorized
{
"error": "Unauthorized",
"message": "Invalid or expired token"
}
# 403 Forbidden - User can't access resource
DELETE /api/users/1 ← Admin trying to delete super admin
→ 403 Forbidden
{
"error": "Forbidden",
"message": "Cannot delete super admin users"
}
# 404 Not Found - Resource doesn't exist
GET /api/users/99999
→ 404 Not Found
{
"error": "Not Found",
"message": "User with id 99999 not found"
}
# 409 Conflict - State conflict
POST /api/users
{ "email": "existing@example.com" }
→ 409 Conflict
{
"error": "Conflict",
"message": "User with this email already exists"
}
# 422 Unprocessable Entity - Validation failed
POST /api/users
{ "email": "not-an-email", "age": -5 }
→ 422 Unprocessable Entity
{
"error": "Validation Error",
"details": [
{"field": "email", "message": "Invalid email format"},
{"field": "age", "message": "Age must be positive"}
]
}
# 429 Too Many Requests - Rate limited
GET /api/users
→ 429 Too Many Requests
Retry-After: 60
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Try again in 60 seconds"
}5xx - Server Errors
Something went wrong on the server.
| Code | Name | When to Use |
|---|---|---|
| 500 | Internal Server Error | Unexpected server error |
| 502 | Bad Gateway | Upstream server error |
| 503 | Service Unavailable | Server overloaded/maintenance |
| 504 | Gateway Timeout | Upstream server timeout |
# 500 Internal Server Error - Catch-all for bugs
GET /api/users
→ 500 Internal Server Error
{
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"requestId": "req_abc123" ← for debugging
}
# 503 Service Unavailable - Planned maintenance
GET /api/users
→ 503 Service Unavailable
Retry-After: 3600
{
"error": "Service Unavailable",
"message": "Scheduled maintenance in progress"
}Status Code Decision Tree
Request received
├── Is the request valid?
│ ├── No → 400 Bad Request
│ └── Yes ↓
├── Is authentication required?
│ ├── Yes, and missing/invalid → 401 Unauthorized
│ └── Has valid auth ↓
├── Is user authorized for this action?
│ ├── No → 403 Forbidden
│ └── Yes ↓
├── Does the resource exist?
│ ├── No → 404 Not Found
│ └── Yes ↓
├── Is the method allowed?
│ ├── No → 405 Method Not Allowed
│ └── Yes ↓
├── Is there a conflict?
│ ├── Yes → 409 Conflict
│ └── No ↓
├── Is the data valid?
│ ├── No → 422 Unprocessable Entity
│ └── Yes ↓
├── Did the server crash?
│ ├── Yes → 500 Internal Server Error
│ └── No ↓
└── Success! → 2xx (200, 201, 204)Part 5: URL Design and Resource Naming
Good URL design makes your API intuitive and self-documenting.
URL Structure
https://api.example.com/v1/users/123/orders?status=pending&limit=10
└─┬─┘ └─────┬─────┘ └┬┘ └────┬─────────┘ └────────┬───────────┘
scheme host version path (resource) query paramsResource Naming Conventions
1. Use Nouns, Not Verbs
Resources are things, not actions.
✅ Good (nouns)
GET /users
POST /orders
DELETE /products/123
❌ Bad (verbs)
GET /getUsers
POST /createOrder
DELETE /deleteProduct/1232. Use Plural Nouns
Consistency is key—always use plurals.
✅ Good (plural)
/users
/users/123
/products
/products/456/reviews
❌ Bad (inconsistent)
/user ← singular
/users/123 ← plural
/product/4563. Use Hyphens for Multi-Word Resources
✅ Good
/user-profiles
/order-items
/blog-posts
❌ Bad
/user_profiles ← underscores
/userprofiles ← no separator
/UserProfiles ← PascalCase4. Use Lowercase
URLs are case-sensitive in many servers.
✅ Good
/users/123/orders
❌ Bad
/Users/123/Orders
/USERS/123/ORDERS5. Represent Relationships with Nesting
Show parent-child relationships in the URL.
# User's orders
GET /users/123/orders
# Order's items
GET /orders/456/items
# Specific item in an order
GET /orders/456/items/789But don't nest too deep (max 2-3 levels):
❌ Too deep
/users/123/orders/456/items/789/variants/321
✅ Better - flatten when needed
/order-items/789?include=variantsQuery Parameters
Use query parameters for:
Filtering
GET /products?category=electronics&price_min=100&price_max=500
GET /users?role=admin&status=active
GET /orders?created_after=2024-01-01Sorting
GET /products?sort=price # ascending
GET /products?sort=-price # descending (prefix with -)
GET /products?sort=category,-price # multiple fieldsPagination
# Offset-based
GET /products?page=2&limit=20
# Cursor-based
GET /products?cursor=abc123&limit=20Field Selection
GET /users?fields=id,name,email
GET /users/123?fields=name,avatarExpansion/Inclusion
GET /orders/123?include=items,customer
GET /posts?include=author,commentsURL Patterns Cheat Sheet
| Action | HTTP Method | URL Pattern | Example |
|---|---|---|---|
| List all | GET | /resources | GET /users |
| Get one | GET | /resources/:id | GET /users/123 |
| Create | POST | /resources | POST /users |
| Replace | PUT | /resources/:id | PUT /users/123 |
| Update | PATCH | /resources/:id | PATCH /users/123 |
| Delete | DELETE | /resources/:id | DELETE /users/123 |
| Nested list | GET | /resources/:id/subresources | GET /users/123/orders |
| Nested one | GET | /resources/:id/subresources/:subId | GET /users/123/orders/456 |
Non-Resource Endpoints
Sometimes you need endpoints that don't fit the resource model:
# Actions on resources
POST /users/123/activate
POST /orders/456/cancel
POST /payments/789/refund
# Search across resources
GET /search?q=laptop&type=products,reviews
# Aggregations
GET /stats/users/monthly
GET /reports/sales?year=2024Part 6: Request and Response Bodies
Request Body Structure
For POST, PUT, and PATCH requests:
// Creating a user
POST /api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"password": "securePass123!",
"profile": {
"bio": "Software developer",
"location": "San Francisco",
"website": "https://johndoe.dev"
},
"preferences": {
"newsletter": true,
"theme": "dark"
}
}Response Body Structure
Single Resource
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"profile": {
"bio": "Software developer",
"location": "San Francisco"
},
"createdAt": "2024-01-20T14:30:00Z",
"updatedAt": "2024-01-20T14:30:00Z"
}Collection with Metadata
{
"data": [
{"id": 1, "name": "John"},
{"id": 2, "name": "Jane"},
{"id": 3, "name": "Bob"}
],
"meta": {
"total": 150,
"page": 1,
"limit": 10,
"totalPages": 15
},
"links": {
"self": "/api/users?page=1&limit=10",
"next": "/api/users?page=2&limit=10",
"last": "/api/users?page=15&limit=10"
}
}Error Response Structure
Consistent error responses help clients handle failures:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"value": "not-an-email"
},
{
"field": "password",
"message": "Must be at least 8 characters",
"value": "short"
}
],
"timestamp": "2024-01-20T15:00:00Z",
"requestId": "req_abc123xyz"
}
}Date/Time Formats
Always use ISO 8601 format with timezone:
{
"createdAt": "2024-01-20T14:30:00Z", // UTC
"scheduledFor": "2024-01-25T09:00:00-08:00", // With offset
"date": "2024-01-20" // Date only
}Part 7: Authentication and Authorization
Authentication Methods
1. API Keys
Simple but limited. Best for server-to-server communication.
# In header
GET /api/data HTTP/1.1
X-API-Key: sk_live_abc123xyz
# Or in query (less secure)
GET /api/data?api_key=sk_live_abc123xyzPros: Simple to implement Cons: No user context, easy to leak
2. JWT (JSON Web Tokens)
Stateless authentication. Most common for modern APIs.
GET /api/users/me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cJWT structure:
header.payload.signature// Decoded payload
{
"sub": "user_123",
"name": "John Doe",
"role": "admin",
"iat": 1516239022,
"exp": 1516325422
}Pros: Stateless, contains user info, scalable Cons: Can't be revoked easily, size overhead
3. OAuth 2.0
For third-party access and social logins.
# Step 1: Redirect to authorization server
GET https://accounts.google.com/oauth/authorize?
client_id=your-client-id&
redirect_uri=https://yourapp.com/callback&
scope=email profile&
response_type=code
# Step 2: Exchange code for token
POST https://oauth2.googleapis.com/token
{
"code": "auth_code_from_callback",
"client_id": "your-client-id",
"client_secret": "your-secret",
"grant_type": "authorization_code"
}
# Step 3: Use access token
GET /api/users/me HTTP/1.1
Authorization: Bearer access_token_from_step_2Pros: Industry standard, granular permissions, third-party support Cons: Complex flow, many moving parts
Authorization Patterns
Role-Based Access Control (RBAC)
// User with roles
{
"id": 123,
"email": "admin@example.com",
"roles": ["admin", "editor"]
}
// Permission check
if (user.roles.includes("admin")) {
// Allow action
}Permission-Based Access Control
// User with specific permissions
{
"id": 123,
"permissions": [
"users:read",
"users:write",
"posts:read",
"posts:write",
"posts:delete"
]
}Security Headers
Always include these security headers:
# Response headers
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'Part 8: Versioning Strategies
APIs evolve. Versioning lets you make breaking changes without breaking existing clients.
URL Path Versioning (Recommended)
Version in the URL path:
GET /api/v1/users
GET /api/v2/usersPros:
- Obvious and explicit
- Easy to route
- Cacheable (different URLs)
Cons:
- URL "pollution"
- Clients must update URLs
Header Versioning
Custom header:
GET /api/users HTTP/1.1
Accept-Version: v2
# or
API-Version: 2024-01-20Pros: Clean URLs Cons: Less visible, harder to test in browser
Query Parameter Versioning
GET /api/users?version=2Pros: Easy to switch versions Cons: Optional parameter can be forgotten
Content Negotiation
Use Accept header:
GET /api/users HTTP/1.1
Accept: application/vnd.company.v2+jsonPros: Proper HTTP semantics Cons: Complex, hard to test
Versioning Best Practices
- Start with v1, even if you think you won't need versions
- Version the entire API, not individual endpoints
- Document deprecation timelines (e.g., "v1 supported until 2025-01-01")
- Use semantic versioning for major changes
- Provide migration guides when releasing new versions
Part 9: Pagination Patterns
Offset-Based Pagination
Traditional approach using page number and limit:
GET /api/users?page=2&limit=20{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
}
}Pros: Simple, can jump to any page Cons: Inconsistent with real-time data (items shift), slow on large offsets
Cursor-Based Pagination
Use an opaque cursor instead of page numbers:
GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20{
"data": [...],
"cursors": {
"before": "eyJpZCI6MTAzfQ",
"after": "eyJpZCI6MTIzfQ",
"hasMore": true
}
}Pros: Consistent results, handles real-time data, efficient Cons: Can't jump to specific page, cursor can become invalid
Keyset Pagination
Similar to cursor, but uses actual field values:
# First page
GET /api/users?limit=20
# Next page (after last item's created_at)
GET /api/users?created_after=2024-01-20T14:30:00Z&limit=20Pros: Extremely efficient (uses indexes), stable Cons: Requires sortable unique field, can't skip pages
Choosing a Pagination Strategy
| Use Case | Recommended |
|---|---|
| Admin dashboards with page numbers | Offset |
| Social media feeds | Cursor |
| High-performance, large datasets | Keyset |
| Simple APIs with stable data | Offset |
| Real-time data | Cursor or Keyset |
Part 10: Filtering, Sorting, and Searching
Filtering
Simple equality filters:
GET /api/products?category=electronics&brand=appleRange filters:
GET /api/products?price_min=100&price_max=500
GET /api/orders?created_after=2024-01-01&created_before=2024-02-01Complex filters (JSON):
GET /api/products?filter={"category":"electronics","price":{"$gte":100,"$lte":500}}Sorting
Single field:
GET /api/products?sort=price # Ascending
GET /api/products?sort=-price # Descending (prefix with -)Multiple fields:
GET /api/products?sort=-featured,price # Featured first, then by priceFull-Text Search
Simple search:
GET /api/products?q=wireless+headphonesAdvanced search:
GET /api/search?q=laptop&fields=name,description&fuzzy=trueCombining Everything
GET /api/products?
category=electronics&
price_min=100&
price_max=1000&
sort=-rating,price&
q=laptop&
page=1&
limit=20&
fields=id,name,price,ratingPart 11: HATEOAS (Hypermedia)
HATEOAS = Hypermedia as the Engine of Application State
The idea: responses include links to related resources and available actions.
Example Without HATEOAS
{
"id": 123,
"status": "pending",
"total": 99.99
}Client must know:
- How to get order details
- What actions are available
- Where to find related resources
Example With HATEOAS
{
"id": 123,
"status": "pending",
"total": 99.99,
"_links": {
"self": {"href": "/api/orders/123"},
"items": {"href": "/api/orders/123/items"},
"customer": {"href": "/api/customers/456"},
"cancel": {
"href": "/api/orders/123/cancel",
"method": "POST"
},
"payment": {
"href": "/api/orders/123/payment",
"method": "POST"
}
}
}Benefits of HATEOAS
- Self-documenting: Response tells you what's possible
- Loose coupling: Client doesn't hardcode URLs
- Evolvability: Server can change URLs without breaking clients
Common HATEOAS Formats
HAL (Hypertext Application Language)
{
"id": 123,
"_links": {
"self": {"href": "/orders/123"},
"items": {"href": "/orders/123/items"}
},
"_embedded": {
"items": [
{"id": 1, "name": "Product A"}
]
}
}JSON:API
{
"data": {
"type": "orders",
"id": "123",
"attributes": {"status": "pending"},
"relationships": {
"items": {
"links": {
"related": "/orders/123/items"
}
}
}
}
}Part 12: REST API Best Practices
1. Use Proper HTTP Methods
✅ GET for reading
✅ POST for creating
✅ PUT/PATCH for updating
✅ DELETE for deleting
❌ POST /deleteUser
❌ GET /createUser?name=John2. Return Appropriate Status Codes
✅ 200 for successful GET/PUT/PATCH
✅ 201 for successful POST (creation)
✅ 204 for successful DELETE
✅ 400 for bad client input
✅ 401 for authentication failure
✅ 403 for authorization failure
✅ 404 for missing resources
✅ 500 for server errors
❌ 200 for everything with "error" in body3. Use Consistent Naming
✅ /users (plural, lowercase)
✅ /user-profiles (hyphenated)
✅ /orders/123/items (nested resources)
❌ /User
❌ /user_profile
❌ /getOrders4. Version Your API
✅ /api/v1/users
✅ /api/v2/users
❌ /api/users (no version = trouble later)5. Implement Proper Error Handling
// ✅ Good: Structured error with details
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{"field": "email", "message": "Invalid email format"}
]
}
}
// ❌ Bad: Generic error message
{
"error": "Something went wrong"
}6. Use HTTPS Always
✅ https://api.example.com
❌ http://api.example.com7. Implement Rate Limiting
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Retry-After: 60
{
"error": "Rate limit exceeded"
}8. Document Your API
Use OpenAPI/Swagger for documentation:
openapi: 3.0.0
info:
title: My API
version: 1.0.0
paths:
/users:
get:
summary: List all users
responses:
'200':
description: Successful response9. Support Content Negotiation
# Request
Accept: application/json
# Response
Content-Type: application/json10. Include Request IDs
For debugging and tracing:
HTTP/1.1 500 Internal Server Error
X-Request-ID: req_abc123xyz
{
"error": "Internal server error",
"requestId": "req_abc123xyz"
}Part 13: REST vs Alternatives
REST vs GraphQL
| Aspect | REST | GraphQL |
|---|---|---|
| Data fetching | Fixed endpoints | Flexible queries |
| Over-fetching | Common | Solved |
| Under-fetching | Requires multiple calls | Single query |
| Caching | HTTP caching | Custom caching |
| Learning curve | Lower | Higher |
| Tooling | Mature | Growing |
| Real-time | WebSocket/SSE | Subscriptions |
Use REST when: CRUD operations, simple data, caching important Use GraphQL when: Complex data relationships, mobile apps, flexible clients
REST vs gRPC
| Aspect | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1, HTTP/2 | HTTP/2 |
| Format | JSON (text) | Protocol Buffers (binary) |
| Performance | Good | Excellent |
| Streaming | Limited | Built-in |
| Browser support | Native | Requires proxy |
| Contract | OpenAPI (optional) | Required (.proto) |
Use REST when: Public APIs, browser clients, simplicity Use gRPC when: Microservices, high-performance, bi-directional streaming
When to Use REST
REST is ideal for:
- ✅ Public APIs consumed by third parties
- ✅ Web applications with browser clients
- ✅ CRUD-heavy applications
- ✅ When caching is important
- ✅ Simple, well-understood requirements
- ✅ Teams new to API development
Summary
REST APIs are the backbone of modern web development. Here's what we covered:
Key Concepts
- Resources are the core abstraction—everything is a resource
- HTTP methods map to CRUD operations (GET, POST, PUT, PATCH, DELETE)
- Status codes communicate what happened (2xx success, 4xx client error, 5xx server error)
- URLs should use nouns, be plural, and show relationships
Best Practices Checklist
✅ Use nouns for resources, not verbs
✅ Use proper HTTP methods and status codes
✅ Version your API from day one
✅ Implement authentication (JWT, OAuth)
✅ Use HTTPS everywhere
✅ Paginate large collections
✅ Document with OpenAPI/Swagger
✅ Return consistent error responses
✅ Implement rate limiting
✅ Include request IDs for debugging
What's Next?
Now that you understand REST API fundamentals, explore:
Build REST APIs:
- Spring Boot REST API Best Practices - Build REST APIs with Java
- TypeScript Phase 3: Backend Development - Build REST APIs with Node.js
- FastAPI Getting Started - Build REST APIs with Python
Document Your APIs:
- API Documentation with OpenAPI/Swagger - Auto-generate interactive docs
- Understanding OpenAPI with FastAPI - OpenAPI in Python
Deep Dive:
- HTTP Protocol Complete Guide - Deep dive into HTTP
- Database Schema Design Guide - Design databases for your APIs
Practice Exercises
Exercise 1: Design a Blog API
Design endpoints for a blog with posts, comments, and users:
GET /api/posts
POST /api/posts
GET /api/posts/:id
PUT /api/posts/:id
DELETE /api/posts/:id
GET /api/posts/:id/comments
POST /api/posts/:id/comments
GET /api/users/:id/postsExercise 2: Status Code Quiz
What status code would you return for:
- User not authenticated → ?
- User authenticated but not allowed → ?
- Email already exists → ?
- Invalid JSON in request → ?
- Resource created successfully → ?
Answers
- 401 Unauthorized
- 403 Forbidden
- 409 Conflict
- 400 Bad Request
- 201 Created
Exercise 3: Implement a REST API
Build a simple REST API with these endpoints:
GET /api/todos - List all todos
POST /api/todos - Create a todo
GET /api/todos/:id - Get a todo
PATCH /api/todos/:id - Update a todo
DELETE /api/todos/:id - Delete a todoUse your favorite framework (Express.js, FastAPI, Spring Boot) and implement proper:
- Status codes
- Error handling
- Request validation
REST APIs may seem simple on the surface, but mastering them requires understanding HTTP, resource design, and many subtle best practices. With this foundation, you're ready to build robust, scalable APIs that developers love to use.
Happy API building! 🚀
📬 Subscribe to Newsletter
Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.
We respect your privacy. Unsubscribe at any time.
💬 Comments
Sign in to leave a comment
We'll never post without your permission.