Build a Real-Time Chat App
Use WebSockets to build a multi-user chat app in Node.js.
Introduction
30 Second Summary
Think about how group chats work. You type a message and everyone else sees it instantly, no refreshing, no waiting. That kind of real-time communication is powered by a technology called WebSockets.
In this project, you will build a real-time chat application where multiple browser tabs can exchange messages instantly. You will create both the server that manages connections and the browser client that sends and displays messages.
What You'll Build
A multi-user chat application running locally, powered by a Node.js WebSocket server and a browser-based client.
By the end of this project, you'll have:
- A WebSocket server that accepts multiple simultaneous connections and broadcasts messages to all connected clients.
- A browser-based chat client with a message display area, username support, and an input form for sending messages.
- A clear understanding of how WebSockets differ from HTTP by experiencing real-time bidirectional communication firsthand.
- Secret Mission: a "user is typing" indicator that shows when someone is composing a message.
Are there any prerequisites?
None. This project includes full setup instructions for every tool you need. You will install Node.js, set up your code editor, and create everything from scratch.
Set Up Your Project
Every real-time application starts with a solid foundation. Before writing any server or client code, you need a project folder, a package manager configuration, and the WebSocket library that handles the connection mechanics.
In this step, you'll create a new Node.js project and install the ws package. This is the only external dependency your chat app needs.
In this step, get ready to:
- Create your project folder and initialize it as a Node.js project.
- Install the WebSocket library.
Create your project folder
Your chat app needs a dedicated folder to live in. You'll create this folder on your Desktop so it's easy to find later.
- Open a terminal window.
How do I open a terminal?
Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir websocket-chat
cd websocket-chat
- Confirm the folder exists by running:
ls
The folder is empty right now, so ls produces no output. That's expected.
Seeing 'No such file or directory'?
Make sure you ran cd ~/Desktop first.
On Windows, use cd %USERPROFILE%\Desktop if ~/Desktop doesn't work in Command Prompt.
help me with terminal folder navigation issues
- Open the websocket-chat folder in VS Code.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Select the websocket-chat folder on your Desktop, then click Open.
You should see WEBSOCKET-CHAT in the VS Code Explorer sidebar on the left.
- Open a terminal inside VS Code by pressing Ctrl+` (macOS and Windows).
- Initialize your Node.js project by running:
npm init -y
What does this command do?
npm init -y creates a package.json file with default settings.
This file tracks your project's name, version, and any libraries it depends on.
- Confirm that package.json appeared in your Explorer sidebar.
Don't see package.json?
Make sure your VS Code terminal prompt shows the websocket-chat folder name.
If it shows a different folder, run cd ~/Desktop/websocket-chat and then run npm init -y again.
help me find where my package.json was created
Your project needs one small configuration change. By default, Node.js uses an older module system. Adding "type": "module" switches your project to use ES modules, which lets you use modern import statements.
- Click on package.json in the Explorer sidebar to open it.
- Find this line:
"version": "1.0.0",
- Add a new line directly below it so the file now looks like this:
"version": "1.0.0",
"type": "module",
- Save package.json by pressing Cmd+S (macOS) or Ctrl+S (Windows).
Install the ws library
The ws library is a lightweight WebSocket implementation for Node.js. It has zero dependencies and handles the low-level WebSocket protocol so you can focus on building your chat logic.
- Install the WebSocket library by running:
npm install ws
What does this command do?
npm install ws downloads the ws package from the npm registry and saves it as a dependency in your package.json.
It also creates a node_modules folder where the library's actual code lives.
- Open package.json again and scroll to the bottom.
You should see a new "dependencies" section containing "ws" with a version number starting with ^8.
npm install not working?
Check that you have an internet connection. npm needs to download the package from the registry.
Make sure your terminal is inside the websocket-chat folder. Run pwd to check your current location.
help me fix npm install errors
✔️ Awesome, I've got everything!
Great. Double check you've saved your package.json file.
ⓧ I'd like to double check the full code
Your package.json should look like this. The exact version number after ^8. may differ slightly.
{
"name": "websocket-chat",
"version": "1.0.0",
"type": "module",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"ws": "^8.21.0"
}
}
Your project is configured and ready to go. Next up, you'll build the WebSocket server that accepts connections and broadcasts messages between clients.
Build the WebSocket Server
Your project folder is ready with Node.js and the ws library installed. Now it's time to write the server that powers your chat app.
A WebSocket server keeps a persistent connection open with every client that connects. Instead of waiting for requests, it can push messages to clients the instant something happens. In this step, you'll build a server that accepts connections, receives messages, and broadcasts them to everyone else in the chat.
In this step, get ready to:
- Create a WebSocket server that accepts client connections.
- Add message broadcasting so every connected client receives new messages.
- Serve your chat page over HTTP so browsers can load it.
Create server.js
Your chat app needs two things running on the same port: an HTTP server to deliver the web page, and a WebSocket server to handle real-time messaging. Node.js lets you attach both to one server instance.
- In VS Code, create a new file called server.js in your websocket-chat folder.
- Add the following code to server.js:
import { createServer } from 'http';
import { readFileSync } from 'fs';
import { WebSocketServer } from 'ws';
// Create an HTTP server to deliver the chat page to browsers
const server = createServer((req, res) => {
if (req.url === '/') {
const html = readFileSync('./index.html', 'utf8');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
} else {
res.writeHead(404);
res.end('Not found');
}
});
// Attach a WebSocket server to the same HTTP server
const wss = new WebSocketServer({ server });
// Listen for new WebSocket connections
wss.on('connection', function connection(ws) {
console.log('New client connected');
ws.on('error', console.error);
ws.on('close', function close() {
console.log('Client disconnected');
});
});
// Start the combined server on port 8080
server.listen(8080, () => {
console.log('WebSocket server running on http://localhost:8080');
});
What does this code do?
- The three import lines pull in Node.js built-in modules (http and fs) plus the ws library you installed earlier.
- createServer builds an HTTP server that reads and returns index.html when a browser visits the root URL.
- new WebSocketServer({ server }) attaches the WebSocket layer to that same HTTP server so both share port 8080.
- The connection event fires every time a new browser tab opens a WebSocket link to your server.
- Save server.js.
You won't see a change yet. The server needs its message-handling logic and the index.html file before it's ready to run.
Handle messages and broadcast
When a client sends a chat message, the server needs to relay it to every other connected client. This is called broadcasting.
- In server.js, find this line inside the connection handler:
ws.on('error', console.error);
- Add the following message handler directly below that line:
ws.on('message', function message(data) {
const parsed = JSON.parse(data);
// Broadcast to every other connected client
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === 1) {
client.send(JSON.stringify(parsed));
}
});
});
What does this code do?
- ws.on('message', ...) fires whenever a connected client sends data to the server.
- JSON.parse(data) converts the incoming text into a JavaScript object containing the sender's username and message text.
- wss.clients is a set of every currently connected WebSocket client.
- The if check skips the sender (client !== ws) and only sends to clients whose connection is open (readyState === 1).
Why exclude the sender?
The client that sent the message already knows what it typed. It adds the message to its own display locally. Broadcasting back to the sender would create duplicate messages on their screen.
- Save server.js.
You won't see a change yet. The next section adds the client interface that connects to this server.
Seeing a red squiggly line in VS Code?
If VS Code underlines an import or variable, make sure your package.json includes "type": "module". Without it, Node.js expects require() syntax instead of import.
Also check that you ran npm install ws in the previous step. The node_modules folder should exist in your project directory.
help me fix an import error in my WebSocket server
Serve the HTML client
Your server already includes the HTTP logic that will deliver your chat page to browsers. Let's trace through how it works and verify the server starts correctly.
How does the HTTP server deliver your chat page?
When a browser visits http://localhost:8080, the createServer callback checks the URL. If it's the root path (/), it reads index.html from disk and sends it back as the response.
The browser then executes the JavaScript inside that HTML file, which opens a WebSocket connection back to the same server on port 8080. One port handles both jobs: delivering the page and managing the real-time connection.
- Verify the server starts by running this in your terminal:
node server.js
You should see an error mentioning that index.html cannot be found. That's expected. The server is trying to read a file you haven't created yet.
- Press Ctrl+C in your terminal to stop the server.
Why does it error?
The readFileSync('./index.html', 'utf8') call tries to read the file at startup (when a request hits /). Since index.html doesn't exist yet, Node.js throws an error. You'll create this file in the next step, and then the server will work end to end.
✔️ Awesome, I've got everything!
Great. Double check you've saved server.js and confirm the message handler is inside the connection callback.
ⓧ I'd like to double check the full code
Here is the complete server.js file. Make sure yours matches exactly:
import { createServer } from 'http';
import { readFileSync } from 'fs';
import { WebSocketServer } from 'ws';
const server = createServer((req, res) => {
if (req.url === '/') {
const html = readFileSync('./index.html', 'utf8');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
} else {
res.writeHead(404);
res.end('Not found');
}
});
const wss = new WebSocketServer({ server });
wss.on('connection', function connection(ws) {
console.log('New client connected');
ws.on('error', console.error);
ws.on('message', function message(data) {
const parsed = JSON.parse(data);
// Broadcast to every other connected client
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === 1) {
client.send(JSON.stringify(parsed));
}
});
});
ws.on('close', function close() {
console.log('Client disconnected');
});
});
server.listen(8080, () => {
console.log('WebSocket server running on http://localhost:8080');
});
Your server is ready to accept connections and broadcast messages. Next up, you'll build the browser-based chat interface that connects to it.
Build the Chat Client
Your server can accept connections and relay messages between clients. Now you need a front end that people can actually interact with.
In this step, you'll build the browser-based chat interface. It includes a username screen, a message display area, and a text input for sending messages.
In this step, get ready to:
- Create an HTML page with a username screen and chat interface.
- Connect to the WebSocket server and handle connection events.
- Send and display messages in real time.
Create index.html
The chat client lives in a single HTML file. It has two screens: a username entry screen that appears first, and the chat screen that appears after the user joins.
- In VS Code, create a new file called index.html inside your websocket-chat folder.
- Copy the following HTML structure into the file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Chat</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #1a1a2e;
color: #eee;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.chat-container {
width: 100%;
max-width: 600px;
height: 80vh;
display: flex;
flex-direction: column;
background: #16213e;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
}
.chat-header {
padding: 16px 20px;
background: #0f3460;
font-size: 18px;
font-weight: 600;
}
.messages {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 10px;
}
.message {
padding: 10px 14px;
background: #0f3460;
border-radius: 8px;
max-width: 80%;
}
.message .username {
font-size: 12px;
color: #e94560;
margin-bottom: 4px;
font-weight: 600;
}
.message .text {
font-size: 14px;
line-height: 1.4;
}
.message.system {
background: transparent;
color: #888;
font-size: 12px;
text-align: center;
align-self: center;
}
.input-area {
padding: 16px 20px;
background: #0f3460;
display: flex;
gap: 10px;
}
.input-area input {
flex: 1;
padding: 10px 14px;
border: none;
border-radius: 6px;
background: #1a1a2e;
color: #eee;
font-size: 14px;
outline: none;
}
.input-area button {
padding: 10px 20px;
border: none;
border-radius: 6px;
background: #e94560;
color: white;
font-weight: 600;
cursor: pointer;
font-size: 14px;
}
.input-area button:hover {
background: #d63851;
}
.username-setup {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 40px;
height: 100%;
}
.username-setup h2 {
font-size: 24px;
}
.username-setup input {
padding: 12px 16px;
border: none;
border-radius: 6px;
background: #1a1a2e;
color: #eee;
font-size: 16px;
outline: none;
width: 100%;
max-width: 300px;
text-align: center;
}
.username-setup button {
padding: 12px 24px;
border: none;
border-radius: 6px;
background: #e94560;
color: white;
font-weight: 600;
cursor: pointer;
font-size: 16px;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">WebSocket Chat</div>
<div id="username-screen" class="username-setup">
<h2>Enter your name</h2>
<input type="text" id="username-input" placeholder="Your name..." />
<button id="join-btn">Join Chat</button>
</div>
<div id="chat-screen" class="hidden">
<div class="messages" id="messages"></div>
<div class="input-area">
<input type="text" id="message-input" placeholder="Type a message..." />
<button id="send-btn">Send</button>
</div>
</div>
</div>
<script>
// We will add chat functionality here next
</script>
</body>
</html>
What does this HTML do?
- The username-screen div shows first. It has a text input and a Join Chat button.
- The chat-screen div starts hidden (the hidden class sets display: none). It contains the message display area and the message input.
- The CSS creates a dark-themed card layout centered on the page. Messages will stack vertically inside the scrollable .messages container.
- Save index.html.
- Drag index.html from your file explorer into a browser window to open it directly.
- You should see a dark-themed screen with "Enter your name", a text input, and a "Join Chat" button.
Don't see the username screen?
- Make sure you saved the file as index.html (not .txt) inside the websocket-chat folder.
- Check that the file starts with <!DOCTYPE html>. If the browser shows raw code, it may not recognize the file as HTML.
Help me figure out why my HTML page isn't rendering correctly.
Connect to the WebSocket server
The browser has a built-in WebSocket object that handles the connection to your server. You need to grab references to the HTML elements, then open a connection when the user clicks Join Chat.
- In index.html, find the empty <script> block and replace the comment with the following code:
let ws;
let username = '';
const usernameScreen = document.getElementById('username-screen');
const chatScreen = document.getElementById('chat-screen');
const usernameInput = document.getElementById('username-input');
const joinBtn = document.getElementById('join-btn');
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
joinBtn.addEventListener('click', joinChat);
usernameInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') joinChat();
});
function joinChat() {
username = usernameInput.value.trim();
if (!username) return;
usernameScreen.classList.add('hidden');
chatScreen.classList.remove('hidden');
connectWebSocket();
}
function connectWebSocket() {
ws = new WebSocket('ws://localhost:8080');
ws.onopen = function() {
addSystemMessage('Connected to chat!');
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
addMessage(data.username, data.text);
};
ws.onclose = function() {
addSystemMessage('Disconnected from server.');
};
ws.onerror = function() {
addSystemMessage('Connection error.');
};
}
What does this code do?
- The let ws variable will hold the WebSocket connection once the user joins.
- All the document.getElementById lines grab references to the HTML elements so you can manipulate them with JavaScript.
- The joinChat() function hides the username screen, shows the chat screen, and opens the WebSocket connection.
- The connectWebSocket() function creates a new WebSocket pointing at ws://localhost:8080. It handles four events: onopen (connected), onmessage (received data), onclose (disconnected), and onerror (something went wrong).
- Save index.html.
- You won't see a change yet in the browser. The next chunk adds the message display functions that connectWebSocket() calls.
Send and display messages
When the user types a message and hits Send (or presses Enter), the client packages it as JSON and sends it to the server. You also need functions to display incoming messages on screen.
- In index.html, add the following code directly below the connectWebSocket() function (still inside the <script> block):
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') sendMessage();
});
function sendMessage() {
const text = messageInput.value.trim();
if (!text || !ws) return;
const message = { username: username, text: text };
ws.send(JSON.stringify(message));
addMessage(username, text);
messageInput.value = '';
}
function addMessage(user, text) {
const div = document.createElement('div');
div.className = 'message';
div.innerHTML = '<div class="username">' + user + '</div><div class="text">' + text + '</div>';
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function addSystemMessage(text) {
const div = document.createElement('div');
div.className = 'message system';
div.textContent = text;
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
What does this code do?
- The sendMessage() function reads the input field, packages the text and username into a JSON object, and sends it via ws.send(). It also displays the message locally so the sender sees their own text immediately.
- The addMessage() function creates a new div element with the sender's username and message text, then appends it to the messages container.
- The addSystemMessage() function displays connection status messages (like "Connected to chat!") with a different style.
- Both display functions auto-scroll the container to the bottom using scrollTop = scrollHeight so new messages are always visible.
- Save index.html.
- You won't see messages flowing yet. In the next step, you'll start the server and test everything with multiple browser tabs.
Seeing a syntax error in the browser console?
- Open your browser's developer console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) to check for errors.
- Make sure all three code sections are inside the same <script> block, not in separate script tags.
- Check that the connectWebSocket() closing brace appears before the sendBtn.addEventListener line.
Help me fix a JavaScript error in my WebSocket chat client.
✔️ Awesome, I've got everything!
Great. Double check you've saved index.html and move on to the next step.
ⓧ I'd like to double check the full code
Here is the complete index.html file. Compare yours against this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #1a1a2e; color: #eee; height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.chat-container { width: 100%; max-width: 600px; height: 80vh; display: flex; flex-direction: column; background: #16213e; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 40px rgba(0,0,0,0.4); }
.chat-header { padding: 16px 20px; background: #0f3460; font-size: 18px; font-weight: 600; }
.messages { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 10px; }
.message { padding: 10px 14px; background: #0f3460; border-radius: 8px; max-width: 80%; }
.message .username { font-size: 12px; color: #e94560; margin-bottom: 4px; font-weight: 600; }
.message .text { font-size: 14px; line-height: 1.4; }
.message.system { background: transparent; color: #888; font-size: 12px; text-align: center; align-self: center; }
.input-area { padding: 16px 20px; background: #0f3460; display: flex; gap: 10px; }
.input-area input { flex: 1; padding: 10px 14px; border: none; border-radius: 6px; background: #1a1a2e; color: #eee; font-size: 14px; outline: none; }
.input-area button { padding: 10px 20px; border: none; border-radius: 6px; background: #e94560; color: white; font-weight: 600; cursor: pointer; font-size: 14px; }
.input-area button:hover { background: #d63851; }
.username-setup { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 40px; height: 100%; }
.username-setup h2 { font-size: 24px; }
.username-setup input { padding: 12px 16px; border: none; border-radius: 6px; background: #1a1a2e; color: #eee; font-size: 16px; outline: none; width: 100%; max-width: 300px; text-align: center; }
.username-setup button { padding: 12px 24px; border: none; border-radius: 6px; background: #e94560; color: white; font-weight: 600; cursor: pointer; font-size: 16px; }
.hidden { display: none; }
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">WebSocket Chat</div>
<div id="username-screen" class="username-setup">
<h2>Enter your name</h2>
<input type="text" id="username-input" placeholder="Your name..." />
<button id="join-btn">Join Chat</button>
</div>
<div id="chat-screen" class="hidden">
<div class="messages" id="messages"></div>
<div class="input-area">
<input type="text" id="message-input" placeholder="Type a message..." />
<button id="send-btn">Send</button>
</div>
</div>
</div>
<script>
let ws;
let username = '';
const usernameScreen = document.getElementById('username-screen');
const chatScreen = document.getElementById('chat-screen');
const usernameInput = document.getElementById('username-input');
const joinBtn = document.getElementById('join-btn');
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
joinBtn.addEventListener('click', joinChat);
usernameInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') joinChat(); });
function joinChat() {
username = usernameInput.value.trim();
if (!username) return;
usernameScreen.classList.add('hidden');
chatScreen.classList.remove('hidden');
connectWebSocket();
}
function connectWebSocket() {
ws = new WebSocket('ws://localhost:8080');
ws.onopen = function() { addSystemMessage('Connected to chat!'); };
ws.onmessage = function(event) { const data = JSON.parse(event.data); addMessage(data.username, data.text); };
ws.onclose = function() { addSystemMessage('Disconnected from server.'); };
ws.onerror = function() { addSystemMessage('Connection error.'); };
}
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') sendMessage(); });
function sendMessage() {
const text = messageInput.value.trim();
if (!text || !ws) return;
const message = { username: username, text: text };
ws.send(JSON.stringify(message));
addMessage(username, text);
messageInput.value = '';
}
function addMessage(user, text) {
const div = document.createElement('div');
div.className = 'message';
div.innerHTML = '<div class="username">' + user + '</div><div class="text">' + text + '</div>';
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function addSystemMessage(text) {
const div = document.createElement('div');
div.className = 'message system';
div.textContent = text;
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
</script>
</body>
</html>
Your chat client is built. In the next step, you'll start the server and watch messages fly between multiple browser tabs in real time.
Test Multi-User Chat
Your server and client code are complete. Now it's time to see the real power of WebSockets in action.
In this step, you'll start the server and open multiple browser tabs to simulate different users chatting in real time. Each tab acts as a separate user connected to the same WebSocket server.
In this step, get ready to:
- Start the WebSocket server.
- Connect multiple browser tabs as different users.
- Send messages and verify real-time delivery across tabs.
Start the server
Your server.js file creates both an HTTP server (to serve the HTML page) and a WebSocket server (to handle real-time connections). Running this single file starts both.
- Start the server by running this command in your VS Code terminal:
node server.js
- You should see this message printed in your terminal:
WebSocket server running on http://localhost:8080
What does this mean?
This log confirms two things: your HTTP server is listening on port 8080 and can serve your index.html page, and the WebSocket server is attached and ready to accept connections.
The terminal will stay "busy" while the server runs. This is normal. The server needs to keep running to accept connections.
Seeing an error instead?
- If you see EADDRINUSE, port 8080 is already in use. Close any other servers running on that port and try again.
- If you see Cannot find module, make sure you ran npm install ws in the websocket-chat folder first.
- If you see a syntax error, double check that your package.json contains "type": "module". Without this, Node.js cannot use import statements.
My server won't start. Help me debug the error message I'm seeing.
Open multiple browser tabs
Each browser tab acts as a separate chat user. When a tab connects, the server logs a new connection and the client shows a "Connected to chat!" system message.
- Open your web browser (Chrome or Edge).
- Navigate to http://localhost:8080 in the address bar.
- You should see the "Enter your name" screen with an input field and a Join Chat button.
- Type a name (like Alice) and click Join Chat.
- You should see the chat screen appear with the system message "Connected to chat!" in the center.
Now open a second tab to simulate another user.
- Open a new browser tab with Cmd+T (macOS) or Ctrl+T (Windows).
- Navigate to http://localhost:8080 again.
- This time, enter a different name (like Bob) and click Join Chat.
What's happening behind the scenes?
Each time a tab joins, the browser creates a new WebSocket connection to your server. If you check your VS Code terminal, you should see "New client connected" logged once for each tab that joined.
Page not loading?
- Make sure your server is still running in the terminal. If you accidentally closed it, run node server.js again.
- Check the URL is exactly http://localhost:8080 with no extra slashes or characters.
My browser won't connect to localhost:8080. What should I check?
Send messages between tabs
This is the moment of truth. When you send a message from one tab, the server broadcasts it to every other connected tab instantly.
- In the first tab (Alice), type a message like Hello from Alice! in the message input field.
- Click Send or press Enter.
- Switch to the second tab (Bob). You should see Alice's message appear immediately with her username displayed in red above the message text.
- In the second tab, type Hey Alice! and send it.
- Switch back to the first tab. Bob's message should already be there.
Why is this different from HTTP?
With traditional HTTP, Alice's browser would have to keep asking the server "any new messages?" over and over (called polling). With WebSockets, the connection stays open and the server pushes new messages to Alice's browser the instant they arrive.
No refreshing, no polling, no delay. That's the power of bidirectional communication.
- Try opening a third tab, joining with a different name, and sending messages. Every connected tab receives messages from all other tabs.
- Check your VS Code terminal. You should see "New client connected" logged once for each tab that joined the chat.
Messages not appearing in other tabs?
- Make sure both tabs show "Connected to chat!" as a system message. If one tab shows "Connection error" or "Disconnected", refresh that tab and rejoin.
- Messages only go to OTHER tabs. The tab you send from shows the message immediately through the local addMessage call. The server broadcasts to everyone except the sender.
- If no messages arrive anywhere, check the browser console (F12 then Console tab) for errors.
Help me debug why messages aren't being received in my other browser tabs.
Secret mission
Add a Typing Indicator
Your chat app works great, but wouldn't it feel more alive if you could see when someone is composing a message? In this secret mission, you'll add a real-time typing indicator that shows when another user is actively typing.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
This project runs entirely locally, so there are no ongoing costs. The only resource using system resources is the running Node.js server process.
Resources you used:
- The websocket-chat project folder on your Desktop (contains server.js, index.html, package.json, and node_modules).
- The Node.js server process running on port 8080.
Keep everything running
No action needed. Choose this if you're still actively building or want to continue experimenting with WebSocket features.
- Your server continues running on http://localhost:8080 and you can open new browser tabs to chat at any time.
- Your project files remain in the websocket-chat folder on your Desktop.
Pause - I'll come back to this later
Stop the running server to free up the port and terminal, but keep your project files for later.
- Switch to the terminal where your server is running.
- Press Ctrl+C to stop the server.
- Your project files stay in the websocket-chat folder on your Desktop. When you're ready to resume, open the folder in VS Code and run node server.js again.
Delete - I don't want to use this again
Remove all project files and free up the port completely.
- Switch to the terminal where your server is running.
- Press Ctrl+C to stop the server.
- Delete the project folder by running the appropriate command for your operating system.
On macOS or Linux:
cd ~/Desktop
rm -rf websocket-chat
On Windows:
cd ~\Desktop
rmdir /s /q websocket-chat
- Confirm the folder is gone by running:
ls ~/Desktop
You should no longer see websocket-chat in the output.
Nice Work!
Nice Work!
You just built a real-time chat application powered by WebSockets. Multiple browser tabs can now exchange messages instantly over persistent connections, with no page refreshes required.
You've learned how to:
- Built a WebSocket server in Node.js using the ws library that accepts multiple simultaneous connections.
- Created a browser-based chat client using the native WebSocket API with username identification and structured JSON messaging.
- Implemented message broadcasting that relays every message to all connected clients in real time.
- Added a typing indicator with debouncing and timeout logic so users see when someone is composing a message.
Ready to quiz yourself?