CHƯƠNG 08 · API · ~120 phút

WebSocket, SSE, gRPC,
REST, GraphQL

5 cách build API + realtime communication. Mỗi cái có điểm mạnh riêng — biết khi nào dùng cái nào quyết định kiến trúc app. Học xong, bạn nắm: REST principles, GraphQL trade-off, gRPC streaming, WebSocket vs SSE vs polling.

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ý:

  1. Stateless: mỗi request độc lập, server không lưu session client
  2. Resource-oriented: mọi thứ là resource, có URL riêng (vd /users/42)
  3. HTTP method = action (GET đọc, POST tạo, PUT/PATCH update, DELETE xoá)
  4. HTTP status code = kết quả
  5. Cacheable: GET cache được
  6. 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íRESTGraphQL
EndpointNhiều (theo resource)1 endpoint duy nhất
Method HTTPGET/POST/PUT/DELETEPOST (hầu hết)
Over/under-fetchingKhông
HTTP cacheDễ (GET với URL khác)Khó
File uploadTự nhiênPhức tạp (multipart spec)
Versioningv1/v2 trong URLField deprecation
Learning curveThấpCao hơn
Mobile (bandwidth limited)Tốn nhiều req1 query lấy tất cả
ToolPostman, SwaggerGraphiQL, 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:

  1. Short polling — client liên tục hỏi
  2. Long polling — server giữ request đến khi có data
  3. Server-Sent Events — 1 chiều server→client, qua HTTP
  4. 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?

PatternDirectionLatencyComplexityUse case
Short pollingClient pullCao (= interval)ThấpDon't use, trừ cùng cực
Long pollingClient pullTrungTrungLegacy support
SSEServer → ClientThấpThấpNotification, news feed, log stream
WebSocketBidirectionalThấpCaoChat, collab, game, trading
gRPC streamingBidirectionalThấpCaoMicroservice 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

Bài 1 — RESTful URL

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.

Bài 2 — GraphQL query

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

Bài 3 — gRPC vs REST benchmark

Search "grpc vs rest benchmark". So sánh: latency, throughput, payload size. gRPC nhanh hơn bao nhiêu?

Bài 4 — SSE thực hành

Viết Express server có endpoint /sse gửi timestamp mỗi giây. Browser HTML connect bằng new EventSource('/sse'), in messages.

Bài 5 — WebSocket chat

Viết simple chat server bằng ws: nhiều client connect, broadcast message tới mọi client.

Bài 6 — Pick the right tool

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?

  • Mã hoá data
  • WebSocket-based
  • Stateless, resource-oriented (URL = resource), HTTP method = action, cacheable
  • Chỉ dùng JSON

Đá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?

  • Chậm
  • Over-fetching (lấy quá nhiều) + Under-fetching (cần nhiều request)
  • Không hỗ trợ HTTP/2
  • Không có authentication

Đá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ì?

  • Dùng IPv6
  • Single threaded
  • Binary protobuf (nhỏ + parse nhanh) + HTTP/2 multiplex + native streaming
  • Không cần auth

Đáp án: protobuf + HTTP/2 + streaming. 5-10x nhanh hơn REST/JSON.

Câu 4. WebSocket khác HTTP polling thế nào?

  • WebSocket dùng UDP
  • WebSocket = full-duplex, 1 connection, server push được; polling = client phải hỏi
  • WebSocket free hơn
  • Hai cái giống nhau

Đáp án: full-duplex 1 connection. Polling tốn nhiều request lãng phí.

Câu 5. SSE vs WebSocket?

  • SSE 1 chiều server→client (đơn giản hơn); WebSocket bidirectional
  • SSE binary, WebSocket text
  • WebSocket 1 chiều, SSE 2 chiều
  • Hai cái giống nhau

Đá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?

  • HTTP request với Upgrade: websocket header → server response 101 Switching Protocols
  • TCP SYN trực tiếp
  • UDP packet
  • DNS query

Đá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?

  • Public API đơn giản
  • File upload chính
  • Mobile app phức tạp với nhiều view khác nhau cần data khác nhau, tránh over-fetch trên mobile data
  • Microservice nội bộ tốc độ cao

Đá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?

  • Short polling 1s
  • SSE — server push 1 chiều, đơn giản hơn WebSocket
  • REST GET
  • gRPC unary

Đá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
← Chương trước Chương 07: DNS Chương kế tiếp Chương 09: Auth →