Tutorials Build a Real-Time Chat Application with Node.js

Frontend JavaScript — Connect & Send Messages

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.

On this page
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

Interview prep for this lesson

Practice these questions aloud after reading—each links to a full structured answer.

Junior Detailed
Explain Design in the context of Build a Real-Time Chat Application with Node.js.
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Design in plain language for…
Mid Detailed
What are common mistakes teams make with Implementation when using Build a Real-Time Chat Application with Node.js?
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Implementation in plain lang…
Senior Detailed
How would you debug a production issue related to Trade-offs in a Build a Real-Time Chat Application with Node.js application?
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Trade-offs in plain language…
Mid Detailed
Compare two approaches to Testing—when would you choose each?
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Testing in plain language fo…
Junior Detailed
Describe a real-world scenario where Demo mattered in a Build a Real-Time Chat Application with Node.js project.
Short answer: Interviewers want a crisp definition, a practical example from your projects, and awareness of trade-offs—not textbook dumps. How to structure your answer (60–90 seconds) Define Demo in plain language for B…
Questions on this lesson 0

Sign in to ask a question or upvote helpful answers.

No questions yet — be the first to ask!

Build a Real-Time Chat Application with Node.js
Course syllabus
Part 1 — Getting Started
Part 2 — Build the App
Part 3 — Run, Learn & Extend
Part 4 — Ship It
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