Membangun Chat App Real-time
Chat app adalah "hello world" dari real-time. Terlihat simple tapi banyak detail: message ordering, read receipts, typing indicators, history.
Architecture
React Client ←→ Socket.IO ←→ Node.js Server ←→ Database
←→ Redis (pub/sub)
Server Implementation
io.on("connection", async (socket) => {
const userId = socket.data.userId;
// Load user channels & join rooms
const channels = await db.channels.findByUser(userId);
channels.forEach(ch => socket.join(`ch:${ch.id}`));
// Send message
socket.on("message:send", async ({ channelId, text }, ack) => {
const message = await db.messages.create({
data: {
channelId,
authorId: userId,
text,
createdAt: new Date(),
},
});
// Broadcast to channel
io.to(`ch:${channelId}`).emit("message:new", message);
ack({ id: message.id, status: "sent" });
});
// Typing indicator
socket.on("typing:start", ({ channelId }) => {
socket.to(`ch:${channelId}`).emit("typing:start", { userId });
});
socket.on("typing:stop", ({ channelId }) => {
socket.to(`ch:${channelId}`).emit("typing:stop", { userId });
});
});
Client (React)
function useChat(channelId) {
const [messages, setMessages] = useState([]);
const socket = useSocket();
useEffect(() => {
// Load history
fetch(`/api/channels/${channelId}/messages`).then(r => r.json()).then(setMessages);
// Listen for new messages
socket.on("message:new", (msg) => {
if (msg.channelId === channelId) {
setMessages(prev => [...prev, msg]);
}
});
return () => socket.off("message:new");
}, [channelId]);
const send = (text) => {
socket.emit("message:send", { channelId, text }, (ack) => {
// Optimistic update already shown, ack confirms server receipt
});
};
return { messages, send };
}
Key Features
- Message ordering — Sort by createdAt, handle out-of-order delivery
- Optimistic updates — Show message immediately, confirm later
- Typing indicator — Debounce, auto-stop after 3s
- Read receipts — Track last read message per user per channel
- Infinite scroll — Load older messages on scroll up