Cấu trúc hệ thống web, các giao thức phổ biến và thực hành hiện đại
Frontend (trình duyệt) và Backend (máy chủ) cần giao tiếp để trao đổi dữ liệu. Frontend gửi request (yêu cầu), Backend xử lý và trả về response (phản hồi).
fetch(), axios để không block UI1. Trigger: User click button/submit form → 2. Request: Frontend gọi API → 3. Process: Backend xử lý → 4. Response: Trả dữ liệu → 5. Update: Frontend cập nhật UI
Giao tiếp dựa trên HTTP methods (GET/POST/PUT/PATCH/DELETE), dữ liệu thường ở dạng JSON. HTTP là cách trình duyệt gửi yêu cầu đến máy chủ và nhận phản hồi.
METHOD PATH HTTP/VERSION
/api/users/123?page=2)Header-Name: Header-Value
Content-Type: application/json
Authorization: Bearer token123
Key: Value, không phân biệt hoa thường cho key\r\n (Carriage Return + Line Feed)Content-Length headerHTTP/VERSION STATUS_CODE REASON_PHRASE
Content-Type: application/json
Set-Cookie: sessionId=abc123
Cache-Control: max-age=3600
| Code | Reason Phrase | Mô tả | Khi nào gặp |
|---|---|---|---|
| 📡 1xx - Informational (Thông tin trung gian) | |||
| 100 | Continue | Server đã nhận request headers, client có thể gửi tiếp body | Upload file lớn |
| 101 | Switching Protocols | Server đồng ý chuyển protocol (HTTP → WebSocket) | WebSocket handshake |
| ✅ 2xx - Success (Thành công) | |||
| 200 | OK | Request thành công, có response body | GET, POST thành công |
| 201 | Created | Tài nguyên mới đã được tạo thành công | POST tạo user/post |
| 204 | No Content | Thành công nhưng không có response body | DELETE thành công |
| 🔄 3xx - Redirection (Chuyển hướng) | |||
| 301 | Moved Permanently | Tài nguyên đã chuyển vĩnh viễn sang URL mới | Domain đổi, SEO redirect |
| 302 | Found | Tài nguyên tạm thời ở URL khác | Redirect sau login |
| 304 | Not Modified | Tài nguyên chưa thay đổi, dùng cache | Conditional requests |
| ❌ 4xx - Client Error (Lỗi phía client) | |||
| 400 | Bad Request | Request không đúng format hoặc thiếu data | JSON sai, validation fail |
| 401 | Unauthorized | Chưa xác thực hoặc token không hợp lệ | Thiếu/sai login |
| 403 | Forbidden | Đã login nhưng không có quyền truy cập | User role không đủ |
| 404 | Not Found | Không tìm thấy tài nguyên hoặc endpoint | URL sai, resource đã xóa |
| 409 | Conflict | Request xung đột với trạng thái hiện tại | Email đã tồn tại |
| 422 | Unprocessable Entity | Request đúng format nhưng có lỗi logic | Validation rules fail |
| 429 | Too Many Requests | Vượt quá rate limit | API limit, spam requests |
| 💥 5xx - Server Error (Lỗi phía server) | |||
| 500 | Internal Server Error | Lỗi không xác định trong server | Code crash, exception |
| 502 | Bad Gateway | Server nhận response không hợp lệ từ upstream | Load balancer, proxy issue |
| 503 | Service Unavailable | Server tạm thời không thể xử lý | Maintenance, overload |
| 504 | Gateway Timeout | Server không nhận được response kịp thời | Upstream timeout |
Authorization: Bearer <JWT_token>
Authorization: Basic <base64(username:password)>
Cookie: sessionId=abc123; userId=456; theme=dark
Set-Cookie: sessionId=xyz789; Path=/; HttpOnly; Secure; SameSite=Strict
Content-Type: application/json
Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Accept: application/json, text/html, */*;q=0.8
Content-Encoding: gzip, deflate, br
Cache-Control: no-cache
Cache-Control: max-age=3600
Cache-Control: private, must-revalidate
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-None-Match, server trả 304 nếu chưa thay đổi
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
If-Modified-Since để kiểm tra
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept-Language: en-US,en;q=0.9,vi;q=0.8,zh;q=0.7
Referer: https://example.com/page1
Origin: https://mydomain.com
Content-Type = content-type = CONTENT-TYPEAccept: text/html, application/json; q=0.9X- (vd: X-API-Key, X-Request-ID)📤 REQUEST:
GET /users?page=2&limit=5 HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Cache-Control: no-cache
[Dòng trống - không có body cho GET]
📤 REQUEST (POST):
POST /posts HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 67
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{
"title": "Bài học HTTP",
"body": "Học cách giao tiếp Frontend-Backend",
"userId": 17
}
📥 RESPONSE:
HTTP/1.1 201 Created
Content-Type: application/json
Location: /posts/101
Set-Cookie: sessionId=abc123; Path=/; HttpOnly
X-RateLimit-Remaining: 99
{
"id": 101,
"title": "Bài học HTTP",
"body": "Học cách giao tiếp Frontend-Backend",
"userId": 17,
"createdAt": "2025-08-09T10:30:00Z"
}
/users/123 → user có id=123
?page=2&search=alice
Authorization, Content-Type
HTTP Method (HTTP Verb) là động từ chỉ định hành động mà client muốn thực hiện với tài nguyên trên server. Nó giống như "động từ" trong câu: Subject + Verb + Object → Client + Method + Resource.
| Method | Mục đích | Dữ liệu ở đâu | Body | Idempotent | Ví dụ thực tế |
|---|---|---|---|---|---|
| GET | Lấy/đọc dữ liệu | URL (path + query) | ❌ | ✅ | Xem profile, danh sách sản phẩm, search |
| POST | Tạo mới/gửi dữ liệu | Body (JSON/Form) | ✅ | ❌ | Đăng ký user, tạo post, upload file |
| PUT | Thay thế toàn bộ | Body (full resource) | ✅ | ✅ | Cập nhật toàn bộ profile user |
| PATCH | Cập nhật một phần | Body (partial data) | ✅ | ⚠️ | Đổi avatar, update status |
| DELETE | Xoá tài nguyên | URL (resource ID) | ❌ | ✅ | Xoá post, huỷ order |
Mục đích: Đọc/lấy thông tin từ server
Dữ liệu: Trong URL (path + query string)
Đặc tính: Safe, Idempotent, Cacheable
HTTP Request:
GET /users/123?include=posts HTTP/1.1
Host: api.example.com
Accept: application/json
Mục đích: Tạo tài nguyên mới, gửi form
Dữ liệu: Trong Request Body (JSON/Form)
Đặc tính: Not Safe, Not Idempotent
HTTP Request:
POST /users HTTP/1.1
Content-Type: application/json
{"name":"John","email":"john@example.com"}
Mục đích: Thay thế/ghi đè tài nguyên
Dữ liệu: Toàn bộ resource trong Body
Đặc tính: Not Safe, Idempotent
HTTP Request:
PUT /users/123 HTTP/1.1
Content-Type: application/json
{"id":123,"name":"John Updated","email":"new@example.com"}
Mục đích: Sửa một vài thuộc tính
Dữ liệu: Chỉ các field thay đổi trong Body
Đặc tính: Not Safe, Not Idempotent (tuỳ)
HTTP Request:
PATCH /users/123 HTTP/1.1
Content-Type: application/json
{"name":"John Edited"}
Mục đích: Xoá tài nguyên
Dữ liệu: Resource ID trong URL
Đặc tính: Not Safe, Idempotent
HTTP Request:
DELETE /users/123 HTTP/1.1
Authorization: Bearer token...
Ví dụ đời thường: Nghĩ server như thư viện sách — GET: xem sách; POST: thêm sách mới; PUT: thay cả cuốn; PATCH: dán nhãn/sửa vài trang; DELETE: bỏ sách khỏi kệ.
Gọi API mất thời gian (network latency), nếu dùng đồng bộ sẽ block UI → trải nghiệm tệ. JavaScript có nhiều cách để gọi API bất đồng bộ.
Các cách gọi HTTP API: XMLHttpRequest (cũ), Fetch API (hiện đại), Axios (thư viện). Tất cả đều có thể gọi GET, POST, PUT, PATCH, DELETE.
// XMLHttpRequest GET
function xhrGetUser(userId, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', `https://jsonplaceholder.typicode.com/users/${userId}`, true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const data = JSON.parse(xhr.responseText);
callback(null, data);
} catch (e) {
callback(new Error('Invalid JSON: ' + e.message));
}
} else {
callback(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
}
};
xhr.onerror = () => callback(new Error('Network error'));
xhr.ontimeout = () => callback(new Error('Request timeout'));
xhr.timeout = 10000;
xhr.send();
}
// Sử dụng
xhrGetUser(1, (error, user) => {
if (error) console.error('Error:', error.message);
else console.log('User:', user);
});
// XMLHttpRequest POST
function xhrCreatePost(postData, callback) {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://jsonplaceholder.typicode.com/posts', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const data = JSON.parse(xhr.responseText);
callback(null, data);
} catch (e) {
callback(new Error('Invalid JSON: ' + e.message));
}
} else {
callback(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
}
};
xhr.onerror = () => callback(new Error('Network error'));
xhr.send(JSON.stringify(postData));
}
// Sử dụng
xhrCreatePost({
title: 'XMLHttpRequest Post',
body: 'Learning XHR in Bài 17',
userId: 1
}, (error, result) => {
if (error) console.error('Error:', error.message);
else console.log('Created post:', result);
});
.then() hoặc awaitres.json()res.ok// Fetch GET
async function fetchGetUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const users = await response.json();
console.log('Users:', users);
return users;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
// Với query parameters
async function fetchSearchUsers(query) {
const url = new URL('https://jsonplaceholder.typicode.com/users');
url.searchParams.set('search', query);
url.searchParams.set('limit', '10');
const response = await fetch(url);
if (!response.ok) throw new Error('HTTP ' + response.status);
return await response.json();
}
// Fetch POST
async function fetchCreatePost(postData) {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(postData)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
console.log('Created post:', result);
return result;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
// Fetch với timeout
async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
npm install axiosresponse.data đã được parse JSON tự động.ok như Fetch| Thuộc tính | Mục đích | Ví dụ giá trị |
|---|---|---|
baseURL |
URL gốc cho tất cả requests | 'https://api.example.com/v1' |
timeout |
Thời gian chờ tối đa (ms) | 5000, 10000, 30000 |
headers |
Headers mặc định cho mọi request | {'Authorization': 'Bearer token'} |
responseType |
Kiểu dữ liệu response mong muốn | 'json', 'text', 'blob', 'stream' |
withCredentials |
Gửi cookies cho cross-origin | true, false |
auth |
HTTP Basic Authentication | {username: 'user', password: 'pass'} |
validateStatus |
Function xác định status nào OK | status => status < 400 |
maxRedirects |
Số redirect tối đa | 5, 10 (default: 5) |
// 1. CÀI ĐẶT AXIOS
// Qua CDN (dễ nhất cho học tập)
// <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// Qua npm (cho dự án thực tế)
// npm install axios
// Import trong module
// import axios from 'axios';
// 2. SỬ DỤNG CƠ BẢN (không khuyến khích cho dự án lớn)
async function basicAxiosExample() {
try {
// GET request đơn giản
const response = await axios.get('https://jsonplaceholder.typicode.com/users/1');
console.log('User data:', response.data); // JSON đã được parse tự động
console.log('Status:', response.status); // 200
console.log('Headers:', response.headers);
// POST request đơn giản
const newUser = {
name: 'John Doe',
email: 'john@example.com'
};
const postResponse = await axios.post('https://jsonplaceholder.typicode.com/users', newUser);
console.log('Created:', postResponse.data);
} catch (error) {
// Axios tự động reject 4xx/5xx status codes
if (error.response) {
console.error('HTTP Error:', error.response.status);
console.error('Error data:', error.response.data);
} else if (error.request) {
console.error('Network error - no response received');
} else {
console.error('Request setup error:', error.message);
}
}
}
// 3. TẠO AXIOS INSTANCE (KHUYẾN KHÍCH)
const apiClient = axios.create({
// Base URL - sẽ được thêm vào trước mọi relative URLs
baseURL: 'https://jsonplaceholder.typicode.com',
// Timeout - tự động cancel request sau 10 giây
timeout: 10000,
// Headers mặc định cho mọi request
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'MyApp/1.0.0',
// 'Authorization': 'Bearer your-token-here' // Thêm nếu cần auth
},
// Response type mong muốn
responseType: 'json', // 'json' | 'text' | 'blob' | 'arraybuffer' | 'stream'
// Gửi cookies cho cross-origin requests
withCredentials: false, // true nếu cần cookies
// HTTP Basic Authentication
// auth: {
// username: 'your-username',
// password: 'your-password'
// },
// Validate status codes (mặc định: 200-299 là OK)
validateStatus: function (status) {
return status >= 200 && status < 300; // hoặc status < 400 để accept 3xx
},
// Transform request data trước khi gửi
transformRequest: [function (data, headers) {
// Có thể modify data ở đây
console.log('Transforming request data:', data);
return data;
}],
// Transform response data sau khi nhận
transformResponse: [function (data) {
// Có thể modify response ở đây
console.log('Transforming response data:', data);
return data;
}],
// Số lần redirect tối đa
maxRedirects: 5,
// Retry config (nếu dùng axios-retry plugin)
// retry: 3,
// retryDelay: 1000
});
// 4. SỬ DỤNG INSTANCE
async function useInstanceExample() {
try {
// GET với instance - baseURL tự động thêm vào
const users = await apiClient.get('/users'); // → GET https://jsonplaceholder.typicode.com/users
console.log('Users from instance:', users.data);
// GET với query parameters
const filteredUsers = await apiClient.get('/users', {
params: {
_limit: 5,
_sort: 'name'
}
});
// POST với instance
const newPost = await apiClient.post('/posts', {
title: 'My New Post',
body: 'This is the post content',
userId: 1
});
// PUT update
const updatedPost = await apiClient.put('/posts/1', {
id: 1,
title: 'Updated Title',
body: 'Updated content',
userId: 1
});
// PATCH partial update
const patchedPost = await apiClient.patch('/posts/1', {
title: 'Patched Title'
});
// DELETE
await apiClient.delete('/posts/1');
console.log('Post deleted successfully');
} catch (error) {
console.error('Instance request failed:', error);
}
}
// REQUEST INTERCEPTOR - chạy trước mỗi request
apiClient.interceptors.request.use(
function (config) {
console.log(`🚀 Request: ${config.method?.toUpperCase()} ${config.url}`);
// Thêm timestamp để track performance
config.metadata = { startTime: new Date() };
// Auto thêm auth token từ localStorage
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Thêm request ID cho tracking
config.headers['X-Request-ID'] = Math.random().toString(36).substr(2, 9);
// Log request details trong development
if (process.env.NODE_ENV === 'development') {
console.log('Request config:', config);
}
return config; // PHẢI return config
},
function (error) {
console.error('❌ Request interceptor error:', error);
return Promise.reject(error);
}
);
// RESPONSE INTERCEPTOR - chạy sau mỗi response
apiClient.interceptors.response.use(
function (response) {
// Tính response time
const endTime = new Date();
const duration = endTime - response.config.metadata.startTime;
console.log(`✅ Response: ${response.status} (${duration}ms)`);
// Log response trong development
if (process.env.NODE_ENV === 'development') {
console.log('Response data:', response.data);
}
// Có thể transform response data ở đây
if (response.data && response.data.data) {
// Unwrap nested data structure
response.data = response.data.data;
}
return response; // PHẢI return response
},
function (error) {
// Centralized error handling
if (error.response) {
const { status, data } = error.response;
// Handle specific status codes
switch (status) {
case 401:
console.error('🔒 Unauthorized - redirecting to login');
localStorage.removeItem('authToken');
window.location.href = '/login';
break;
case 403:
console.error('🚫 Forbidden - insufficient permissions');
alert('Bạn không có quyền thực hiện hành động này');
break;
case 404:
console.error('🔍 Not Found');
break;
case 422:
console.error('📝 Validation Error:', data.errors);
break;
case 429:
console.error('⏰ Rate Limited - too many requests');
alert('Quá nhiều requests, vui lòng chờ một chút');
break;
case 500:
console.error('💥 Server Error');
alert('Lỗi server, vui lòng thử lại sau');
break;
default:
console.error(`❌ HTTP ${status}:`, data);
}
} else if (error.request) {
console.error('🌐 Network Error - no response received');
alert('Lỗi kết nối mạng');
} else {
console.error('⚙️ Request Setup Error:', error.message);
}
return Promise.reject(error);
}
);
// MULTIPLE INSTANCES cho different APIs
const authAPI = axios.create({
baseURL: 'https://auth-service.com/api',
timeout: 5000
});
const userAPI = axios.create({
baseURL: 'https://user-service.com/api',
timeout: 8000
});
const fileAPI = axios.create({
baseURL: 'https://file-service.com/api',
timeout: 30000, // File upload cần timeout lâu hơn
headers: {
'Content-Type': 'multipart/form-data'
}
});
// API SERVICE CLASS pattern
class UserService {
constructor() {
this.api = axios.create({
baseURL: process.env.API_BASE_URL || 'https://api.example.com/v1',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
this.setupInterceptors();
}
setupInterceptors() {
// Request interceptor để thêm auth
this.api.interceptors.request.use(config => {
const token = this.getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor để handle auth errors
this.api.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
this.handleAuthError();
}
return Promise.reject(error);
}
);
}
// GET users với pagination và search
async getUsers(page = 1, limit = 10, search = '') {
try {
const response = await this.api.get('/users', {
params: { page, limit, search }
});
return {
users: response.data.data,
pagination: response.data.pagination
};
} catch (error) {
throw new Error(`Failed to fetch users: ${error.message}`);
}
}
// POST create user với validation
async createUser(userData) {
try {
const response = await this.api.post('/users', userData);
return response.data;
} catch (error) {
if (error.response?.status === 422) {
throw new Error(`Validation failed: ${error.response.data.message}`);
}
throw new Error(`Failed to create user: ${error.message}`);
}
}
// File upload với progress tracking
async uploadAvatar(userId, file, onProgress) {
try {
const formData = new FormData();
formData.append('avatar', file);
const response = await this.api.post(`/users/${userId}/avatar`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
onProgress?.(percentCompleted);
}
});
return response.data;
} catch (error) {
throw new Error(`Upload failed: ${error.message}`);
}
}
// Request với retry logic
async getUserWithRetry(userId, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await this.api.get(`/users/${userId}`);
return response.data;
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Wait before retry (exponential backoff)
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
console.log(`Retry attempt ${attempt + 1} for user ${userId}`);
}
}
}
getAuthToken() {
return localStorage.getItem('authToken');
}
handleAuthError() {
localStorage.removeItem('authToken');
window.location.href = '/login';
}
}
// ENVIRONMENT-SPECIFIC CONFIG
const createAPIClient = (environment) => {
const configs = {
development: {
baseURL: 'http://localhost:3000/api',
timeout: 30000, // Development có thể chậm hơn
headers: {
'X-Environment': 'development'
}
},
staging: {
baseURL: 'https://staging-api.example.com/v1',
timeout: 15000,
headers: {
'X-Environment': 'staging'
}
},
production: {
baseURL: 'https://api.example.com/v1',
timeout: 10000,
headers: {
'X-Environment': 'production'
}
}
};
return axios.create(configs[environment] || configs.production);
};
// Sử dụng
const userService = new UserService();
const apiClient = createAPIClient(process.env.NODE_ENV);
Kết nối 2 chiều, thời gian thực (real-time) cho chat, thông báo, dashboard, game. Khác với HTTP request-response, WebSocket duy trì kết nối liên tục.
// WebSocket Basic Usage
class WebSocketClient {
constructor(url) {
this.url = url;
this.socket = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect() {
try {
this.socket = new WebSocket(this.url);
this.socket.addEventListener('open', (event) => {
console.log('🔌 WebSocket Connected');
this.reconnectAttempts = 0;
this.onOpen(event);
});
this.socket.addEventListener('message', (event) => {
console.log('📨 Received:', event.data);
this.onMessage(event);
});
this.socket.addEventListener('close', (event) => {
console.log('❎ WebSocket Closed', event.code, event.reason);
this.onClose(event);
this.attemptReconnect();
});
this.socket.addEventListener('error', (event) => {
console.error('💥 WebSocket Error:', event);
this.onError(event);
});
} catch (error) {
console.error('Failed to create WebSocket:', error);
}
}
send(data) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(data));
} else {
console.warn('WebSocket not connected');
}
}
close() {
if (this.socket) {
this.socket.close();
}
}
attemptReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`Reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
setTimeout(() => this.connect(), 2000 * this.reconnectAttempts);
}
}
// Override these methods
onOpen(event) {}
onMessage(event) {}
onClose(event) {}
onError(event) {}
}
// Usage example
const wsClient = new WebSocketClient('wss://echo.websocket.events');
wsClient.onOpen = () => {
wsClient.send({ type: 'greeting', message: 'Hello from Bài 17!' });
};
wsClient.onMessage = (event) => {
const data = JSON.parse(event.data);
console.log('Message type:', data.type);
console.log('Content:', data.message);
};
wsClient.connect();
Client định nghĩa dữ liệu cần lấy. Một endpoint duy nhất, query linh hoạt, tránh over/under-fetching. GraphQL vẫn dùng HTTP nhưng chỉ POST method.
// GraphQL Query Function
async function graphqlQuery(query, variables = {}) {
try {
const response = await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
query,
variables
})
});
const result = await response.json();
if (result.errors) {
throw new Error('GraphQL errors: ' + JSON.stringify(result.errors));
}
return result.data;
} catch (error) {
console.error('GraphQL query failed:', error);
throw error;
}
}
// Example queries
const GET_USER = `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
createdAt
}
}
}
`;
const CREATE_POST = `
mutation CreatePost($input: PostInput!) {
createPost(input: $input) {
id
title
body
author {
name
}
}
}
`;
// Usage examples
async function getUserWithPosts(userId) {
const data = await graphqlQuery(GET_USER, { id: userId });
console.log('User:', data.user);
return data.user;
}
async function createNewPost(postInput) {
const data = await graphqlQuery(CREATE_POST, { input: postInput });
console.log('Created post:', data.createPost);
return data.createPost;
}
// GraphQL with variables
getUserWithPosts('1');
createNewPost({
title: 'Learning GraphQL',
body: 'GraphQL is powerful for flexible API queries',
authorId: '1'
});
Mỗi kiểu giao tiếp có ưu điểm và use case riêng. Chọn dựa trên yêu cầu dự án, tính chất dữ liệu, và khả năng team.
| Kiểu giao tiếp | Đặc điểm chính | Ưu điểm | Nhược điểm | Khi nào dùng |
|---|---|---|---|---|
| REST API | HTTP methods, JSON, stateless | Đơn giản, phổ biến, cache tốt, RESTful | Over/under-fetching, nhiều request | CRUD apps, web services tiêu chuẩn |
| WebSocket | Kết nối 2 chiều, real-time | Đẩy dữ liệu tức thời, ít latency | Phức tạp, giữ kết nối, resource-heavy | Chat, gaming, live dashboard |
| GraphQL | Query linh hoạt, 1 endpoint | Tối ưu băng thông, strongly typed | Caching khó, learning curve cao | Complex queries, mobile apps |
Gọi API mất thời gian (network latency), nếu dùng đồng bộ sẽ block UI → trải nghiệm tệ. JavaScript dùng Promise và async/await để xử lý bất đồng bộ.
Promiseonreadystatechangefunction xhrGet(url, callback) {
// Tạo XMLHttpRequest object
const xhr = new XMLHttpRequest();
// Cấu hình request
xhr.open('GET', url, true); // true = asynchronous
// Set headers (nếu cần)
xhr.setRequestHeader('Accept', 'application/json');
xhr.setRequestHeader('User-Agent', 'MyApp/1.0');
// Event handler cho thay đổi trạng thái
xhr.onreadystatechange = function() {
// readyState: 0=UNSENT, 1=OPENED, 2=HEADERS_RECEIVED, 3=LOADING, 4=DONE
if (xhr.readyState === 4) { // DONE = hoàn thành
if (xhr.status >= 200 && xhr.status < 300) {
// Success
try {
const data = JSON.parse(xhr.responseText);
callback(null, data);
} catch (e) {
callback(new Error('Invalid JSON: ' + e.message));
}
} else {
// HTTP error
callback(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
}
};
// Error handler (network errors)
xhr.onerror = function() {
callback(new Error('Network error'));
};
// Timeout handler
xhr.ontimeout = function() {
callback(new Error('Request timeout'));
};
// Set timeout (optional)
xhr.timeout = 10000; // 10 seconds
// Gửi request
xhr.send(); // Không có body cho GET
}
// Sử dụng với callback
xhrGet('https://jsonplaceholder.typicode.com/users/1', (error, data) => {
if (error) {
console.error('Error:', error.message);
} else {
console.log('User:', data);
}
});
// Wrap XMLHttpRequest thành Promise để dễ sử dụng hơn
function xhrPromise(method, url, data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method.toUpperCase(), url, true);
// Set headers cho JSON
if (data && typeof data === 'object') {
xhr.setRequestHeader('Content-Type', 'application/json');
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
const response = {
status: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders(),
data: null
};
// Parse response
try {
response.data = xhr.responseText ? JSON.parse(xhr.responseText) : null;
} catch (e) {
response.data = xhr.responseText;
}
if (xhr.status >= 200 && xhr.status < 300) {
resolve(response);
} else {
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
}
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.ontimeout = () => reject(new Error('Timeout'));
xhr.timeout = 10000;
// Gửi data
const body = data ? JSON.stringify(data) : null;
xhr.send(body);
});
}
// Sử dụng như Promise (có thể dùng async/await!)
async function testXhrPromise() {
try {
// GET request
const getResponse = await xhrPromise('GET', 'https://jsonplaceholder.typicode.com/posts/1');
console.log('GET result:', getResponse.data);
// POST request
const postResponse = await xhrPromise('POST', 'https://jsonplaceholder.typicode.com/posts', {
title: 'XMLHttpRequest test',
body: 'Learning old-school AJAX',
userId: 1
});
console.log('POST result:', postResponse.data);
} catch (error) {
console.error('XHR Error:', error.message);
}
}
// Alternative: sử dụng .then()
xhrPromise('GET', 'https://jsonplaceholder.typicode.com/users?_limit=3')
.then(response => console.log('Users:', response.data))
.catch(error => console.error('Error:', error.message));
Kết nối 2 chiều, thời gian thực (real-time) cho chat, thông báo, dashboard, game.
// Kết nối tới echo server công khai
const socket = new WebSocket('wss://echo.websocket.events');
socket.addEventListener('open', () => {
console.log('🔌 Opened');
socket.send('Hello WS');
});
socket.addEventListener('message', (ev) => {
console.log('📨', ev.data);
});
socket.addEventListener('close', () => console.log('❎ Closed'));
Client định nghĩa dữ liệu cần lấy. Một endpoint duy nhất, query linh hoạt, tránh over/under-fetching.
async function queryGraphQL() {
const query = `query($id: ID!) { user(id: $id) { id name email posts { id title } } }`;
const res = await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables: { id: '1' } })
});
const data = await res.json();
console.log(data);
}