Master REST API principles and patterns. Learn resource modeling, CRUD operations, status codes, and how to build robust API client architectures.
REST (Representational State Transfer) is an architectural style that defines constraints for creating web services. It's the foundation of modern web APIs, providing a standardized approach to building scalable, maintainable, and efficient web services.
REST (Representational State Transfer) is an architectural style that defines key constraints for building web services. Understanding these principles is crucial for effective API design and integration.
The REST constraints:
1. Client-Server Architecture:
2. Stateless Communication:
3. Uniform Interface:
Resource-Oriented Design:
1// Good REST API design
2GET /api/users // Get all users
3GET /api/users/123 // Get specific user
4POST /api/users // Create new user
5PUT /api/users/123 // Update entire user
6PATCH /api/users/123 // Partial update
7DELETE /api/users/123 // Delete user
8
9// Nested resources
10GET /api/users/123/posts // Get user's posts
11POST /api/users/123/posts // Create post for userURI Design Best Practices:
CRUD (Create, Read, Update, Delete) operations map directly to HTTP methods in REST APIs. Understanding this mapping is essential for effective API integration.
HTTP Methods and Their Purpose:
GET - Read/Retrieve Data:
1// Get all users
2async function getUsers() {
3 const response = await fetch('/api/users');
4 if (!response.ok) {
5 throw new Error('Failed to fetch users');
6 }
7 return await response.json();
8}
9
10// Get specific user
11async function getUser(userId) {
12 const response = await fetch('/api/users/' + userId);
13 if (!response.ok) {
14 if (response.status === 404) {
15 throw new Error('User not found');
16 }
17 throw new Error('Failed to fetch user');
18 }
19 return await response.json();
20}POST - Create New Resources:
1// Create new user
2async function createUser(userData) {
3 const response = await fetch('/api/users', {
4 method: 'POST',
5 headers: {
6 'Content-Type': 'application/json',
7 },
8 body: JSON.stringify(userData)
9 });
10
11 if (!response.ok) {
12 const error = await response.json();
13 throw new Error(error.message || 'Failed to create user');
14 }
15
16 return await response.json();
17}
18
19// Usage
20const newUser = await createUser({
21 name: 'John Doe',
22 email: '[email protected]',
23 role: 'user'
24});PUT - Update Entire Resource:
1// Update entire user (replaces all fields)
2async function updateUser(userId, userData) {
3 const response = await fetch('/api/users/' + userId, {
4 method: 'PUT',
5 headers: {
6 'Content-Type': 'application/json',
7 },
8 body: JSON.stringify(userData)
9 });
10
11 if (!response.ok) {
12 throw new Error('Failed to update user');
13 }
14
15 return await response.json();
16}PATCH - Partial Update:
1// Update specific fields only
2async function updateUserEmail(userId, newEmail) {
3 const response = await fetch('/api/users/' + userId, {
4 method: 'PATCH',
5 headers: {
6 'Content-Type': 'application/json',
7 },
8 body: JSON.stringify({
9 email: newEmail
10 })
11 });
12
13 if (!response.ok) {
14 throw new Error('Failed to update user email');
15 }
16
17 return await response.json();
18}DELETE - Remove Resources:
1// Delete user
2async function deleteUser(userId) {
3 const response = await fetch('/api/users/' + userId, {
4 method: 'DELETE'
5 });
6
7 if (!response.ok) {
8 throw new Error('Failed to delete user');
9 }
10
11 // DELETE typically returns 204 No Content
12 return response.status === 204;
13}Complete CRUD API Client:
1class UserAPIClient {
2 constructor(baseURL) {
3 this.baseURL = baseURL;
4 }
5
6 async request(endpoint, options = {}) {
7 const url = this.baseURL + endpoint;
8 const config = {
9 headers: {
10 'Content-Type': 'application/json',
11 ...options.headers
12 },
13 ...options
14 };
15
16 const response = await fetch(url, config);
17
18 if (!response.ok) {
19 throw new Error('HTTP ' + response.status + ': ' + response.statusText);
20 }
21
22 // Handle empty responses (e.g., 204 No Content)
23 if (response.status === 204) {
24 return null;
25 }
26
27 return await response.json();
28 }
29
30 // Create
31 async create(userData) {
32 return this.request('/users', {
33 method: 'POST',
34 body: JSON.stringify(userData)
35 });
36 }
37
38 // Read
39 async getAll() {
40 return this.request('/users');
41 }
42
43 async getById(id) {
44 return this.request('/users/' + id);
45 }
46
47 // Update
48 async update(id, userData) {
49 return this.request('/users/' + id, {
50 method: 'PUT',
51 body: JSON.stringify(userData)
52 });
53 }
54
55 async partialUpdate(id, userData) {
56 return this.request('/users/' + id, {
57 method: 'PATCH',
58 body: JSON.stringify(userData)
59 });
60 }
61
62 // Delete
63 async delete(id) {
64 return this.request('/users/' + id, {
65 method: 'DELETE'
66 });
67 }
68}
69
70// Usage
71const userAPI = new UserAPIClient('/api');
72
73// Create user
74const newUser = await userAPI.create({
75 name: 'Jane Smith',
76 email: '[email protected]'
77});
78
79// Read users
80const users = await userAPI.getAll();
81const user = await userAPI.getById(123);
82
83// Update user
84await userAPI.update(123, { name: 'Jane Johnson', email: '[email protected]' });
85await userAPI.partialUpdate(123, { email: '[email protected]' });
86
87// Delete user
88await userAPI.delete(123);Real-world APIs require sophisticated patterns for pagination, filtering, sorting, and error handling. These patterns ensure APIs remain performant and usable at scale.
Pagination:
1// Cursor-based pagination
2async function getUsers(cursor = null, limit = 20) {
3 const params = new URLSearchParams({
4 limit: limit.toString()
5 });
6
7 if (cursor) {
8 params.append('cursor', cursor);
9 }
10
11 const response = await fetch('/api/users?' + params);
12 const data = await response.json();
13
14 return {
15 users: data.users,
16 nextCursor: data.nextCursor,
17 hasMore: data.hasMore
18 };
19}
20
21// Offset-based pagination
22async function getUsersPage(page = 1, pageSize = 20) {
23 const params = new URLSearchParams({
24 page: page.toString(),
25 pageSize: pageSize.toString()
26 });
27
28 const response = await fetch('/api/users?' + params);
29 const data = await response.json();
30
31 return {
32 users: data.users,
33 totalCount: data.totalCount,
34 currentPage: data.currentPage,
35 totalPages: data.totalPages
36 };
37}Filtering and Sorting:
1// Advanced filtering
2async function searchUsers(filters = {}) {
3 const params = new URLSearchParams();
4
5 // Add filters
6 if (filters.name) {
7 params.append('name', filters.name);
8 }
9 if (filters.role) {
10 params.append('role', filters.role);
11 }
12 if (filters.isActive !== undefined) {
13 params.append('isActive', filters.isActive.toString());
14 }
15
16 // Add sorting
17 if (filters.sortBy) {
18 params.append('sortBy', filters.sortBy);
19 params.append('sortOrder', filters.sortOrder || 'asc');
20 }
21
22 const response = await fetch('/api/users?' + params);
23 return await response.json();
24}
25
26// Usage
27const activeUsers = await searchUsers({
28 isActive: true,
29 role: 'admin',
30 sortBy: 'name',
31 sortOrder: 'asc'
32});API Versioning:
1class VersionedAPIClient {
2 constructor(baseURL, version = 'v1') {
3 this.baseURL = baseURL;
4 this.version = version;
5 }
6
7 getEndpoint(path) {
8 return this.baseURL + '/api/' + this.version + path;
9 }
10
11 async request(path, options = {}) {
12 const url = this.getEndpoint(path);
13 const config = {
14 headers: {
15 'Accept': 'application/vnd.api+json',
16 'Content-Type': 'application/json',
17 ...options.headers
18 },
19 ...options
20 };
21
22 const response = await fetch(url, config);
23 return this.handleResponse(response);
24 }
25
26 async handleResponse(response) {
27 if (!response.ok) {
28 const error = await response.json();
29 throw new APIError(error.message, response.status, error);
30 }
31
32 return await response.json();
33 }
34}
35
36// Usage with different versions
37const apiV1 = new VersionedAPIClient('https://api.example.com', 'v1');
38const apiV2 = new VersionedAPIClient('https://api.example.com', 'v2');Error Handling Patterns:
1class APIError extends Error {
2 constructor(message, status, details = null) {
3 super(message);
4 this.name = 'APIError';
5 this.status = status;
6 this.details = details;
7 }
8}
9
10class RobustAPIClient {
11 constructor(baseURL, options = {}) {
12 this.baseURL = baseURL;
13 this.defaultHeaders = options.headers || {};
14 this.timeout = options.timeout || 10000;
15 }
16
17 async request(endpoint, options = {}) {
18 const controller = new AbortController();
19 const timeoutId = setTimeout(() => controller.abort(), this.timeout);
20
21 try {
22 const response = await fetch(this.baseURL + endpoint, {
23 ...options,
24 headers: {
25 ...this.defaultHeaders,
26 ...options.headers
27 },
28 signal: controller.signal
29 });
30
31 clearTimeout(timeoutId);
32 return await this.handleResponse(response);
33
34 } catch (error) {
35 clearTimeout(timeoutId);
36
37 if (error.name === 'AbortError') {
38 throw new APIError('Request timeout', 408);
39 }
40
41 if (error.name === 'TypeError') {
42 throw new APIError('Network error', 0, error);
43 }
44
45 throw error;
46 }
47 }
48
49 async handleResponse(response) {
50 // Handle different status codes
51 if (response.ok) {
52 // Success responses (200-299)
53 return response.status === 204 ? null : await response.json();
54 }
55
56 // Client errors (400-499)
57 if (response.status >= 400 && response.status < 500) {
58 let errorData;
59 try {
60 errorData = await response.json();
61 } catch {
62 errorData = { message: response.statusText };
63 }
64
65 switch (response.status) {
66 case 400:
67 throw new APIError('Bad Request: ' + errorData.message, 400, errorData);
68 case 401:
69 throw new APIError('Unauthorized: Authentication required', 401, errorData);
70 case 403:
71 throw new APIError('Forbidden: Access denied', 403, errorData);
72 case 404:
73 throw new APIError('Not Found: Resource does not exist', 404, errorData);
74 case 422:
75 throw new APIError('Validation Error: ' + errorData.message, 422, errorData);
76 default:
77 throw new APIError('Client Error: ' + errorData.message, response.status, errorData);
78 }
79 }
80
81 // Server errors (500-599)
82 if (response.status >= 500) {
83 throw new APIError('Server Error: Please try again later', response.status);
84 }
85
86 // Other status codes
87 throw new APIError('Unexpected response status: ' + response.status, response.status);
88 }
89
90 // Retry logic for failed requests
91 async requestWithRetry(endpoint, options = {}, maxRetries = 3) {
92 for (let attempt = 1; attempt <= maxRetries; attempt++) {
93 try {
94 return await this.request(endpoint, options);
95 } catch (error) {
96 // Don't retry client errors (4xx)
97 if (error.status >= 400 && error.status < 500) {
98 throw error;
99 }
100
101 // Retry server errors and network errors
102 if (attempt === maxRetries) {
103 throw error;
104 }
105
106 // Exponential backoff
107 const delay = Math.pow(2, attempt - 1) * 1000;
108 await new Promise(resolve => setTimeout(resolve, delay));
109 }
110 }
111 }
112}Your progress is saved in this browser, on this device only.