🌐 Mô Hình & Giao Tiếp Frontend ↔ Backend

Cấu trúc hệ thống web, các giao thức phổ biến và thực hành hiện đại

1. 🏗️ Tổng quan kiến trúc Website

Kiến trúc 3 tầng

🎨 Presentation (Frontend)
⬇️
⚙️ Business (Backend)
⬇️
💾 Data (Database)

JavaScript ở đâu?

  • Frontend: DOM, sự kiện, gọi API, hiển thị dữ liệu động
  • Backend (Node.js): Xử lý request, truy vấn DB, business logic, trả response

2. 🔄 Kiểu giao tiếp giữa Frontend và Backend

🤝 Giao tiếp Frontend - Backend là gì?

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).

  • Frontend gửi: Yêu cầu lấy dữ liệu, gửi form, upload file, đăng nhập...
  • Backend trả về: Dữ liệu JSON, HTML, file, status code (200/404/500...)
  • Bất đồng bộ: Dùng fetch(), axios để không block UI
  • Định dạng phổ biến: JSON, XML, FormData, Binary

🌊 Luồng giao tiếp cơ bản

1. 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

🌐 HTTP/REST API

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.

🔬 Cấu trúc chung của HTTP: Request và Response

📤 HTTP Request gồm có:

1️⃣ Request Line (dòng đầu tiên)
METHOD PATH HTTP/VERSION
  • METHOD: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE
  • PATH: Đường dẫn tài nguyên (ví dụ: /api/users/123?page=2)
  • VERSION: HTTP/1.0, HTTP/1.1, HTTP/2.0, HTTP/3.0
2️⃣ Headers (metadata)
Header-Name: Header-Value
Content-Type: application/json
Authorization: Bearer token123
  • Format: Key: Value, không phân biệt hoa thường cho key
  • Số lượng: 0 đến nhiều headers
  • Phổ biến: Host, User-Agent, Accept, Content-Type, Authorization, Cookie
3️⃣ Dòng trống (CRLF)
  • Bắt buộc: Phân cách headers và body
  • Format: \r\n (Carriage Return + Line Feed)
4️⃣ Body (tuỳ chọn)
  • Có body: POST, PUT, PATCH (thường)
  • Không body: GET, DELETE, HEAD (thường)
  • Kiểu dữ liệu: JSON, XML, Form data, Binary, Text
  • Kích thước: Xác định bởi Content-Length header

📥 HTTP Response gồm có:

1️⃣ Status Line (dòng đầu tiên)
HTTP/VERSION STATUS_CODE REASON_PHRASE
  • VERSION: HTTP/1.0, HTTP/1.1, HTTP/2.0
  • STATUS_CODE: 100-599 (3 chữ số)
  • REASON_PHRASE: Mô tả ngắn (OK, Not Found, Internal Server Error...)
2️⃣ Response Headers
Content-Type: application/json
Set-Cookie: sessionId=abc123
Cache-Control: max-age=3600
  • Format: Giống request headers
  • Phổ biến: Content-Type, Content-Length, Set-Cookie, Cache-Control, Location
  • Tác dụng: Chỉ dẫn cách xử lý response body
3️⃣ Dòng trống (CRLF)
  • Bắt buộc: Phân cách headers và body
  • Ý nghĩa: Kết thúc phần headers, bắt đầu body
4️⃣ Response Body
  • Có body: 200 OK (thường), 201 Created, 400 Bad Request...
  • Không body: 204 No Content, 304 Not Modified, HEAD requests
  • Kiểu dữ liệu: JSON, HTML, CSS, JS, images, videos...
  • Encoding: gzip, deflate, br (Brotli) để nén

🔍 Giải thích chi tiết các thành phần:

📡 HTTP Methods (Miền giá trị)
  • Safe methods: GET, HEAD, OPTIONS, TRACE (không thay đổi dữ liệu server)
  • Idempotent methods: GET, HEAD, PUT, DELETE, OPTIONS, TRACE
  • Non-idempotent: POST, PATCH (có thể tạo side effects khác nhau)
🎯 HTTP Status Codes (100-599)
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
📋 Headers chi tiết - Ý nghĩa và mục đích
🔐 Authentication & Security Headers
Authorization - Xác thực người dùng
  • Authorization: Bearer <JWT_token>
    Ý nghĩa: Gửi JWT token để xác thực. Bearer = "người mang token"
    Mục đích: Xác minh user đã đăng nhập, không cần username/password mỗi lần
  • Authorization: Basic <base64(username:password)>
    Ý nghĩa: Mã hoá username:password thành base64
    Mục đích: Xác thực đơn giản, thường dùng cho API internal hoặc admin tools
Cookie & Set-Cookie - Quản lý session
  • Cookie: sessionId=abc123; userId=456; theme=dark
    Ý nghĩa: Client gửi cookies đã lưu về server
    Mục đích: Duy trì trạng thái đăng nhập, preferences, shopping cart
  • Set-Cookie: sessionId=xyz789; Path=/; HttpOnly; Secure; SameSite=Strict
    Ý nghĩa: Server yêu cầu browser lưu cookie mới
    Thuộc tính: Path (phạm vi), HttpOnly (chống XSS), Secure (chỉ HTTPS), SameSite (chống CSRF)
📄 Content Type & Encoding Headers
Content-Type - Định dạng dữ liệu
  • Content-Type: application/json
    Ý nghĩa: Body chứa dữ liệu JSON
    Mục đích: Server biết cách parse body, client biết cách xử lý response
  • Content-Type: text/html; charset=utf-8
    Ý nghĩa: Body là HTML với encoding UTF-8
    Mục đích: Browser hiển thị đúng, hỗ trợ ký tự Unicode (tiếng Việt, emoji)
  • Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
    Ý nghĩa: Form có file upload
    Mục đích: Gửi file + text data trong cùng 1 request
Accept & Content-Encoding - Thương lượng định dạng
  • Accept: application/json, text/html, */*;q=0.8
    Ý nghĩa: Client chấp nhận JSON (ưu tiên), HTML, hoặc bất kỳ (quality=0.8)
    Mục đích: Server chọn format phù hợp để trả về
  • Content-Encoding: gzip, deflate, br
    Ý nghĩa: Response đã được nén bằng gzip/deflate/brotli
    Mục đích: Giảm băng thông, tăng tốc tải trang (file JSON 100KB → 20KB)
💾 Caching Headers
Cache-Control - Điều khiển cache
  • Cache-Control: no-cache
    Ý nghĩa: Luôn kiểm tra với server trước khi dùng cache
    Mục đích: Đảm bảo dữ liệu luôn fresh (user profile, balance)
  • Cache-Control: max-age=3600
    Ý nghĩa: Cache trong 3600 giây (1 giờ)
    Mục đích: Tăng tốc, giảm tải server cho dữ liệu ít thay đổi (CSS, images)
  • Cache-Control: private, must-revalidate
    Ý nghĩa: Chỉ cache ở browser (không proxy), phải validate lại
    Mục đích: Bảo mật dữ liệu cá nhân, đảm bảo tính chính xác
ETag & Last-Modified - Conditional requests
  • ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
    Ý nghĩa: Fingerprint của nội dung (hash MD5/SHA)
    Mục đích: Client gửi If-None-Match, server trả 304 nếu chưa thay đổi
  • Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
    Ý nghĩa: Thời điểm file/resource được sửa lần cuối
    Mục đích: Client gửi If-Modified-Since để kiểm tra
🌐 Client Information Headers
Browser & Device Info
  • User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
    Ý nghĩa: Thông tin browser, OS, device của client
    Mục đích: Server serve content phù hợp (mobile/desktop), analytics, security
  • Accept-Language: en-US,en;q=0.9,vi;q=0.8,zh;q=0.7
    Ý nghĩa: Ngôn ngữ ưu tiên (quality factor q=0.9 nghĩa là 90% preference)
    Mục đích: Server trả content đa ngôn ngữ, localization
Navigation Context
  • Referer: https://example.com/page1
    Ý nghĩa: URL của trang trước đó (user click link từ đâu)
    Mục đích: Analytics (traffic source), security (check valid navigation), UX
  • Origin: https://mydomain.com
    Ý nghĩa: Domain gốc của client (cho CORS)
    Mục đích: Server kiểm tra có cho phép cross-origin request không
💡 Lưu ý quan trọng:
  • Case-insensitive: Content-Type = content-type = CONTENT-TYPE
  • Multiple values: Accept: text/html, application/json; q=0.9
  • Custom headers: Thường bắt đầu bằng X- (vd: X-API-Key, X-Request-ID)
  • Security: Không bao giờ để thông tin nhạy cảm trong headers có thể log được
📦 Body Content Types
application/json → {"name": "John", "age": 30}
application/x-www-form-urlencoded → name=John&age=30
multipart/form-data → File uploads, form with files
text/plain → Raw text data
application/xml → <user><name>John</name></user>
application/octet-stream → Binary data (files, images...)
📋 Ví dụ Request/Response thực tế
http
📤 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"
}

🎯 Dữ liệu đi đâu khi gọi API?

🔗 URL Path
Xác định tài nguyên cụ thể
/users/123 → user có id=123
❓ Query String
Tham số lọc/tìm kiếm
?page=2&search=alice
📋 Headers
Metadata, xác thực, cấu hình
Authorization, Content-Type
📦 Body
Dữ liệu chính gửi lên
JSON, Form, File (POST/PUT/PATCH)

📡 HTTP Methods - Các phương thức HTTP

🤔 HTTP Method là gì?

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.

  • Ví dụ: GET /users → "Lấy danh sách users"
  • RESTful: Cùng 1 URL nhưng method khác nhau → hành động khác nhau
  • Semantic: Mỗi method có ý nghĩa và đặc tính riêng
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
📖 GET - Lấy dữ liệu

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
➕ POST - Tạo mới

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"}
🔄 PUT - Thay thế toàn bộ

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"}
✏️ PATCH - Cập nhật một phần

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"}
🗑️ DELETE - Xoá

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...

🧠 Mẹo nhớ HTTP Methods:

  • CRUD Mapping: Create→POST, Read→GET, Update→PUT/PATCH, Delete→DELETE
  • Idempotent: Gọi nhiều lần = gọi 1 lần (GET, PUT, DELETE). POST thì không!
  • Safe: Không thay đổi dữ liệu server (chỉ GET)
  • PUT vs PATCH: PUT = thay cả bánh xe, PATCH = vá lốp xe

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ệ.

📚 Thuật ngữ quan trọng:
  • Safe: Không thay đổi dữ liệu server (chỉ GET)
  • Idempotent: Gọi nhiều lần = kết quả như gọi 1 lần (GET, PUT, DELETE)
  • Cacheable: Có thể cache kết quả (thường GET)

3. 🛠️ Kỹ thuật gọi API

🔄 Tại sao cần bất đồng bộ?

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ộ.

  • Promise-based: Fetch, Axios (modern)
  • Callback-based: XMLHttpRequest (legacy)
  • Event-driven: WebSocket (real-time)
  • Query-based: GraphQL (flexible)

🌐 HTTP/REST API Calling Techniques

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 (XHR) - Cách truyền thống

XMLHttpRequest là gì?
  • API gốc của browser để gọi HTTP, từ thời IE5 (1999)
  • Event-driven → dùng callback/event listener
  • Callback hell khi xử lý nhiều request liên tiếp
  • Tương thích tốt với browser cũ
  • Verbose → cần nhiều code cho một request đơn giản
  • Hỗ trợ progress, upload monitoring, synchronous request
XMLHttpRequest GET example
javascript
// 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 example
javascript
// 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);
});

🌐 Fetch API (Native JavaScript)

Fetch là gì?
  • Hàm native của browser, thay thế XMLHttpRequest
  • Trả về Promise → dùng .then() hoặc await
  • Không tự động parse JSON → phải gọi res.json()
  • Không auto-reject HTTP 4xx/5xx → phải check res.ok
  • Hỗ trợ AbortController để hủy request
Fetch GET example
javascript
// 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 example
javascript
// 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;
  }
}

📦 Axios (Third-party Library)

Axios là gì?
  • Thư viện HTTP client phổ biến nhất cho JavaScript, cần cài đặt: npm install axios
  • Promise-based với response.data đã được parse JSON tự động
  • Auto-reject HTTP 4xx/5xx → không cần check .ok như Fetch
  • Rich features: Interceptors, timeout, retry, automatic transforms
  • Better error handling với detailed error objects
  • Request/Response interceptors - middleware pattern
  • TypeScript support tốt với type definitions
🤔 Tại sao dùng Axios Instance thay vì gọi trực tiếp?
  • Tái sử dụng config: Không cần lặp lại baseURL, headers cho mỗi request
  • Centralized configuration: 1 nơi quản lý timeout, auth, headers
  • Multiple API services: Tạo instance riêng cho từng API (auth, users, products)
  • Interceptors per instance: Mỗi instance có interceptors riêng
  • Environment-specific: Dev/staging/prod có config khác nhau
  • Team collaboration: Standardized API calls across team
⚙️ Các thuộc tính Config quan trọng:
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)
Axios Setup và Config chi tiết
javascript
// 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);
  }
}
Interceptors - Middleware Pattern
javascript
// 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'
  }
});
Real-world Usage Patterns
javascript
// 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);
✅ Lợi ích cụ thể khi dùng Axios Instance:
🔧 Technical Benefits:
  • Code DRY: Không lặp lại config
  • Centralized auth: Token tự động thêm
  • Error handling: Xử lý lỗi tập trung
  • Request logging: Track performance
  • Response transformation: Auto format data
🚀 Business Benefits:
  • Development speed: Faster coding
  • Maintenance: Easier to update
  • Consistency: Standardized patterns
  • Team collaboration: Shared conventions
  • Testing: Easier to mock/stub
💡 Kết luận: Axios Instance không chỉ là "best practice" mà là NECESSARY cho mọi dự án có quy mô. Nó giúp code sạch hơn, dễ maintain hơn, và team work hiệu quả hơn.

🚀 Demo: Lợi ích của việc dùng Axios Instance


          

⚡ WebSocket Real-time Communication

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 Client Implementation
javascript
// 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();
⚠️ Lưu ý WebSocket:
  • Không thay thế HTTP: Dùng cho real-time data push, không phải CRUD operations
  • Connection management: Cần xử lý reconnection, heartbeat
  • Firewall/Proxy: Có thể bị block bởi network infrastructure
  • Resource usage: Giữ kết nối liên tục, tốn tài nguyên server

🔍 GraphQL Query Language

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 Examples
javascript
// 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'
});
🎯 GraphQL vs REST:
  • Flexibility: Client chỉ định fields cần thiết
  • Single endpoint: /graphql thay vì nhiều REST endpoints
  • Type system: Strongly typed, self-documenting
  • Real-time: Subscriptions cho live data
  • Trade-offs: Phức tạp hơn REST, caching khó hơn

4. 📊 So sánh các kiểu giao tiếp

🎯 Chọn công nghệ phù hợp

Mỗi kiểu giao tiếp có ưu điểmuse 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

🏆 Khuyến nghị cho người mới

  • Bắt đầu với REST API + Fetch để học cơ bản
  • Thêm Axios khi cần tính năng nâng cao (interceptors, timeout)
  • WebSocket cho features real-time đơn giản (chat)
  • GraphQL sau khi thành thạo REST và có nhu cầu tối ưu

5. 🔧 Chi tiết kỹ thuật & Ví dụ nâng cao

🔄 Tại sao cần hàm bất đồng bộ?

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 Promiseasync/await để xử lý bất đồng bộ.

  • Fetch & Axios đều trả về Promise
  • Promise có 3 trạng thái: pending → fulfilled/rejected
  • async/await giúp viết code như đồng bộ nhưng thực chất bất đồng bộ
🏛️ XMLHttpRequest (Legacy, nền tảng của AJAX)
XMLHttpRequest là gì?
  • API cũ (2000s) nhưng vẫn hoạt động, nền tảng của AJAX
  • Event-driven → dùng callback onreadystatechange
  • Không có Promise → phải tự wrap hoặc dùng callback
  • Verbose → nhiều dòng code hơn Fetch/Axios
  • Tương thích tốt với browser cũ (IE5.5+)
  • Hiểu XMLHttpRequest giúp debug và hiểu cách Fetch hoạt động
XMLHttpRequest cơ bản (callback style)
javascript
function 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);
  }
});
XMLHttpRequest POST + Promise wrapper
javascript
// 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));

🔍 XMLHttpRequest vs Fetch/Axios

  • Callbacks vs Promises: XHR dùng events/callbacks, Fetch/Axios dùng Promises
  • Syntax: XHR dài dòng, Fetch/Axios ngắn gọn hơn
  • Error handling: XHR cần check readyState + status manually
  • Browser support: XHR hoạt động trên mọi browser, kể cả IE cũ
  • Features: XHR có upload progress events, Fetch thì không (cần AbortController)

⚡ WebSocket

Kết nối 2 chiều, thời gian thực (real-time) cho chat, thông báo, dashboard, game.

Ví dụ WebSocket (client)
javascript
// 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'));
WebSocket không thay thế REST. Hãy dùng cho dữ liệu push real-time; còn CRUD dữ liệu vẫn nên dùng REST/GraphQL.

🔍 GraphQL

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.

Ví dụ query GraphQL
javascript
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);
}
GraphQL mạnh khi nhiều view khác nhau cần các trường dữ liệu khác nhau. Cần server hỗ trợ GraphQL.