REST APIs
Learn RESTful API design principles, HTTP methods, endpoints, and status codes
What is REST?
REST (Representational State Transfer) is an architectural style for designing networked applications. Created by Roy Fielding in 2000, REST has become the dominant web API design pattern due to its simplicity, scalability, and use of standard HTTP protocols.
A RESTful API is one that adheres to REST principles, providing a uniform interface for interacting with resources.
REST Principles
🔗 Uniform Interface
Consistent way to interact with resources using standard HTTP methods and URLs.
🚫 Stateless
Each request contains all information needed. Server doesn't store client state.
📦 Cacheable
Responses can be cached to improve performance and reduce server load.
📊 Layered System
Client can't tell if connected directly to server or through intermediary.
🖥️ Client-Server
Separation of concerns between client (UI) and server (data).
📝 Resource-Based
Everything is a resource identified by URIs. Resources have representations (JSON/XML).
RESTful URL Structure
REST APIs use descriptive, hierarchical URLs to represent resources:
// Resource URL Structure
https://api.example.com/v1/users // Collection of users
https://api.example.com/v1/users/123 // Single user (ID: 123)
https://api.example.com/v1/users/123/posts // User's posts
https://api.example.com/v1/users/123/posts/456 // Specific post
// URL Components:
// Protocol: https://
// Host: api.example.com
// Version: /v1
// Resource: /users
// Identifier: /123
// Sub-resource: /posts
✅ Good URL Practices:
- • Use nouns, not verbs:
/usersnot/getUsers - • Use plural nouns:
/usersnot/user - • Use lowercase:
/usersnot/Users - • Use hyphens for multi-word:
/user-profilesnot/userProfiles
HTTP Methods in Detail
// GET - Retrieve all users
const response = await fetch('https://api.example.com/users');
const users = await response.json();
// GET - Retrieve single user
const user = await fetch('https://api.example.com/users/123');
const userData = await user.json();
// GET with query parameters (filtering/pagination)
const filteredUsers = await fetch(
'https://api.example.com/users?role=admin&page=1&limit=10'
);
// Response: 200 OK
{
"data": [
{ "id": 1, "name": "John Doe", "role": "admin" }
],
"pagination": { "page": 1, "limit": 10, "total": 45 }
}
// POST - Create new user
const newUser = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: 'Jane Smith',
email: 'jane@example.com',
role: 'user'
})
});
const created = await newUser.json();
// Response: 201 Created
{
"id": 124,
"name": "Jane Smith",
"email": "jane@example.com",
"role": "user",
"createdAt": "2024-01-15T10:30:00Z"
}
// PUT - Replace entire user (all fields required)
const updatedUser = await fetch('https://api.example.com/users/124', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: 'Jane Doe', // Changed
email: 'jane@example.com', // Same
role: 'admin' // Changed
})
});
// Response: 200 OK
{
"id": 124,
"name": "Jane Doe",
"email": "jane@example.com",
"role": "admin",
"updatedAt": "2024-01-15T11:00:00Z"
}
// PATCH - Update only specific fields
const patchedUser = await fetch('https://api.example.com/users/124', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
role: 'moderator' // Only updating role
})
});
// Response: 200 OK
{
"id": 124,
"name": "Jane Doe",
"email": "jane@example.com",
"role": "moderator",
"updatedAt": "2024-01-15T11:30:00Z"
}
// DELETE - Remove user
const deleteResponse = await fetch('https://api.example.com/users/124', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer token123'
}
});
// Response: 204 No Content (empty body)
// OR
// Response: 200 OK
{
"message": "User successfully deleted",
"deletedId": 124
}
HTTP Status Codes
Status codes indicate the result of an HTTP request:
✅ 2xx Success
200 OK - Request succeeded201 Created - Resource created204 No Content - Success, no body202 Accepted - Processing queued↪️ 3xx Redirection
301 Moved Permanently - New URL302 Found - Temporary redirect304 Not Modified - Use cache307 Temporary Redirect⚠️ 4xx Client Errors
400 Bad Request - Invalid syntax401 Unauthorized - Auth required403 Forbidden - No permission404 Not Found - Resource missing422 Unprocessable - Validation error429 Too Many Requests - Rate limited❌ 5xx Server Errors
500 Internal Error - Server error502 Bad Gateway - Invalid upstream503 Unavailable - Server down504 Gateway Timeout - Upstream timeoutComplete REST API Client
Building a reusable API client class:
// REST API Client Class
class ApiClient {
constructor(baseUrl, token = null) {
this.baseUrl = baseUrl;
this.token = token;
}
setToken(token) {
this.token = token;
}
getHeaders() {
const headers = {
'Content-Type': 'application/json',
};
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
return headers;
}
async request(method, endpoint, data = null) {
const url = `${this.baseUrl}${endpoint}`;
const options = {
method,
headers: this.getHeaders(),
};
if (data && ['POST', 'PUT', 'PATCH'].includes(method)) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
const contentType = response.headers.get('content-type');
let responseData;
if (contentType?.includes('application/json')) {
responseData = await response.json();
} else {
responseData = await response.text();
}
if (!response.ok) {
throw {
status: response.status,
message: responseData.message || 'Request failed',
data: responseData
};
}
return responseData;
} catch (error) {
console.error(`API Error [${method}] ${endpoint}:`, error);
throw error;
}
}
// Convenience methods
get(endpoint) { return this.request('GET', endpoint); }
post(endpoint, data) { return this.request('POST', endpoint, data); }
put(endpoint, data) { return this.request('PUT', endpoint, data); }
patch(endpoint, data) { return this.request('PATCH', endpoint, data); }
delete(endpoint) { return this.request('DELETE', endpoint); }
}
// Usage Example
const api = new ApiClient('https://api.example.com/v1');
api.setToken('your-jwt-token');
// CRUD Operations
async function userOperations() {
// Create
const newUser = await api.post('/users', {
name: 'John Doe',
email: 'john@example.com'
});
// Read
const users = await api.get('/users');
const user = await api.get(`/users/${newUser.id}`);
// Update
const updated = await api.patch(`/users/${newUser.id}`, {
name: 'John Smith'
});
// Delete
await api.delete(`/users/${newUser.id}`);
}
userOperations();
Query Parameters
Use query parameters for filtering, sorting, and pagination:
// Building URLs with Query Parameters
class QueryBuilder {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.params = new URLSearchParams();
}
page(page, limit = 10) {
this.params.set('page', page);
this.params.set('limit', limit);
return this;
}
filter(field, value) {
this.params.set(field, value);
return this;
}
sort(field, order = 'asc') {
this.params.set('sort', field);
this.params.set('order', order);
return this;
}
search(query) {
this.params.set('q', query);
return this;
}
fields(fieldList) {
this.params.set('fields', fieldList.join(','));
return this;
}
build() {
const queryString = this.params.toString();
return queryString ? `${this.baseUrl}?${queryString}` : this.baseUrl;
}
}
// Usage
const url = new QueryBuilder('https://api.example.com/users')
.page(2, 20)
.filter('role', 'admin')
.sort('createdAt', 'desc')
.search('john')
.fields(['id', 'name', 'email'])
.build();
console.log(url);
// https://api.example.com/users?page=2&limit=20&role=admin&sort=createdAt&order=desc&q=john&fields=id,name,email
Response Format Standards
// Consistent API Response Structure
// Success Response
{
"success": true,
"data": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
},
"meta": {
"timestamp": "2024-01-15T10:30:00Z"
}
}
// Collection Response with Pagination
{
"success": true,
"data": [
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
],
"pagination": {
"page": 1,
"limit": 10,
"total": 100,
"totalPages": 10,
"hasNext": true,
"hasPrev": false
}
}
// Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "age", "message": "Must be a positive number" }
]
}
}
💡 REST API Best Practices
- ✓ Use proper HTTP methods - GET for reading, POST for creating, etc.
- ✓ Return appropriate status codes - 201 for created, 404 for not found
- ✓ Version your API - Use /v1/, /v2/ in URLs
- ✓ Use JSON for data - Standard format for web APIs
- ✓ Implement pagination - Don't return unlimited results
- ✓ Document your API - Use OpenAPI/Swagger