REST (Representational State Transfer) is the most popular architectural style for building web APIs. This comprehensive guide explains REST principles, how REST APIs work, and how to use them effectively.
Table of Contents
- What is REST?
- The 6 REST Principles
- REST API Components
- HTTP Methods in REST
- REST API Endpoints
- Request and Response Examples
- Best Practices
What is REST?
REST (Representational State Transfer) is an architectural style for designing networked applications. It relies on a stateless, client-server protocol - almost always HTTP.
Simple Definition: REST is a set of rules for building APIs that are easy to use, scale well, and work with standard web technologies. Think of it as a "best practices guide" for creating web APIs.
Why REST is Popular
✅ Simple and Standardized: Uses HTTP methods everyone knows
✅ Stateless: Each request is independent (easy to scale)
✅ Cacheable: Responses can be cached for better performance
✅ Works Everywhere: Any platform that supports HTTP can use it
✅ Human-Readable: URLs and responses make sense
REST vs Other API Styles
| style | protocol | format | complexity | use |
|---|---|---|---|---|
| REST | HTTP | JSON/XML | Simple | Most web APIs |
| GraphQL | HTTP | JSON | Medium | Complex data needs |
| SOAP | Multiple | XML | Complex | Enterprise systems |
| gRPC | HTTP/2 | Protocol Buffers | Complex | Microservices |
The 6 REST Principles
REST APIs must follow these architectural constraints:
1. Client-Server Architecture
The client (frontend) and server (backend) are separate and independent.
CLIENT (Your App/Website)
↓ Request
↓
API (REST)
↓
↓ Response
SERVER (Database/Logic)
Benefits:
- Frontend and backend can evolve independently
- Multiple clients can use the same API
- Easier to scale and maintain
Example:
// Client (React app)
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('https://api.example.com/users/123') // REST API
.then(res => res.json())
.then(data => setUser(data));
}, []);
return <div>{user?.name}</div>;
}
// Server doesn't care if the client is React, Vue, or a mobile app!
2. Stateless
Each request from client to server must contain all information needed to understand and process the request. The server doesn't store any client context between requests.
BAD (Stateful):
Request 1: "Login as user123"
Server: "OK, I'll remember you're user123"
Request 2: "Get my profile"
Server: "I know you're user123, here's your profile"
GOOD (Stateless):
Request 1: "Login as user123"
Server: "Here's your token: abc123"
Request 2: "Get profile for user123" + token
Server: "Verified token, here's profile for user123"
Example:
// ✅ Stateless - Every request includes auth token
fetch('https://api.example.com/users/me', {
headers: {
'Authorization': 'Bearer abc123token'
}
});
// ❌ Stateful - Relying on session stored on server
fetch('https://api.example.com/users/me');
// Server: "Who are you? I need to check my session storage"
Benefits:
- Easy to scale (any server can handle any request)
- More reliable (no session data to lose)
- Simpler to debug
3. Cacheable
Responses must define themselves as cacheable or non-cacheable. This improves performance by reducing unnecessary requests.
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600 ← Cacheable for 1 hour
{
"id": 123,
"name": "John Doe"
}
Example:
// First request - Goes to server
fetch('https://api.example.com/users/123')
.then(res => res.json())
.then(data => console.log(data));
// Second request within cache time - Uses cached data (faster!)
fetch('https://api.example.com/users/123')
.then(res => res.json())
.then(data => console.log(data)); // Instant!
4. Uniform Interface
REST APIs should have a consistent, predictable structure. This is achieved through:
a) Resource Identification: Each resource has a unique URL
/users ← All users
/users/123 ← Specific user
/users/123/posts ← That user's posts
b) Resource Manipulation: Use HTTP methods consistently
GET /users/123 → Read user
POST /users → Create user
PUT /users/123 → Update user
DELETE /users/123 → Delete user
c) Self-Descriptive Messages: Responses include everything needed
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"links": {
"self": "/users/123",
"posts": "/users/123/posts"
}
}
d) HATEOAS: Hypermedia as the Engine of Application State (advanced)
{
"id": 123,
"name": "John Doe",
"_links": {
"self": { "href": "/users/123" },
"posts": { "href": "/users/123/posts" },
"friends": { "href": "/users/123/friends" }
}
}
5. Layered System
Client can't tell if it's connected directly to the end server or an intermediary (load balancer, cache, etc.).
CLIENT
↓
LOAD BALANCER
↓
CACHE SERVER
↓
API SERVER
↓
DATABASE
Client just sees: api.example.com
Doesn't know about all these layers!
6. Code on Demand (Optional)
Servers can extend client functionality by sending executable code (like JavaScript).
// Server sends JavaScript that client executes
// Rarely used in modern REST APIs
REST API Components
URLs (Uniform Resource Locators)
REST APIs use URLs to identify resources:
https://api.example.com/v1/users/123/posts?limit=10
└──┬──┘ └────┬────┘ └┬┘ └─┬──┘└┬┘└─┬─┘ └───┬───┘
Protocol Domain Ver Path ID Res Query
Breakdown:
https://- Protocol (always use HTTPS for security)api.example.com- Domain/v1- API version/users- Resource collection/123- Specific resource ID/posts- Related sub-resource?limit=10- Query parameter
HTTP Methods
REST uses standard HTTP methods (also called HTTP verbs):
| Method | Purpose | Safe? | Idempotent? | Example |
|---|---|---|---|---|
GET | Read/Retrieve | ✅ Yes | ✅ Yes | Get user data |
POST | Create | ❌ No | ❌ No | Create new user |
PUT | Update/Replace | ❌ No | ✅ Yes | Update entire user |
PATCH | Partial Update | ❌ No | ❌ No | Update user email only |
DELETE | Remove | ❌ No | ✅ Yes | Delete user |
HEAD | Get Headers Only | ✅ Yes | ✅ Yes | Check if resource exists |
OPTIONS | Get Available Methods | ✅ Yes | ✅ Yes | CORS preflight |
Safe: Doesn't modify data
Idempotent: Same result if called multiple times
Learn more about HTTP methods →
Headers
Headers provide metadata about the request or response:
# Request Headers
GET /users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer abc123token
Content-Type: application/json
Accept: application/json
User-Agent: Mozilla/5.0
# Response Headers
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
ETag: "abc123"
Content-Length: 256
Status Codes
REST APIs use HTTP status codes to indicate results:
Success (2xx):
200 OK- Successful GET, PUT, PATCH, or DELETE201 Created- Successful POST204 No Content- Successful DELETE (no body)
Client Errors (4xx):
400 Bad Request- Invalid syntax401 Unauthorized- Authentication required403 Forbidden- No permission404 Not Found- Resource doesn't exist422 Unprocessable Entity- Validation failed
Server Errors (5xx):
500 Internal Server Error- Server crashed503 Service Unavailable- Server overloaded
Request/Response Body
Data sent to or from the API, usually in JSON format:
// Request Body (POST/PUT/PATCH)
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
// Response Body
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"age": 30,
"createdAt": "2025-01-15T10:30:00Z"
}
HTTP Methods in REST
GET - Retrieve Data
Get data from the server. Should never modify data.
// Get all users
fetch('https://api.example.com/users')
.then(res => res.json())
.then(users => console.log(users));
// Get specific user
fetch('https://api.example.com/users/123')
.then(res => res.json())
.then(user => console.log(user));
// Get with query parameters
fetch('https://api.example.com/users?role=admin&limit=10')
.then(res => res.json())
.then(users => console.log(users));
Response:
// GET /users/123
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
POST - Create New Resource
Create a new resource on the server.
// Create new user
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer abc123token'
},
body: JSON.stringify({
name: 'Jane Smith',
email: 'jane@example.com',
age: 28
})
})
.then(res => res.json())
.then(newUser => console.log('Created:', newUser));
Response:
HTTP/1.1 201 Created
Location: /users/124
Content-Type: application/json
{
"id": 124,
"name": "Jane Smith",
"email": "jane@example.com",
"age": 28,
"createdAt": "2025-01-15T10:30:00Z"
}
PUT - Update/Replace Resource
Replace entire resource with new data.
// Update user (replace all fields)
fetch('https://api.example.com/users/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer abc123token'
},
body: JSON.stringify({
name: 'John Updated',
email: 'john.new@example.com',
age: 31
})
})
.then(res => res.json())
.then(user => console.log('Updated:', user));
PATCH - Partial Update
Update only specific fields of a resource.
// Update only email (keep name and age)
fetch('https://api.example.com/users/123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer abc123token'
},
body: JSON.stringify({
email: 'john.new@example.com'
})
})
.then(res => res.json())
.then(user => console.log('Patched:', user));
DELETE - Remove Resource
Delete a resource from the server.
// Delete user
fetch('https://api.example.com/users/123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer abc123token'
}
})
.then(res => {
if (res.status === 204) {
console.log('User deleted successfully');
}
});
Response:
HTTP/1.1 204 No Content
# No response body
REST API Endpoints
Resource Naming Conventions
Use nouns, not verbs:
✅ GOOD (nouns)
/users
/posts
/comments
/products
❌ BAD (verbs)
/getUsers
/createPost
/deleteComment
/fetchProducts
Use plural nouns:
✅ GOOD
/users ← Collection
/users/123 ← Specific item
❌ BAD
/user
/user/123
Use hierarchical structure for relationships:
✅ GOOD
/users/123/posts ← User's posts
/users/123/posts/456 ← Specific post by user
/users/123/posts/456/comments ← Comments on that post
❌ BAD
/getUserPosts?userId=123
/getPostComments?postId=456
Common Endpoint Patterns
# Collection Operations
GET /users → List all users
POST /users → Create new user
# Single Resource Operations
GET /users/123 → Get user 123
PUT /users/123 → Update user 123 (full)
PATCH /users/123 → Update user 123 (partial)
DELETE /users/123 → Delete user 123
# Sub-resource Operations
GET /users/123/posts → Get posts by user 123
POST /users/123/posts → Create post for user 123
GET /users/123/posts/456 → Get post 456 by user 123
# Filtering and Pagination
GET /users?role=admin → Filter by role
GET /users?page=2&limit=10 → Pagination
GET /users?sort=name&order=asc → Sorting
GET /users?search=john → Search
GET /users?fields=id,name,email → Partial response
Query Parameters
Use query strings for filtering, sorting, and pagination:
// Filtering
GET /products?category=electronics&minPrice=100&maxPrice=500
// Pagination
GET /users?page=2&limit=20
// Sorting
GET /users?sort=createdAt&order=desc
// Searching
GET /users?search=john&searchFields=name,email
// Field Selection
GET /users?fields=id,name,email
// Combined
GET /products?category=electronics&sort=price&order=asc&page=1&limit=20
Request and Response Examples
Example 1: User Management API
// ========================================
// GET /users - List all users
// ========================================
fetch('https://api.example.com/users?limit=2')
.then(res => res.json())
.then(console.log);
// Response:
{
"data": [
{
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
{
"id": 2,
"name": "Jane Smith",
"email": "jane@example.com"
}
],
"pagination": {
"page": 1,
"limit": 2,
"total": 100,
"totalPages": 50
}
}
// ========================================
// GET /users/1 - Get specific user
// ========================================
fetch('https://api.example.com/users/1')
.then(res => res.json())
.then(console.log);
// Response:
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}
// ========================================
// POST /users - Create new user
// ========================================
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here'
},
body: JSON.stringify({
name: 'Bob Wilson',
email: 'bob@example.com',
password: 'securepass123'
})
})
.then(res => res.json())
.then(console.log);
// Response (201 Created):
{
"id": 3,
"name": "Bob Wilson",
"email": "bob@example.com",
"role": "user",
"createdAt": "2025-01-15T10:35:00Z"
}
// ========================================
// PUT /users/3 - Update user (full replacement)
// ========================================
fetch('https://api.example.com/users/3', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here'
},
body: JSON.stringify({
name: 'Robert Wilson',
email: 'robert@example.com',
role: 'moderator'
})
})
.then(res => res.json())
.then(console.log);
// Response (200 OK):
{
"id": 3,
"name": "Robert Wilson",
"email": "robert@example.com",
"role": "moderator",
"updatedAt": "2025-01-15T10:40:00Z"
}
// ========================================
// PATCH /users/3 - Partial update
// ========================================
fetch('https://api.example.com/users/3', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here'
},
body: JSON.stringify({
email: 'robert.new@example.com'
})
})
.then(res => res.json())
.then(console.log);
// Response (200 OK):
{
"id": 3,
"name": "Robert Wilson",
"email": "robert.new@example.com",
"role": "moderator",
"updatedAt": "2025-01-15T10:45:00Z"
}
// ========================================
// DELETE /users/3 - Delete user
// ========================================
fetch('https://api.example.com/users/3', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer your-token-here'
}
})
.then(res => {
if (res.status === 204) {
console.log('User deleted successfully');
}
});
// Response (204 No Content):
// Empty response body
Example 2: Blog API
// ========================================
// GET /posts - Get all posts
// ========================================
fetch('https://api.example.com/posts?status=published&sort=createdAt&order=desc')
.then(res => res.json())
.then(console.log);
// Response:
{
"data": [
{
"id": 1,
"title": "Getting Started with REST APIs",
"slug": "getting-started-rest-apis",
"excerpt": "Learn the basics of REST APIs...",
"author": {
"id": 1,
"name": "John Doe"
},
"createdAt": "2025-01-15T10:00:00Z",
"commentsCount": 5
}
]
}
// ========================================
// GET /posts/1 - Get specific post
// ========================================
fetch('https://api.example.com/posts/1')
.then(res => res.json())
.then(console.log);
// Response:
{
"id": 1,
"title": "Getting Started with REST APIs",
"slug": "getting-started-rest-apis",
"content": "Full post content here...",
"author": {
"id": 1,
"name": "John Doe",
"avatar": "https://example.com/avatars/john.jpg"
},
"tags": ["api", "rest", "tutorial"],
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}
// ========================================
// GET /posts/1/comments - Get post comments
// ========================================
fetch('https://api.example.com/posts/1/comments')
.then(res => res.json())
.then(console.log);
// Response:
{
"data": [
{
"id": 1,
"author": "Alice",
"content": "Great article!",
"createdAt": "2025-01-15T11:00:00Z"
},
{
"id": 2,
"author": "Bob",
"content": "Very helpful, thanks!",
"createdAt": "2025-01-15T12:00:00Z"
}
]
}
// ========================================
// POST /posts/1/comments - Add comment
// ========================================
fetch('https://api.example.com/posts/1/comments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here'
},
body: JSON.stringify({
content: 'This is my comment!'
})
})
.then(res => res.json())
.then(console.log);
// Response (201 Created):
{
"id": 3,
"author": {
"id": 2,
"name": "Current User"
},
"content": "This is my comment!",
"createdAt": "2025-01-15T13:00:00Z"
}
Best Practices
1. Version Your API
Always include API version in the URL:
✅ GOOD
https://api.example.com/v1/users
https://api.example.com/v2/users
❌ BAD
https://api.example.com/users ← No version
Why? You can update v2 without breaking v1 users
2. Use HTTPS Always
✅ GOOD
https://api.example.com
❌ BAD
http://api.example.com ← Insecure!
HTTPS encrypts data in transit
3. Return Appropriate Status Codes
// ✅ GOOD - Descriptive status codes
if (user) {
res.status(200).json(user); // Found
} else {
res.status(404).json({ error: 'User not found' }); // Not found
}
// ❌ BAD - Always 200
res.status(200).json({ error: 'User not found' }); // Wrong!
4. Use Consistent Naming
// ✅ GOOD - Consistent camelCase
{
"userId": 123,
"firstName": "John",
"createdAt": "2025-01-15"
}
// ❌ BAD - Mixed conventions
{
"user_id": 123,
"FirstName": "John",
"created-at": "2025-01-15"
}
5. Provide Helpful Error Messages
// ✅ GOOD - Descriptive error
{
"error": {
"code": "INVALID_EMAIL",
"message": "Email address is not valid",
"field": "email",
"value": "not-an-email"
}
}
// ❌ BAD - Vague error
{
"error": "Invalid input"
}
6. Document Your API
Provide clear documentation with examples:
# GET /users/:id
Get a specific user by ID.
## Parameters
- \`id\` (required): User ID
## Response (200 OK)
\`\`\`json
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
\`\`\`
## Errors
- \`404\`: User not found
- \`401\`: Unauthorized
7. Implement Rate Limiting
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1610000000
# When limit exceeded:
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
8. Support Pagination
// ✅ GOOD - Paginated response
GET /users?page=2&limit=20
{
"data": [ /* 20 users */ ],
"pagination": {
"page": 2,
"limit": 20,
"total": 1000,
"totalPages": 50,
"nextPage": "/users?page=3&limit=20",
"prevPage": "/users?page=1&limit=20"
}
}
Summary
REST APIs are the foundation of modern web services. By following REST principles and best practices, you can build APIs that are easy to use, maintain, and scale.
Key Takeaways:
✅ REST uses HTTP methods semantically (GET, POST, PUT, DELETE)
✅ Resources are identified by URLs
✅ Each request is stateless
✅ Use proper HTTP status codes
✅ Return JSON for data interchange
✅ Version your API
✅ Always use HTTPS
✅ Provide clear documentation