Rooms, private messages, user presence, message history.
A real chat app needs: rooms (channels), user presence (who's online), private messages, and message history. We'll build all four today. The server is the single source of truth — it manages room membership and broadcasts messages.
io.on('connection', (socket) => {
// Join a room
socket.on('joinRoom', (room) => {
socket.join(room);
// Tell others in the room
socket.to(room).emit('userJoined', {
userId: socket.id, room
});
socket.emit('joinedRoom', room);
});
// Send to a room
socket.on('roomMessage', ({ room, message }) => {
io.to(room).emit('newMessage', {
from: socket.id,
message,
time: new Date().toISOString()
});
});
// Leave a room
socket.on('leaveRoom', (room) => {
socket.leave(room);
socket.to(room).emit('userLeft', socket.id);
});
});
Track who's online using a simple in-memory Map. In production you'd use Redis, but for a single-server app a Map works fine.
const onlineUsers = new Map(); // socketId -> username
io.on('connection', (socket) => {
socket.on('setUsername', (username) => {
onlineUsers.set(socket.id, username);
io.emit('onlineUsers', Array.from(onlineUsers.values()));
});
socket.on('disconnect', () => {
const username = onlineUsers.get(socket.id);
onlineUsers.delete(socket.id);
io.emit('userLeft', username);
io.emit('onlineUsers', Array.from(onlineUsers.values()));
});
});
socket.on('privateMessage', ({ to, message }) => {
// Each socket automatically joins a room named by its own ID
io.to(to).emit('privateMessage', {
from: socket.id,
message,
time: new Date().toISOString()
});
// Also emit to sender so they see their own message
socket.emit('privateMessage', {
from: socket.id, to, message,
time: new Date().toISOString()
});
});
typing to the room. Stop broadcasting after 2 seconds of no input.