Xây realtime chat app với Socket.io 4.x: Rooms và broadcasting cho dự án Việt

3 tháng trước · 8 phút đọc
WebSocket và Socket.io: Tại sao cần phân biệt?
Nhiều bạn mới học hay nhầm Socket.io = WebSocket. Thực ra không phải vậy.
WebSocket là giao thức - định nghĩa cách client và server giao tiếp hai chiều liên tục qua một connection duy nhất. Socket.io là thư viện xây trên WebSocket, nhưng thêm vào đó một đống tính năng mà native WebSocket không có: auto-reconnect, rooms, namespaces, fallback transport.
Nghe có vẻ Socket.io ngon hơn hoàn toàn. Nhưng có trade-off: bundle size ~200KB so với WebSocket gần như 0KB. Nếu bạn chỉ cần stream dữ liệu đơn giản tốc độ cao (ví dụ financial tick data), native WebSocket hợp lý hơn. Còn làm chat app với nhiều phòng, nhiều user, cần reconnect tự động - Socket.io là lựa chọn thực tế.
Socket.io thêm rooms, reconnect, fallback - native WebSocket không có
Bài này dùng Socket.io 4.x (phiên bản ổn định mới nhất, 4.8.3 tính đến thời điểm viết). Nếu bạn thấy đâu đó có nói "Socket.io 5.0" - phiên bản đó chưa được phát hành chính thức, đừng tin.
Mình cũng giả định bạn đã quen JavaScript cơ bản và biết Node.js một chút. Nếu chưa, khóa Node & ExpressJS của F8 cover đúng nền tảng bạn cần trước khi đọc tiếp.
Cài đặt và khởi động server trong 5 phút
Mình bắt đầu với một Express server đơn giản tích hợp Socket.io.
Tạo file server.js:
Luồng kết nối: client gọi io() → Socket.io chọn transport → WebSocket hoặc fallback
Một điểm cần chú ý: Socket.io không attach trực tiếp vào Express app, mà attach vào HTTP server. Đây là lỗi phổ biến khi mới bắt đầu. Bạn tạo httpServer từ createServer(app), rồi truyền httpServer vào new Server().
CORS config cũng quan trọng nếu frontend chạy ở port khác. Trong production, thay origin bằng domain thật.
Rooms: Chia người dùng vào phòng riêng
Đây là tính năng mình thấy hữu ích nhất của Socket.io khi làm dự án Việt - từ app chat nhóm đến hệ thống thông báo real-time.
Mỗi room trong Socket.io là một kênh ảo. Socket có thể join nhiều room cùng lúc. Khi server emit vào một room, chỉ những socket trong room đó nhận được message.
Mỗi room là kênh độc lập - message phòng A không lọt sang phòng B
Có 2 cách broadcast quan trọng cần phân biệt:
io.to(roomId).emit(...)- gửi đến tất cả trong room, bao gồm sendersocket.to(roomId).emit(...)- gửi đến tất cả trong room trừ sender
Dùng cái nào? Khi người dùng gửi tin nhắn và bạn muốn họ thấy tin của chính mình ngay lập tức (optimistic UI), dùng io.to(). Khi thông báo "ai đó đang gõ...", dùng socket.to() để người gõ không nhận notification của chính họ.
Broadcasting: Gửi tin đến nhiều người cùng lúc
Broadcasting là gửi message đến nhiều client một lúc. Socket.io hỗ trợ vài pattern broadcasting khác nhau, mỗi cái có use case riêng.
5 pattern broadcasting - chọn đúng pattern tránh gửi nhầm message
Use case thực tế cho dự án Việt:
Chat nhóm nội bộ công ty: Mỗi team = 1 room. Dùng io.to(teamRoom).emit() để broadcast.
Live sale / TikTok Shop clone: Room live-{sessionId} cho mỗi phiên. Người xem join room, host broadcast số lượng tồn kho, giá flash sale, đơn hàng mới.
Hệ thống thông báo: Tạo room riêng cho từng user: user-{userId}. Khi có sự kiện (đơn hàng mới, tin nhắn mới), server emit vào room đó. User login từ nhiều thiết bị vẫn nhận được notification đầy đủ.
Game online đơn giản: Room game-{gameId} cho mỗi bàn chơi. Broadcast trạng thái game đến tất cả người chơi trong bàn.
Xây client React kết nối vào server
Bây giờ mình làm phía frontend. Mình dùng React vì đa số dự án Việt hiện nay chạy React.
Tách socket instance ra file riêng - tránh tạo nhiều connection dư thừa
Hai lỗi phổ biến mình hay thấy khi review code:
- Tạo socket trong component - mỗi lần re-render tạo thêm connection mới. Giải pháp: export socket từ file riêng như trên.
- Quên cleanup -
socket.off('receive_message')trong return của useEffect. Nếu quên, event listener chồng chất, mỗi message nhận được sẽ trigger nhiều lần.
Nếu bạn muốn tìm hiểu sâu hơn về React hooks và patterns như thế này, khóa Xây Dựng Website với ReactJS của F8 cover phần useEffect và custom hooks khá kỹ.
Auto-reconnect và xử lý mất kết nối
Đây là phần nhiều tutorial bỏ qua, nhưng lại là thứ người dùng Việt cần nhất - mạng 3G chập chờn, WiFi yếu, chuyển mạng giữa chừng.
Socket.io 4.x có auto-reconnect mặc định với exponential backoff: thử lại sau 1s, 2s, 4s, 8s... đến khi kết nối được hoặc đạt reconnectionAttempts.
Reconnect thất bại 5 lần → thông báo cho user, tránh chờ đợi vô tận
Một lưu ý quan trọng: sau khi reconnect, socket.id thay đổi. Điều này có nghĩa là server coi đây là connection hoàn toàn mới. Bạn cần emit join_room lại trong event reconnect để user không bị "mất phòng" sau khi mạng ổn định.
Về phía server, có thể bật packet buffering để giữ lại những message gửi đến một socket đang offline và deliver khi họ reconnect - nhưng đây là advanced topic phù hợp hơn khi bạn đã có bản chat chạy được.
