DEV Community

Cover image for Building a Real-time Chat System with Node.js, and WebSockets: A Step-by-Step Guide
Jaimal Dullat
Jaimal Dullat

Posted on • Originally published at Medium

Building a Real-time Chat System with Node.js, and WebSockets: A Step-by-Step Guide

Table of Contents


Introduction

Ah, real-time chat systems. The backbone of modern communication. But what makes them so speedy and reliable? It’s Node.js and Websockets, baby!

Node.js is a server-side JavaScript platform that helps build fast and scalable network applications. Meanwhile, Websockets is a protocol that allows for real-time communication between a client and server. Combine the two and you’ve got a recipe for a highly responsive chat system.

Building a chat system with Node.js and Websockets has its perks. For one, it can handle a large number of users with ease. Plus, it’s extremely reliable, so you don’t have to worry about messages getting lost in the ether.

So, why not build your own real-time chat system with Node.js and Websockets? It’s a fun project that’ll sharpen your skills and maybe even impress some friends. Plus, you can add your own personalized touches, like custom emojis or user authentication. Let’s get started!


What is Node.js?

Node.js is an open-source, cross-platform JavaScript runtime environment that executes JavaScript code outside of a web browser. It allows developers to use JavaScript on the server-side and is particularly useful for building scalable network applications.

Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. It is commonly used for building web servers, real-time applications, and APIs.


WebSockets?

Websockets is a protocol that allows for a persistent, two-way connection between a client and a server. This protocol is useful for real-time applications where the client needs to receive updates from the server without constantly requesting updates.

WebSockets use a single TCP connection that remains open as long as the connection between the client and server is needed. This allows for much faster and more efficient communication compared to traditional HTTP requests.

WebSockets are supported by most modern web browsers and can be used with a variety of programming languages for both client-side and server-side development. Some popular implementations of Websockets include Socket.io, SignalR, and WAMP.

Overall, WebSockets provide a powerful and flexible way to enable real-time communication between web applications and servers.


Setting up the Development Environment

To set up the development environment for creating a basic Express server, you need to follow these steps:

  1. Download and install Node.js from the official website if you haven’t already.

  2. Open a new terminal or command prompt window and navigate to the directory where you want to create your server-side web application.

  3. Create a new directory for your application by running the command mkdir node-chat-app (You can replace “node-chat-app” with any name of your choice).

  4. Move into the newly created directory by running the command cd node-chat-app.

  5. Initialize a new Node.js project by running the command npm init. This will create a package.json file to store project dependencies and other relevant information.

  6. Install Express and ws(a Node.js WebSocket library) as a dependency by running the command npm install express ws.

Once you have completed these steps, you can proceed to create a new file named server.js in the root directory of your project and begin building your Express server.


Setting up the Node.js Server

First things first, let’s create an entry point file for our Express server by running the command touch server.js or you can create new file using your IDE. This file will serve as the starting point for our application. Feel free to name it whatever you like, but for the sake of simplicity, we’ll stick with the conventional name server.js.

Inside the server.js file, we’ll start by importing the necessary modules and creating an instance of the Express application. This can be done as follows:

  1. Requiring Modules:
 const express = require('express');
const http = require('http');
const WebSocket = require('ws');
Enter fullscreen mode Exit fullscreen mode

Here, three modules are being imported:

  • express: A popular web application framework for Node.js.

  • http: A core Node.js module for creating HTTP servers and clients.

  • ws: A third-party module for working with WebSockets in Node.js.

2. Creating an Express Application:

const app = express();
Enter fullscreen mode Exit fullscreen mode

This initializes a new instance of an Express application. This application can be used to define routes, middleware, and other server-related configurations.

3. Creating an HTTP Server:

const server = http.createServer(app);

Enter fullscreen mode Exit fullscreen mode

Here, an HTTP server is being created using the http module. The app (Express application) is passed as a request listener to the server. This means that when the server receives an HTTP request, it will be handled by the Express application.

4. Setting up a WebSocket Server:

const wss = new WebSocket.Server({ server });
Enter fullscreen mode Exit fullscreen mode

This initializes a new instance of a WebSocket server using the ws module. The WebSocket server is bound to the previously created HTTP server (server). This means that the WebSocket server will share the same underlying TCP/IP port as the HTTP server. This is a common pattern because it allows you to serve regular HTTP(S) content and WebSocket content over the same port, which can simplify deployment and firewall configurations.

5. Handling WebSocket Connections and Incoming Messages:

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        wss.clients.forEach((client) => {
            if (client !== ws && client.readyState === WebSocket.OPEN) {
                client.send(message);
            }
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

The wss.on('connection', ...) is an event listener that listens for new WebSocket connections. When a client connects to the WebSocket server, the callback function is executed with the connected WebSocket (ws) as its argument.

Inside the connection callback, there’s another event listener for the message event on the connected WebSocket (ws). This listener is triggered whenever the server receives a message from the connected client. The received message is passed to the callback as the message argument.

Within the message event callback, the code iterates over all connected WebSocket clients using wss.clients.forEach(...). For each client:

  • It checks if the client is not the one who sent the original message (client !== ws). This prevents echoing the message back to the sender.

  • It checks if the client’s connection is still open (client.readyState === WebSocket.OPEN).

  • If both conditions are met, it sends the received message to the client using client.send(message). This effectively broadcasts the message to all other connected clients.

6. Starting the HTTP Server:

server.listen(3000, () => {
  console.log('Server started on http://localhost:3000');
});
Enter fullscreen mode Exit fullscreen mode

This code starts the HTTP server on port 3000. Once the server is successfully started, the callback function is executed, logging the message “Server started on http://localhost:3000" to the console. This provides feedback that the server is running and listening for incoming connections on the specified port.

7. Complete code of server.js file

const express = require('express');
const http = require('http');
const WebSocket = require('ws');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        wss.clients.forEach((client) => {
            if (client !== ws && client.readyState === WebSocket.OPEN) {
                client.send(message);
            }
        });
    });
});

server.listen(3000, () => {
    console.log('Server started on http://localhost:3000');
});
Enter fullscreen mode Exit fullscreen mode

Designing the Chat UI

Create a public folder and inside it, create index.html and style.css\

In index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bot Chat</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
<div class="chat-container">
    <div class="messages"></div>
    <div class="chat-box">
        <div class="messages"></div>
        <div class="input-container">
            <input type="text" class="message-input" placeholder="Type a message...">
            <button class="send-btn">Send Message</button>
        </div>
    </div>
</div>
<script src="script.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

In style.css:

body {
    font-family: 'Arial', sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #f4f4f4;
}

.chat-container {
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    background-color: #fff;
    border-radius: 5px;
    overflow: hidden;
}

.chat-box {
    width: 400px;
    height: 500px;
    display: flex;
    flex-direction: column;
}

.messages {
    flex: 1;
    padding: 20px;
    overflow-y: auto;
}

.input-container {
    display: flex;
    align-items: center;
    padding: 10px;
    border-top: 1px solid #e0e0e0;
}

.message-input {
    flex: 1;
    padding: 10px;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    margin-right: 10px;
}
.send-btn {
    padding: 10px 20px;
    background-color: #007BFF;
    color: #fff;
    border: none;
    cursor: pointer;
    transition: background-color 0.3s;
}

.send-btn:hover {
    background-color: #0056b3;
}
Enter fullscreen mode Exit fullscreen mode

Implementing WebSocket Client

Create a script.js file inside the public folder:

const messagesDiv = document.querySelector('.messages');
const sendBtn = document.querySelector('.send-btn');
const messageInput = document.querySelector('.message-input');

const ws = new WebSocket('ws://localhost:3000');

ws.onopen = () => {
    console.log('Connected to the server');
};

ws.onmessage = (event) => {
    if (event.data instanceof Blob) {
        // Convert Blob to string
        const reader = new FileReader();
        reader.onload = function() {
            const text = reader.result;
            displayMessage(text);
        };
        reader.readAsText(event.data);
    } else {
        displayMessage(event.data);
    }
};
function displayMessage(message) {
    const msgDiv = document.createElement('div');
    msgDiv.textContent = `Other user: ${message}`;
    messagesDiv.appendChild(msgDiv);
}

sendBtn.addEventListener('click', () => {
    const message = messageInput.value;
    if (message.trim() === "") return; // Don't send empty messages

    ws.send(message);
    const msgDiv = document.createElement('div');
    msgDiv.textContent = `You: ${message}`;
    messagesDiv.appendChild(msgDiv);

    messageInput.value = ''; // Clear the input after sending
});
Enter fullscreen mode Exit fullscreen mode

Here’s a breakdown of what each part of the code does:

  1. DOM Element Selection:
const messagesDiv = document.querySelector('.messages');
const sendBtn = document.querySelector('.send-btn');
const messageInput = document.querySelector('.message-input');
Enter fullscreen mode Exit fullscreen mode

These lines select three elements from the DOM:

  • messagesDiv: A container where chat messages will be displayed.

  • sendBtn: A button that users can click to send messages.

  • messageInput: An input field where users can type their messages.

2. WebSocket Initialization:

const ws = new WebSocket('ws://localhost:3000');

Enter fullscreen mode Exit fullscreen mode

This line initializes a new WebSocket connection to the server located at ws://localhost:3000.

3. WebSocket Event Handlers:

onopen:

ws.onopen = () => {
  console.log('Connected to the server');
};
Enter fullscreen mode Exit fullscreen mode

This function is called when the WebSocket connection is successfully established. It logs a message to the console indicating that the connection has been made.

onmessage:

ws.onmessage = (event) => {
    if (event.data instanceof Blob) {
        // Convert Blob to string
        const reader = new FileReader();
        reader.onload = function() {
            const text = reader.result;
            displayMessage(text);
        };
        reader.readAsText(event.data);
    } else {
        displayMessage(event.data);
    }
};
Enter fullscreen mode Exit fullscreen mode

This function is called whenever a message is received from the server. If the received data is a Blob (binary large object, often used for representing file-like objects), it converts the blob to a string using the FileReader API. Otherwise, it directly displays the message.

4. Display Message Function:

function displayMessage(message) {
    const msgDiv = document.createElement('div');
    msgDiv.textContent = `Other user: ${message}`;
    messagesDiv.appendChild(msgDiv);
}
Enter fullscreen mode Exit fullscreen mode

This function creates a new div element, sets its content to the received message prefixed by "Other user:", and appends it to the messagesDiv.

5. Send Button Event Listener:

sendBtn.addEventListener('click', () => {
    const message = messageInput.value;
    if (message.trim() === "") return; // Don't send empty messages

    ws.send(message);
    const msgDiv = document.createElement('div');
    msgDiv.textContent = `You: ${message}`;
    messagesDiv.appendChild(msgDiv);

    messageInput.value = ''; // Clear the input after sending
});
Enter fullscreen mode Exit fullscreen mode

This code adds an event listener to the sendBtn. When the button is clicked:

  • It retrieves the message from the messageInput.

  • If the message is empty (after trimming whitespace), it returns without doing anything.

  • Otherwise, it sends the message to the server using the WebSocket connection.

  • It then creates a new div element, sets its content to the sent message prefixed by "You:", and appends it to the messagesDiv.

  • Finally, it clears the messageInput to prepare for the next message.

Serve the public folder using Express in server.js by adding the following line to the serve.js file

app.use(express.static('public'));
Enter fullscreen mode Exit fullscreen mode

Running the Chat System

Start the server:

node server.js
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000 in two different browser windows. Enter the message and click the "Send Message" button in one window, and you should see the message appear in both windows.


Final Result


Conclusion

Congratulations! You have successfully built a real-time chat system with Node.js and Websockets. Let’s summarize the key steps we covered in this guide. We started with setting up the development environment by installing Node.js and Websockets. Then we built the back-end using Express.js and integrated Websockets with the server to handle user connections, disconnections, and messages.

After building the front-end with HTML and CSS, we tested the application locally and deployed it to a server.

Node.js and Websockets offer many benefits for real-time chat systems. They provide fast and seamless communication, real-time updates, and efficient resource utilization. With their lightweight and scalable architecture, they can handle thousands of connections simultaneously.

The possibilities for future enhancements are endless. You can add more advanced features like real-time translation, voice and video chat, and AI-powered chatbots. The chat system you built here is just the starting point for an incredible chat experience.

Source Code: click here

🔗 Connect with me on:

Thank you for joining me on this journey through my blog!

  • Follow: Stay updated with the latest posts by hitting that follow button.

  • React: Show your appreciation with a like/other reactions if you enjoyed the article.

  • Comment: Engage in the conversation by leaving your thoughts, feedback, and questions.

  • Share: Spread the word by sharing the blog post on your social media platforms or with friends and colleagues.

Top comments (0)