8.1 REST — Representational State Transfer
REST không phải protocol — là kiến trúc API dựa trên HTTP, đề xuất bởi Roy Fielding (2000). Nguyên lý:
- Stateless: mỗi request độc lập, server không lưu session client
- Resource-oriented: mọi thứ là resource, có URL riêng (vd
/users/42) - HTTP method = action (GET đọc, POST tạo, PUT/PATCH update, DELETE xoá)
- HTTP status code = kết quả
- Cacheable: GET cache được
- Uniform interface: mọi resource dùng cùng cách
RESTful URL design
✓ Đúng ✗ Sai (RPC-style)
GET /users GET /getUsers
GET /users/42 GET /getUserById?id=42
POST /users POST /createUser
PUT /users/42 POST /updateUser
DELETE /users/42 POST /deleteUser
# Nested
GET /users/42/posts (lấy tất cả post của user 42)
GET /users/42/posts/123 (post 123 của user 42)
# Query params cho filter/sort/pagination
GET /users?role=admin&sort=name&page=2&limit=20
RESTful response
// GET /users/42
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2025-01-15T10:00:00Z"
}
// GET /users (collection)
{
"data": [...],
"pagination": {
"page": 1,
"perPage": 20,
"total": 150
}
}
// Error
{
"error": {
"code": "USER_NOT_FOUND",
"message": "User 42 does not exist"
}
}
HATEOAS — link trong response
"Hypermedia as the Engine of Application State" — response chứa link đến next action. Đẹp lý thuyết nhưng ít api thực tế dùng đầy đủ.
{
"id": 42,
"name": "Alice",
"_links": {
"self": { "href": "/users/42" },
"posts": { "href": "/users/42/posts" },
"delete": { "href": "/users/42", "method": "DELETE" }
}
}
8.2 GraphQL — query language cho API
GraphQL (Facebook 2015) — 1 endpoint duy nhất, client gửi query mô tả chính xác data cần. Server trả đúng những field đó.
Schema + Query
# Server schema
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
}
# Client query — chỉ lấy field cần
query {
user(id: "42") {
name
email
posts {
title
}
}
}
# Server response
{
"data": {
"user": {
"name": "Alice",
"email": "alice@example.com",
"posts": [
{ "title": "Hello GraphQL" },
{ "title": "GraphQL vs REST" }
]
}
}
}
Mutation = ghi data
mutation {
createUser(name: "Bob", email: "bob@example.com") {
id
name
}
}
Subscription = realtime
subscription {
newPost {
id
title
}
}
# Implement qua WebSocket
Lợi ích
- Không over-fetching: lấy đúng field cần
- Không under-fetching: 1 query lấy cả user + posts (REST cần 2 endpoint)
- Schema tự document
- Strong typing + autocomplete trong tool (GraphiQL, Apollo Studio)
Nhược
- Phức tạp hơn REST cho API đơn giản
- Khó cache HTTP (mọi query đến cùng URL với POST)
- N+1 query problem (cần DataLoader)
- File upload không thuận tiện
8.3 REST vs GraphQL — Khi nào dùng cái nào?
| Tiêu chí | REST | GraphQL |
|---|---|---|
| Endpoint | Nhiều (theo resource) | 1 endpoint duy nhất |
| Method HTTP | GET/POST/PUT/DELETE | POST (hầu hết) |
| Over/under-fetching | Có | Không |
| HTTP cache | Dễ (GET với URL khác) | Khó |
| File upload | Tự nhiên | Phức tạp (multipart spec) |
| Versioning | v1/v2 trong URL | Field deprecation |
| Learning curve | Thấp | Cao hơn |
| Mobile (bandwidth limited) | Tốn nhiều req | 1 query lấy tất cả |
| Tool | Postman, Swagger | GraphiQL, Apollo |
Khi nào REST?
- API đơn giản, CRUD truyền thống
- Public API (Stripe, GitHub) — cache, versioning rõ ràng
- Server-to-server (microservice với khối lượng data nhỏ)
- Team chưa quen GraphQL
Khi nào GraphQL?
- App phức tạp với nhiều view khác nhau cần data khác nhau (mobile + web + dashboard)
- Avoid over-fetching trên mobile (data plan)
- Frontend tự định nghĩa nhu cầu (BFF pattern)
- Multiple legacy backends — GraphQL aggregate
8.4 gRPC — RPC modern, fast
gRPC (Google 2015) — RPC framework dùng Protocol Buffers + HTTP/2. Cực nhanh cho microservice.
Protocol Buffers (protobuf) — schema + serialization
// user.proto
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc StreamUsers (stream GetUserRequest) returns (stream User);
}
message GetUserRequest {
int32 id = 1;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
Compiler tự sinh code Go/Java/Node/Python từ .proto. Type-safe, không phải parse tay.
4 loại RPC
- Unary: 1 request → 1 response (như REST)
- Server streaming: 1 request → nhiều response (vd subscribe to events)
- Client streaming: nhiều request → 1 response (vd upload file)
- Bidirectional streaming: 2 chiều cùng lúc (vd chat)
Lợi ích so với REST
- Nhanh hơn 5-10x: binary protobuf nhỏ + parse nhanh + HTTP/2 multiplex
- Strong typing: schema bắt buộc → ít bug
- Streaming native
- Multi-language: cùng .proto, code gen cho mọi ngôn ngữ
Nhược
- Browser support kém — cần grpc-web proxy
- Khó debug (binary format)
- Yêu cầu HTTP/2 → khó qua proxy cũ
Use case lý tưởng: microservice giao tiếp nội bộ (server-to-server).
8.5 Realtime Communication — Vấn đề
HTTP truyền thống là client pull: client request, server response. Nhưng nếu cần server push (vd notification, chat)?
4 cách giải quyết, từ thô sơ đến hiện đại:
- Short polling — client liên tục hỏi
- Long polling — server giữ request đến khi có data
- Server-Sent Events — 1 chiều server→client, qua HTTP
- WebSocket — 2 chiều full-duplex
8.6 Polling — đơn giản nhất nhưng kém
Short polling
setInterval(async () => {
const messages = await fetch('/api/messages?since=' + lastTime).then(r => r.json());
if (messages.length) renderMessages(messages);
}, 3000); // hỏi mỗi 3 giây
- Ưu: đơn giản, không cần infrastructure đặc biệt
- Nhược: hầu hết request lãng phí (không có data mới); latency = polling interval
Long polling
async function poll() {
// Server giữ request mở đến khi có data hoặc timeout (vd 30s)
const data = await fetch('/api/messages/wait?since=' + lastTime);
renderMessages(data);
poll(); // ngay sau khi nhận, poll tiếp
}
poll();
Server side: nhận request, hold connection, đợi event hoặc timeout, rồi trả response.
- Ưu: latency thấp hơn short polling, dùng được trên HTTP/1.1 thường
- Nhược: chiếm connection lâu, vẫn có overhead reconnect
Long polling là hack. Modern thay bằng SSE/WebSocket.
8.7 Server-Sent Events (SSE) — push 1 chiều, đơn giản
SSE: server giữ HTTP connection mở, gửi nhiều event qua thời gian. Native trong browser qua EventSource API.
Server side
// Express
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Gửi event mỗi 5 giây
const intervalId = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 5000);
req.on('close', () => clearInterval(intervalId));
});
Client side
const es = new EventSource('/events');
es.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('received:', data);
};
es.onerror = (err) => { /* auto reconnect */ };
Đặc điểm
- 1 chiều: chỉ server → client
- Auto reconnect: browser tự retry khi mất connection
- Built on HTTP: dễ qua proxy/firewall
- Text only (UTF-8)
Use case: notification, live feed, stock price, log streaming.
8.8 WebSocket — full-duplex realtime
WebSocket (RFC 6455, 2011): protocol full-duplex over TCP, upgrade từ HTTP. Là chuẩn cho realtime app.
Handshake — Upgrade từ HTTP
# Client request
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
# Server response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sau 101, connection chuyển sang WebSocket frame format. Không còn HTTP nữa.
WebSocket frame
- Header nhỏ (2-14 byte)
- Binary hoặc text
- Cả 2 chiều có thể gửi bất cứ lúc nào
- Có ping/pong để giữ connection alive
Code đơn giản
// Client (browser)
const ws = new WebSocket('wss://example.com/chat');
ws.onopen = () => ws.send(JSON.stringify({ type: 'hello' }));
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log('received:', msg);
};
ws.onclose = () => console.log('closed');
// Server (Node + ws library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
// broadcast to all
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) client.send(data);
});
});
});
Use case
Chat (Slack, Discord), collaborative editing (Google Docs, Figma), live dashboard, multiplayer game, trading.
Khó khăn
- Stateful — server giữ connection per user → khó scale (sticky session, message broker)
- Reconnect logic phức tạp
- Một số proxy/firewall không support
- Library phổ biến:
ws(Node),socket.io(có fallback long polling)
8.9 So sánh — Khi nào dùng cái nào?
| Pattern | Direction | Latency | Complexity | Use case |
|---|---|---|---|---|
| Short polling | Client pull | Cao (= interval) | Thấp | Don't use, trừ cùng cực |
| Long polling | Client pull | Trung | Trung | Legacy support |
| SSE | Server → Client | Thấp | Thấp | Notification, news feed, log stream |
| WebSocket | Bidirectional | Thấp | Cao | Chat, collab, game, trading |
| gRPC streaming | Bidirectional | Thấp | Cao | Microservice realtime |
Quyết định cây
Cần realtime?
├─ Server push 1 chiều (notification, feed) → SSE
├─ 2 chiều (chat, collab) → WebSocket
└─ Microservice nội bộ → gRPC streaming
Không realtime, server-to-server → gRPC unary
Không realtime, public API đơn giản → REST
Frontend phức tạp, nhiều view → GraphQL
Bài tập
Thiết kế REST API cho blog: list bài, xem chi tiết bài, viết comment, like comment, follow author. Liệt kê method + URL.
Cho schema User-Post, viết GraphQL query: lấy 10 user mới nhất + 3 post gần nhất của mỗi user (chỉ title + createdAt).
Search "grpc vs rest benchmark". So sánh: latency, throughput, payload size. gRPC nhanh hơn bao nhiêu?
Viết Express server có endpoint /sse gửi timestamp mỗi giây. Browser HTML connect bằng new EventSource('/sse'), in messages.
Viết simple chat server bằng ws: nhiều client connect, broadcast message tới mọi client.
Mỗi scenario, chọn pattern phù hợp: (a) Stock price ticker; (b) Multiplayer game; (c) GitHub API public; (d) Mobile app fetch user profile + last 10 posts trong 1 request; (e) Microservice cho video transcoding cluster; (f) Log streaming đến browser.
🧪 Quiz cuối chương
Câu 1. REST nguyên lý chính?
Đáp án: Stateless + resource-oriented + HTTP method. Đây là kiến trúc, không phải spec.
Câu 2. GraphQL khắc phục vấn đề nào của REST?
Đáp án: over/under-fetching. Client query đúng field cần. 1 query lấy được data từ nhiều "resource".
Câu 3. gRPC nhanh hơn REST vì?
Đáp án: protobuf + HTTP/2 + streaming. 5-10x nhanh hơn REST/JSON.
Câu 4. WebSocket khác HTTP polling thế nào?
Đáp án: full-duplex 1 connection. Polling tốn nhiều request lãng phí.
Câu 5. SSE vs WebSocket?
Đáp án: SSE 1 chiều, WS 2 chiều. SSE đơn giản hơn cho notification/feed; WS cho chat/collab.
Câu 6. WebSocket handshake bắt đầu bằng?
Đáp án: HTTP Upgrade. Sau 101, không còn HTTP, dùng WebSocket frame format.
Câu 7. Use case TỐT cho GraphQL?
Đáp án: mobile + nhiều view khác nhau. Microservice nội bộ → gRPC. Public API đơn giản → REST.
Câu 8. Stock price live ticker — chọn pattern?
Đáp án: SSE. Chỉ cần server→client. WebSocket overkill. Polling tốn bandwidth.
Tổng kết chương 8
- ✅ REST: stateless, resource-oriented, HTTP method = action — đơn giản, public API
- ✅ GraphQL: 1 endpoint, query language, không over/under-fetch — phức tạp nhưng hợp app frontend phong phú
- ✅ gRPC: protobuf + HTTP/2 — nhanh nhất, native streaming, lý tưởng microservice nội bộ
- ✅ Polling: short (lãng phí), long (hack); chỉ dùng khi không có lựa chọn khác
- ✅ SSE: server push 1 chiều qua HTTP, auto-reconnect — notification, feed, log
- ✅ WebSocket: full-duplex bi-directional, upgrade từ HTTP — chat, collab, game
- ✅ Quy tắc: realtime 1 chiều → SSE; 2 chiều → WebSocket; microservice → gRPC; public CRUD → REST; frontend phức tạp → GraphQL