How to Build a Real-Time Chat App with WebSocket from Scratch in 2025
Hey friend, ready to wire up a live chat in under an hour?
Last weekend I promised my cousin I’d show him how Slack-style messaging works under the hood. We built a tiny demo, laughed at our own typos flying across two laptops, and left with a working app. I’m sharing that exact walk-through here.
So grab your coffee. By the end you’ll have:
- a two-way chat that feels instant
- clean, copy-paste code
- next steps for user names, rooms, and real deploys
Sound good? Let’s roll.
Why WebSocket Beats Plain HTTP for Chat
Short version: HTTP is like snail-mail. WebSocket is like a phone call.
With old-school HTTP you ask the server, wait, get an answer, repeat. That’s fine for loading pages. Terrible for chat.
WebSocket opens one wire and keeps it open. The server can push messages the moment they arrive. No polling. No lag. Less battery drain.
Quick wins you’ll notice right away:
- messages land in ~1 ms on the same network
- only one TCP connection per user
- works in every modern browser (even that old iPad at your parents’ house)
What You Need Before We Start
Nothing fancy. If you’ve run console.log('hello')
before, you’re set.
- Node.js 18+ (grab it from nodejs.org)
- npm or yarn (comes with Node)
- VS Code (or any editor you like)
- Chrome or Firefox for testing
That’s it. No paid tools, no cloud accounts yet.
Step 1: Spin Up the WebSocket Server in 5 Minutes
1.1 Create a new folder and hop in
mkdir chat-websocket && cd chat-websocket
npm init -y
npm install ws
1.2 Drop this into server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', socket => {
console.log('👋 new buddy joined');
socket.on('message', raw => {
const msg = JSON.parse(raw);
msg.timestamp = Date.now();
// broadcast to everyone except sender
wss.clients.forEach(client => {
if (client !== socket && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(msg));
}
});
});
socket.on('close', () => console.log('👋 buddy left'));
});
console.log('🚀 server live at ws://localhost:8080');
Run it:
node server.js
You should see the rocket emoji. Keep this terminal open.
Pro tip: We wrapped our text in JSON so later we can add usernames, avatars, or emoji without rewriting everything.
Step 2: Whip Up a Tiny Frontend
Create index.html
in the same folder. Paste and save.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Live Chat</title>
<style>
body { font-family: Arial, sans-serif; margin: 2rem; }
#chat { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 1rem; }
#msgBox { width: 80%; padding: 0.5rem; }
button { padding: 0.5rem 1rem; }
</style>
</head>
<body>
<h1>💬 Mini Chat</h1>
<div id="chat"></div>
<input id="msgBox" placeholder="Type and hit Enter" aria-label="Message input">
<button onclick="sendMsg()" aria-label="Send message">Send</button>
<script>
const socket = new WebSocket('ws://localhost:8080');
const chat = document.getElementById('chat');
socket.onmessage = event => {
const { text, timestamp } = JSON.parse(event.data);
const time = new Date(timestamp).toLocaleTimeString();
chat.innerHTML += `<div><b>${time}</b> ${text}</div>`;
chat.scrollTop = chat.scrollHeight;
};
function sendMsg() {
const input = document.getElementById('msgBox');
if (!input.value.trim()) return;
socket.send(JSON.stringify({ text: input.value }));
input.value = '';
}
// allow Enter key
document.getElementById('msgBox').addEventListener('keyup', e => {
if (e.key === 'Enter') sendMsg();
});
</script>
</body>
</html>
Open the file in two browser tabs. Type in one, watch it pop up in the other. Magic!
Step 3: Make It Feel Real (Add Usernames & Colors)
Nobody wants to talk to “Anonymous Hedgehog.” Let’s fix that.
3.1 Prompt for a name on load
Add this right after the body tag in index.html
:
const username = prompt('Pick a nickname:') || 'Guest';
3.2 Update the send function
socket.send(JSON.stringify({ text: input.value, username }));
3.3 Update the server broadcast
Change the push line to:
client.send(JSON.stringify({ ...msg, username }));
Refresh your tabs, pick names, and you’ll see something like:
14:32 Ada hey there
14:32 Finn 👋
Step 4: Handle Errors & Reconnects Like a Pro
Real users will close laptops and hop on bad Wi-Fi. Handle it.
Frontend snippet:
socket.onclose = () => {
chat.innerHTML += '<div><i>Connection lost. Reconnecting...</i></div>';
setTimeout(() => location.reload(), 2000);
};
Server tip: Ping-pong every 30 s to keep proxies happy.
setInterval(() => {
wss.clients.forEach(ws => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('connection', ws => {
ws.isAlive = true;
ws.on('pong', () => ws.isAlive = true);
});
Step 5: Deploy in 3 Clicks (Render Example)
You could stay local forever, but showing off is fun.
- Push your code to GitHub.
- Go to render.com → New → Web Service.
- Pick your repo, set Environment to Node, Build Command to
npm install
, Start Command tonode server.js
. - Hit deploy. Done. Render gives you a
wss://
URL swap it into the frontend script and you’re live.
Heroku, Railway, or Fly.io? Same idea. Just remember to enable WebSocket support (some free dynos need the --permit-unauthenticated
flag).
Common Gotchas & Quick Fixes
Problem | Why it happens | One-liner fix |
---|---|---|
Works on localhost, fails on server | forgot HTTPS→WSS switch | change ws:// to wss:// in JS |
Messages arrive twice | running two server instances | check `ps aux |
Browser error “blocked mixed content” | serving HTML over HTTPS but trying ws:// | serve both over HTTPS or use localhost for dev |
Stretch Ideas to Level Up
Feeling spicy? Pick one:
- Rooms: add
?room=gaming
query param, filter broadcasts server-side. - Typing indicators: send
{type:"typing", username}
on input events. - Message history: dump chats into Redis or Mongo for persistence.
- Dark mode: one CSS media query and you’re cool again.
- Rate limiting: max 5 messages/sec to stop spam bots.
Wrapping Up: You Just Built the Internet’s Core Superpower
Real-time chat isn’t just cool it’s the backbone of Slack, Discord, Zoom, and every multiplayer game you love. You now hold the same tech in your hands.
Next step: pick one stretch idea and ship it. Then tweet me the link. I’ll be your first user.
“The best way to learn real-time is to build something real, laugh at the bugs, and hit refresh.” 😊
#WebSocket #RealTimeChat #NodeJSTutorial #SideProject #LearnToCode