Build a Real-Time Chat Application with Node.js
Lesson 6 of 10 60% of course

Frontend JavaScript — Connect & Send Messages

1 · 5 min · 6/7/2026

Learn Frontend JavaScript — Connect & Send Messages in our free Build a Real-Time Chat Application with Node.js series. Step-by-step explanations, examples, and interview tips on Toolliyo Academy.

Sign in to track progress and bookmarks.

Frontend JavaScript — Connect & Send Messages
Lesson 6 of 10 · Part 2 — Build the App · Build a Real-Time Chat Application with Node.js
Course: Build a Real-Time Chat Application with Node.js · Lesson: 6/10 · Read time: ~28 min · Level: Beginner

Frontend JavaScript — Connect & Send Messages

This is where the UI comes alive. client.js connects to the server via Socket.IO, joins a room when the user clicks “Join Chat”, renders incoming messages as bubbles, and sends new messages when the user hits Send or Enter.

Create public/client.js with the full code below.

Full client.js

// client.js — Browser-side chat logic
const socket = io(); // Connect to same host (localhost:3000 in dev)

// DOM elements
const loginScreen = document.getElementById('login-screen');
const chatScreen = document.getElementById('chat-screen');
const usernameInput = document.getElementById('username');
const roomInput = document.getElementById('room');
const joinBtn = document.getElementById('join-btn');
const loginError = document.getElementById('login-error');
const roomTitle = document.getElementById('room-title');
const messagesEl = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const typingIndicator = document.getElementById('typing-indicator');
const onlineUsersEl = document.getElementById('online-users');

let currentUsername = '';
let typingTimeout = null;

function showError(msg) {
  loginError.textContent = msg;
  loginError.classList.remove('hidden');
}

function hideError() {
  loginError.classList.add('hidden');
}

function formatTime(isoString) {
  return new Date(isoString).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}

function scrollToBottom() {
  messagesEl.scrollTop = messagesEl.scrollHeight;
}

function appendMessage(data) {
  const row = document.createElement('div');

  if (data.type === 'system') {
    row.className = 'message-row system';
    row.innerHTML = '<div class="bubble">' + data.message + '</div>';
  } else {
    const isMine = data.username === currentUsername;
    row.className = 'message-row ' + (isMine ? 'mine' : 'other');
    row.innerHTML =
      '<div class="meta">' + (isMine ? 'You' : data.username) + '</div>' +
      '<div class="bubble">' + escapeHtml(data.message) + '</div>' +
      '<div class="time">' + formatTime(data.timestamp) + '</div>';
  }

  messagesEl.appendChild(row);
  scrollToBottom();
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

function renderOnlineUsers(users) {
  if (!users || users.length === 0) {
    onlineUsersEl.innerHTML = '<strong>Online</strong><span>Just you</span>';
    return;
  }
  onlineUsersEl.innerHTML = '<strong>Online (' + users.length + ')</strong><span>' + users.join(', ') + '</span>';
}

// ——— Join room ———
joinBtn.addEventListener('click', function () {
  hideError();
  const username = usernameInput.value.trim();
  const room = roomInput.value.trim().toLowerCase();

  if (!username) return showError('Please enter your name.');
  if (!room) return showError('Please enter a room name.');
  if (username.length < 2) return showError('Name must be at least 2 characters.');

  currentUsername = username;
  roomTitle.textContent = '#' + room;
  loginScreen.classList.add('hidden');
  chatScreen.classList.remove('hidden');
  messageInput.focus();

  socket.emit('join-room', { username: username, room: room });
});

// ——— Send message ———
function sendMessage() {
  const text = messageInput.value.trim();
  if (!text) return;
  socket.emit('send-message', text);
  messageInput.value = '';
  messageInput.focus();
}

sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keydown', function (e) {
  if (e.key === 'Enter') sendMessage();
});

// Typing indicator — emit on keypress, debounced
messageInput.addEventListener('input', function () {
  socket.emit('typing');
});

// ——— Server events ———
socket.on('receive-message', function (data) {
  appendMessage(data);
});

socket.on('user-typing', function (data) {
  if (data.username === currentUsername) return;
  typingIndicator.textContent = data.username + ' is typing…';
  typingIndicator.classList.remove('hidden');
  clearTimeout(typingTimeout);
  typingTimeout = setTimeout(function () {
    typingIndicator.classList.add('hidden');
  }, 1500);
});

socket.on('online-users', function (users) {
  renderOnlineUsers(users);
});

How the client works — step by step

1. Connect to Socket.IO

const socket = io();

One line — connects to the same host and port as the page (localhost:3000 in dev). No URL needed when client and server share origin.

💡 Tip: If you ever host frontend and backend on different domains, pass the server URL: io("https://your-api.com").

2. Join room flow

When user clicks Join:

  1. Validate username (not empty, min 2 chars) and room name
  2. Hide login screen, show chat screen
  3. socket.emit('join-room', { username, room }) — tells server to add user to room
⚠️ Common Mistake: Event names must match the server exactly. join-room on client must pair with socket.on("join-room") on server — typos like joinRoom fail silently with no messages.

3. Sending messages

socket.emit('send-message', text);

We send only the text — server already knows username and room from the join step.

4. Receiving messages

socket.on('receive-message', function (data) { ... });

Server broadcasts three types via the same event:

  • type: 'system' — join/leave notifications (centered, italic)
  • type: 'chat' — normal messages with username + timestamp

5. Rendering chat bubbles

appendMessage() creates DOM elements dynamically:

  • Compares data.username === currentUsername to decide mine vs other styling
  • Uses escapeHtml() to prevent XSS if someone sends <script> in chat
  • Formats time with toLocaleTimeString()
  • Calls scrollToBottom() so newest message is visible

6. Typing indicator (bonus — already included)

On every keystroke in message input, client emits typing. Server relays user-typing to others. We hide the indicator after 1.5 seconds of silence.

7. Online users list

Server emits online-users array. renderOnlineUsers() updates the header sidebar.

👨‍🏫 Teaching note: Pair students for a “debug duel”: one breaks an event name on purpose, the other finds it in DevTools → Network → WS tab. Builds real debugging skills.

Security note for instructors

escapeHtml() is essential. Never use innerHTML = userMessage directly — a student prank can become a script injection lesson you did not plan.

Checkpoint

  • client.js saved in public/
  • ✅ Join validates inputs before emitting
  • ✅ Send works on button click and Enter key
  • ✅ Messages auto-scroll to bottom

Next lesson: run the app and test with two browser tabs!

Continue learning

Previous: Frontend CSS — Modern Chat UI Design

Next: Running the App & Testing in Two Tabs

Course home: All 10 lessons

Test your knowledge

Quizzes linked to this course—pass to earn certificates.

Browse all quizzes
Build a Real-Time Chat Application with Node.js

On this page

Full client.js How the client works — step by step 1. Connect to Socket.IO 2. Join room flow 3. Sending messages 4. Receiving messages 5. Rendering chat bubbles 6. Typing indicator (bonus — already included) 7. Online users list Security note for instructors Checkpoint Continue learning
Part 1 — Getting Started
Introduction — What We Are Building Project Setup — Folder, npm & Dependencies
Part 2 — Build the App
Building the Server — Express & Socket.IO Frontend HTML — Login Screen & Chat Layout Frontend CSS — Modern Chat UI Design Frontend JavaScript — Connect & Send Messages
Part 3 — Run, Learn & Extend
Running the App & Testing in Two Tabs How Socket.IO Works — Deep Dive
Part 4 — Ship It
Bonus Features — Typing, Online Users & Timestamps Deploy to Railway, Troubleshooting & Summary
Toolliyo Assistant
Ask about tutorials, ebooks, training, pricing, mentor services, and support. I use public site content only—not admin or internal tools.

care@toolliyo.com

Need callback? Share your details