-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
223: Building a Simple Chat UI with WebSockets
When you first decide to build a chat app, your instinct is probably to figure out how to get a message from User A to User B as fast as possible. If you're coming from a traditional REST background, you might be tempted to use "short polling"—basically setting up a setInterval that hits an API endpoint every two seconds to ask, "Any new messages for me?"
The Cost of Asking "Are We There Yet?"
I've seen this approach in plenty of early-stage prototypes, and while it works for five minutes, it's a nightmare in production. Think about the overhead: every single poll is a full HTTP request. You're sending headers, negotiating TLS, and hitting the database just to get a response that says "nothing new" 99% of the time. It's incredibly wasteful for the server and creates a laggy experience for the user. If you poll every two seconds, the average message is delayed by one second. In a fast-paced conversation, that feels like an eternity.
Opening a Persistent Pipe
This is where WebSockets come in. Instead of a series of discrete requests, a WebSocket starts as an HTTP request that "upgrades" to a persistent TCP connection. I like to think of it as switching from sending letters back and forth to picking up a phone call. Once the connection is open, the server can push data to the client the millisecond it arrives, without the client asking for it.
const socket = new WebSocket('ws://chat.example.com');
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
displayMessage(data.user, data.text);
};
function sendMessage(text) {
socket.send(JSON.stringify({ text }));
}
The beauty here is the lack of overhead. The server simply broadcasts the message to all connected clients, and the UI updates instantly. But here is where most developers trip up: they focus so much on the connection that they forget about how they're actually updating the DOM.
The Danger of Raw innerHTML in a Chat Stream
The naive way to update your chat window is to just append the new message using innerHTML +=. It's a single line of code, and it looks great in a demo. But if you do this, you're opening a massive security hole. If a user sends a message like <img src=x onerror="alert('Hacked!')">, and you push that directly into your HTML, you've just given every user in your chat the ability to execute arbitrary JavaScript in other people's browsers. This is a textbook XSS (Cross-Site Scripting) vulnerability.
Furthermore, innerHTML += is a performance killer. Every time you use it, the browser has to destroy the entire existing DOM tree inside that element and rebuild it from scratch. In a chat room with 200 messages, adding the 201st message causes the browser to re-parse 200 lines of HTML. You'll notice the UI start to stutter as the conversation grows.
The better way is to create elements programmatically or use textContent to ensure the browser treats the message as literal text, not HTML. I prefer using insertAdjacentHTML with a sanitized template or simply building the nodes via document.createElement.
function displayMessage(user, text) {
const msgDiv = document.createElement('div');
msgDiv.className = 'message';
const userSpan = document.createElement('span');
userSpan.className = 'username';
userSpan.textContent = user; // Safe from XSS
const textSpan = document.createElement('span');
textSpan.textContent = text; // Safe from XSS
msgDiv.appendChild(userSpan);
msgDiv.appendChild(textSpan);
document.getElementById('chat-box').appendChild(msgDiv);
}
Handling the Silent Disconnect
One last thing I want to warn you about: WebSockets are fragile. They can be dropped by a flaky Wi-Fi connection, a load balancer timeout, or a server restart. The worst part is that sometimes the connection "dies" silently, and your UI still looks connected, but messages simply stop arriving. Your users will think the app is frozen.
You can't just assume the WebSocket object will always be active. I always implement a simple heartbeat (a "ping") and a reconnection strategy. If the onclose event fires, don't just log it to the console—trigger a function that attempts to reconnect after a few seconds using an exponential backoff. It's the difference between an app that feels like a toy and one that feels like professional software.
📋 Practical Task
Build a Secure, Auto-Reconnecting Chat Interface
Your goal is to create a functional chat UI that avoids the pitfalls discussed in this lesson. You will be provided with a mock WebSocket server URL (or you can use a public echo server like wss://echo.websocket.org for testing).
Requirements:
- Secure Rendering: Create a function to append messages to a
<div id="chat-window">. You must NOT useinnerHTMLto insert the message text; usetextContentorcreateElementto prevent XSS. - Connection Lifecycle: Implement the WebSocket connection. When the connection is successfully opened, update a status indicator on the page to "Connected".
- Resilience: Implement a reconnection mechanism. If the
oncloseevent is triggered, the app should attempt to reconnect every 5 seconds until the connection is restored. - Interaction: Create an input field and a send button that transmits a JSON string containing the message and a username.
There are no comments for now.