Learn how to go from zero setup to a working real-time chat app using pure JavaScript and WebSockets, no frameworks, no magic.

Introduction
Most web apps today talk back to the user instantly, messages appear without refreshing, dashboards update live, and notifications pop up in real time.
If you’ve ever wondered how that magic happens, it’s not magic at all; it’s WebSockets.
WebSockets let your app keep a persistent connection open between client and server so both can send data anytime, no waiting for HTTP requests, no polling loops, just live interaction.
In this article, I’ll take you from zero setup to a running real-time chat app, built entirely in pure JavaScript (Node.js on the backend, browser WebSocket API on the frontend).
Let’s build this from scratch.
1. What You’ll Build
We’re going to build a minimal but fully functional chat app where:
- Multiple users can connect at once.
- Messages show up instantly for everyone.
- All of it works with less than 100 lines of code.
No frameworks. No Socket.IO. Just the native WebSocket protocol.
2. Why WebSockets?
Before diving into code, let’s understand the why.
Normally, browsers use HTTP requests, which follow a strict pattern:
Client asks → Server responds → Connection closes.
That’s fine for blogs or APIs, but useless for features like:
- Live chat
- Real-time dashboards
- Multiplayer games
- Instant notifications
WebSockets fix this by opening a persistent, two-way connection.
Once the connection is established, both the client and the server can exchange messages freely and instantly.
3. Setting Up Your Project
Create a fresh folder and initialize Node.js:
mkdir websocket-chat
cd websocket-chat
npm init -y
Then install the official WebSocket library for Node:
npm install ws
Your folder structure will look like this:
websocket-chat/
│
├── server.js # WebSocket backend
└── public/
└── index.html # Frontend UI
4. Writing the WebSocket Server
Create a file called server.js in the root folder.
Paste this code:
import { WebSocketServer } from 'ws';
import { createServer } from 'http';
import { readFileSync } from 'fs';
import { resolve } from 'path';
// Serve the frontend HTML file
const server = createServer((req, res) => {
if (req.url === '/') {
const html = readFileSync(resolve('public', 'index.html'), 'utf-8');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
}
});
// Create a WebSocket server on top of the same HTTP server
const wss = new WebSocketServer({ server });
// Handle new connections
wss.on('connection', (socket) => {
console.log('🟢 New client connected');
socket.on('message', (msg) => {
console.log('Received:', msg.toString());
// Broadcast to all connected clients
wss.clients.forEach((client) => {
if (client.readyState === 1) client.send(msg.toString());
});
});
socket.on('close', () => console.log('🔴 Client disconnected'));
});
const PORT = 3000;
server.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));
✅ What’s happening here:
- We create an HTTP server that serves our HTML file.
- On top of it, we attach a WebSocket server (
wss). - Each new message is broadcast to all connected clients.
Simple, minimal, and powerful.
5. Creating the Chat Frontend
Now, create the folder public and inside it, a file called index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real-Time Chat</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #f8f9fa;
margin: 0;
display: flex;
flex-direction: column;
height: 100vh;
}
header {
background: #007bff;
color: white;
padding: 12px;
text-align: center;
font-weight: bold;
}
#chat {
flex: 1;
background: white;
padding: 10px;
overflow-y: auto;
border: 1px solid #ddd;
}
form {
display: flex;
padding: 10px;
border-top: 1px solid #ccc;
background: #fff;
}
input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background: #007bff;
color: white;
border: none;
margin-left: 8px;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<header>💬 Real-Time Chat</header>
<div id="chat"></div>
<form id="form">
<input id="message" placeholder="Type a message..." autocomplete="off" />
<button>Send</button>
</form>
<script>
const chat = document.getElementById('chat');
const form = document.getElementById('form');
const input = document.getElementById('message');
const username = prompt('Enter your name:') || 'Anonymous';
const ws = new WebSocket(`ws://${window.location.host}`);
ws.onopen = () => addMessage('🟢 Connected to the chat');
ws.onmessage = (event) => addMessage(event.data);
form.addEventListener('submit', (e) => {
e.preventDefault();
if (input.value.trim()) {
ws.send(`${username}: ${input.value}`);
input.value = '';
}
});
function addMessage(msg) {
const div = document.createElement('div');
div.textContent = msg;
chat.appendChild(div);
chat.scrollTop = chat.scrollHeight;
}
</script>
</body>
</html>
✅ What this does:
- Prompts for a username when you open the page.
- Connects to the WebSocket server.
- Displays messages in real time for all users.
That’s your entire front-end, no build tools, no libraries.
6. Run the App
Start the server:
node server.js
Then open http://localhost:3000 in two different browser tabs.
Type a message in one tab and watch it appear instantly in both.
🎉 Boom, you now have a working real-time chat built completely from scratch.
7. Understanding What’s Happening
Let’s trace the data flow:
- Each browser connects to the WebSocket server:
const ws = new WebSocket('ws://localhost:3000');
2. The connection stays open for no more HTTP request/response cycles.
3. When a user sends a message, the client calls:
ws.send('User: Hello everyone!');
4. The server receives it and loops through all clients to send it back:
wss.clients.forEach(client => client.send(message));
5. Each connected browser instantly receives and displays the message.
That’s the power of persistent two-way communication.
8. Adding Small Enhancements
Once you’ve got the basics working, here’s how to level up your chat app.
1. Add Join and Leave Notifications
wss.on('connection', (socket) => {
wss.clients.forEach((client) => {
if (client.readyState === 1) client.send('🟢 A new user joined');
});
socket.on('close', () => {
wss.clients.forEach((client) => {
if (client.readyState === 1) client.send('🔴 A user left');
});
});
});
2. Add Timestamps
const time = new Date().toLocaleTimeString();
ws.send(`[${time}] ${username}: ${input.value}`);
3. Add Basic Styles for Messages
div.style.margin = '5px 0';
div.style.padding = '6px 8px';
div.style.background = '#f1f3f5';
div.style.borderRadius = '5px';
Even small touches like this make your chat feel alive.
9. Troubleshooting Common Issues
Here are a few common problems you might run into:

Pro tip: use browser DevTools → Network → WS tab to inspect live WebSocket messages.
10. Where to Go Next
Now that you’ve built your first chat app, you can easily extend it:
- Add chat rooms or channels
- Store messages in a database (MongoDB, SQLite, etc.)
- Add authentication (JWT or sessions)
- Show “user typing…” indicators
- Deploy to a cloud platform (Render, Fly.io, or Railway)
Every big real-time feature you’ve seen online starts from this exact foundation.
Conclusion
You just built a real-time chat application from scratch using nothing but JavaScript and WebSockets.
No frameworks. No abstractions. Just the pure, simple flow of a live connection between browser and server.
Now you understand the core concept behind tools like Socket.IO, Firebase, and even multiplayer game servers.
Pro Tip: Always start from the basics, as it makes you 10× better developer when using frameworks later.
Call to Action
Did you follow this guide and get your chat app running?
Share your experience (or screenshot) in the comments and bookmark this post for your next real-time project.


Leave a Reply