Socket.IO v4.x: Xây Realtime Chat App Với Rooms Và Broadcasting Cho Dự Án Việt

3 tháng trước · 11 phút đọc
Socket.IO là gì và tại sao không phải WebSocket thuần?
Bạn có bao giờ tự hỏi tại sao mình không dùng thẳng WebSocket cho ứng dụng chat, mà lại cần thêm một lớp thư viện nữa?
Câu trả lời ngắn: WebSocket thuần đòi bạn tự xử lý hầu hết mọi thứ - reconnect khi đứt mạng, fallback khi môi trường không hỗ trợ, buffer event trong lúc offline, broadcast đến nhóm người dùng. Socket.IO đóng gói tất cả phần đó lại và cho bạn một API event-based sạch hơn nhiều.
Cụ thể hơn, Socket.IO không phải là WebSocket. Nó là một lớp cao hơn: ưu tiên dùng WebSocket khi có thể, nhưng tự động rơi xuống HTTP long-polling nếu WebSocket bị chặn - điều khá phổ biến ở một số mạng doanh nghiệp và môi trường hosting rẻ tiền tại Việt Nam. Với các phiên bản gần đây trong nhánh v4.x, Socket.IO còn hỗ trợ WebTransport như một transport tùy chọn trong điều kiện phù hợp.
WebSocket là nền, Socket.IO là lớp tiện ích phía trên - không nên nhầm lẫn hai thứ này
Phiên bản hiện tại đáng tin cậy nhất để dùng trong dự án thực là Socket.IO v4.x (latest stable là 4.8.x vào cuối 2025). Mình sẽ dùng v4.x làm chuẩn trong toàn bài. Nếu bạn thấy ai đó nhắc đến "Socket.IO 5.0", hãy thận trọng - chưa có đủ bằng chứng về một bản release chính thức với breaking changes rõ ràng tại thời điểm bài viết này.
Mô hình hoạt động cũng đơn giản hơn bạn nghĩ:
- Client kết nối đến server (namespace mặc định hoặc namespace chỉ định)
- Socket.IO chọn transport phù hợp nhất
- Hai bên trao đổi events có tên, ví dụ
chat_message,typing,join_room - Khi mạng chập chờn, client tự reconnect và event có thể được buffer rồi gửi lại
Đây là lý do Socket.IO phổ biến cho chat app, realtime dashboard và multiplayer game - những use case mà bạn cần reliable connection hơn là raw performance.
Cài đặt và khởi tạo dự án
Mình sẽ dùng stack đơn giản nhất có thể: Node.js + Express cho backend, HTML/JS thuần cho frontend (phần sau sẽ nói thêm về React). Không cần setup phức tạp.
Cấu trúc thư mục:
chat-app/
├── server.js
├── public/
│ └── index.html
└── package.json
Server cơ bản nhất:
Chỉ cần 3 package là đủ để có WebSocket server hoạt động
Phía client, bạn có thể dùng CDN để test nhanh:
Một chi tiết hay: file socket.io.js được serve tự động bởi server Socket.IO, bạn không cần cài thêm gì ở frontend. Nếu dùng React hoặc bundler khác thì npm install socket.io-client và import bình thường.
Nếu bạn chưa quen với Node.js và Express, khóa Node & ExpressJS trên F8 cover đầy đủ phần nền tảng này trước khi đến Socket.IO.
Rooms: Cách gom người dùng vào phòng chat
Rooms là tính năng mình thấy nhiều bạn Việt bỏ qua khi mới học Socket.IO, rồi sau đó tự hỏi tại sao broadcast của mình lại gửi tin nhắn cho tất cả mọi người thay vì chỉ người trong phòng.
Rooms là abstraction phía server. Không có gì ở phía client biết về rooms - đây là khái niệm thuần server-side để Socket.IO biết khi emit event thì gửi cho ai.
Một socket có thể join nhiều rooms cùng lúc. Và mỗi socket mặc định đã nằm trong một room riêng có id là socket.id của nó.
Mỗi room là một nhóm socket riêng biệt - tin nhắn chỉ đi trong phòng đó
Phía client:
Bạn cũng có thể xem ai đang ở trong phòng nào từ phía server:
Use case thực tế cho dự án Việt: phòng theo team (frontend, backend, QA), phòng theo dự án, hoặc channel theo workspace kiểu Slack mini. Logic join/leave room không phức tạp, nhưng bạn cần quyết định rõ ai được join phòng nào - phần đó là business logic của bạn, không phải Socket.IO lo.
Broadcasting: Gửi tin nhắn đúng người, đúng phòng
Đây là phần hay nhất. Socket.IO có nhiều cách emit khác nhau, và chọn sai là nguyên nhân số 1 gây ra bug kiểu "tại sao tôi nhận được tin nhắn của chính mình".
Mình tổng hợp lại 4 kiểu emit phổ biến nhất:
| Cách emit | Gửi đến ai | Dùng khi nào |
|---|---|---|
socket.emit(event, data) |
Chỉ socket hiện tại | Gửi phản hồi cho người vừa gửi |
socket.to(room).emit(...) |
Tất cả trong room, trừ người gửi | Chat message vào phòng |
io.to(room).emit(...) |
Tất cả trong room, kể cả người gửi | Thông báo hệ thống |
io.emit(event, data) |
Tất cả mọi người kết nối | Announcement toàn server |
Code thực tế cho một chat room:
Chọn đúng kiểu emit là khác biệt giữa bug và feature
Phía client React:
Lưu ý cái socket.off('receive_message') trong cleanup. Quên cái này là lý do hay gặp nhất khiến component React nhận duplicate messages - mỗi lần re-render lại đăng ký thêm một listener mới mà không remove cái cũ.
Auto-reconnect và connection state recovery
Mạng Việt Nam không phải lúc nào cũng ổn định. Đây là thực tế mà bất kỳ ai build sản phẩm cho người dùng trong nước đều hiểu.
Mặc định, Socket.IO đã bật auto-reconnect - client sẽ tự thử kết nối lại theo exponential backoff khi mất mạng. Bạn không cần viết thêm gì. Nhưng có một vấn đề: trong lúc mất kết nối, những event gửi từ server sẽ bị mất.
Socket.IO v4.6+ giới thiệu connection state recovery - khi client reconnect thành công trong một khoảng thời gian ngắn, server có thể replay lại các event bị bỏ lỡ. Cấu hình phía server:
Phía client, bạn có thể detect trạng thái reconnect:
socket.recovered giúp bạn biết có cần fetch lại data hay không
Ngoài ra, bạn nên xử lý UI cho người dùng khi mất kết nối:
Một trade-off cần biết: connection state recovery yêu cầu server giữ state trong memory (hoặc adapter tương thích). Nếu bạn scale lên nhiều server instances, cần thêm Redis adapter để các instance chia sẻ state với nhau - không phải vấn đề với prototype, nhưng cần tính trước khi đưa lên production.
Demo: Chat app hoàn chỉnh với rooms
Mình tổng hợp tất cả những phần trên vào một demo có thể chạy được ngay. File HTML dưới đây là client đầy đủ, chạy được với server Node.js đã viết ở trên.
Demo bao gồm:
- Nhập tên và chọn/tạo phòng chat
- Gửi và nhận tin nhắn realtime
- Hiển thị trạng thái "đang gõ..."
- Thông báo khi có người vào/rời phòng
- Hiển thị trạng thái kết nối
Phần server hoàn chỉnh:
Bước tiếp theo: thêm xác thực JWT và dùng Redis adapter khi cần scale
Nếu bạn muốn đưa lên production, hai thứ cần làm ngay:
- Xác thực: Thêm middleware Socket.IO để verify JWT token trước khi cho phép kết nối
- Adapter: Dùng
@socket.io/redis-adapternếu chạy nhiều server instance
Khóa Node & ExpressJS và Xây Dựng Website với ReactJS trên F8 là hai nền tảng bạn cần vững trước khi tích hợp Socket.IO vào dự án thực tế - Socket.IO chỉ là tầng realtime, phần còn lại vẫn là Express routing và React state management bình thường.