Building a Real-Time Chat App with WebSockets and NodeJs

Building a Real-Time Chat App with WebSockets and Node.js

Building a Real-Time Chat App with WebSockets and Node.js

In today's digital landscape, real-time communication is pivotal. Whether it's for customer support, collaborative tools, or social platforms, the demand for instantaneous messaging is ever-growing. This tutorial will guide you through creating a scalable real-time chat application using Node.js and WebSockets.

1. Introduction to WebSockets

WebSockets provide a full-duplex communication channel over a single TCP connection, enabling real-time data exchange between clients and servers. Unlike traditional HTTP requests, WebSockets maintain an open connection, allowing for instantaneous data transfer without the overhead of repeated HTTP requests.

2. Setting Up the Project

a. Initialize the Project

mkdir real-time-chat
cd real-time-chat
npm init -y

b. Install Dependencies

Install the necessary packages:

  • express: Web framework for Node.js
  • ws: WebSocket library for Node.js
npm install express ws

3. Building the Backend with Node.js and WebSockets

a. Create the Server File

Create a file named server.js and add the following code:

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 });

app.use(express.static('public'));

wss.on('connection', (ws) => {
  console.log('New client connected');
  
  ws.on('message', (message) => {
    // Broadcast the message to all clients
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });
});

server.listen(3000, () => {
  console.log('Server is listening on http://localhost:3000');
});

4. Developing the Frontend Interface

a. Create the Public Directory

mkdir public
touch public/index.html

b. Design the Chat Interface

Add the following HTML code to public/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Real-Time Chat</title>
  <style>
    body { font-family: Arial, sans-serif; }
    #chat { height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px; }
    #message { width: 80%; }
    #send { width: 18%; }
  </style>
</head>
<body>
  <h1>Real-Time Chat</h1>
  <div id="chat"></div>
  <input type="text" id="message" placeholder="Type your message here..." />
  <button id="send">Send</button>

  <script>
    const ws = new WebSocket('ws://localhost:3000');
    const chat = document.getElementById('chat');
    const messageInput = document.getElementById('message');
    const sendButton = document.getElementById('send');

    ws.onmessage = (event) => {
      const message = document.createElement('div');
      message.textContent = event.data;
      chat.appendChild(message);
      chat.scrollTop = chat.scrollHeight;
    };

    sendButton.onclick = () => {
      if (messageInput.value) {
        ws.send(messageInput.value);
        messageInput.value = '';
      }
    };

    messageInput.addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        sendButton.click();
      }
    });
  </script>
</body>
</html>

5. Testing the Chat Application

Start the server:

node server.js

Open multiple browser tabs or windows and navigate to http://localhost:3000. Type messages in one window, and observe them appearing in real-time across all open instances.

6. Storing Chat Messages in a Database

a. Choose a Database

For this tutorial, we'll use MongoDB due to its flexibility and scalability. MongoDB stores data in JSON-like documents, making it suitable for chat applications.

b. Set Up MongoDB

  1. Install MongoDB: If you haven't already, install MongoDB on your system or use a cloud-based service like MongoDB Atlas.
  2. Install Mongoose: Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js.
npm install mongoose

Connect to MongoDB in your server.js file:

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/chat-app', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
}).then(() => {
  console.log('Connected to MongoDB');
}).catch((err) => {
  console.error('MongoDB connection error:', err);
});

c. Define the Message Schema

Create a models directory and add a Message.js file:

// models/Message.js
const mongoose = require('mongoose');

const messageSchema = new mongoose.Schema({
  username: String,
  content: String,
  timestamp: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Message', messageSchema);

d. Save Messages to the Database

In your WebSocket connection handler, modify the message event to save incoming messages:

const Message = require('./models/Message');

wss.on('connection', (ws) => {
  ws.on('message', async (data) => {
    const messageData = JSON.parse(data);
    const message = new Message({
      username: messageData.username,
      content: messageData.content,
    });
    await message.save();

    // Broadcast the message to all clients
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(message));
      }
    });
  });
});

Retrieve and Display Past Messages

When a new client connects, send them the chat history:

wss.on('connection', async (ws) => {
  const messages = await Message.find().sort({ timestamp: 1 }).limit(100);
  ws.send(JSON.stringify({ type: 'history', data: messages }));

  // ...existing message handler...
});

On the client side, handle the 'history' message type to display past messages.

7. Implementing User Sessions

To enhance the chat application, we'll implement user sessions, allowing users to resume their chats and maintain a consistent identity across sessions.

a. Install Session Management Dependencies

We'll use express-session and connect-mongo to manage sessions and store them in MongoDB:

npm install express-session connect-mongo

b. Configure Sessions in Express

In your server.js file, add the following configuration:

const session = require('express-session');
const MongoStore = require('connect-mongo');

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  store: MongoStore.create({ mongoUrl: 'mongodb://localhost:27017/chat-app' }),
  cookie: { maxAge: 1000 * 60 * 60 * 24 } // 1 day
}));

c. Associate Sessions with WebSocket Connections

To access session data within WebSocket connections, we'll need to integrate session middleware with the WebSocket server. One approach is to use a library like express-socket.io-session or manually parse cookies and retrieve session data. Here's a simplified example using express-socket.io-session:

npm install express-socket.io-session

Then, in your server.js:

const sharedsession = require("express-socket.io-session");

const sessionMiddleware = session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  store: MongoStore.create({ mongoUrl: 'mongodb://localhost:27017/chat-app' }),
  cookie: { maxAge: 1000 * 60 * 60 * 24 }
});

app.use(sessionMiddleware);

const io = require('socket.io')(server);
io.use(sharedsession(sessionMiddleware, {
  autoSave: true
}));

io.on('connection', (socket) => {
  console.log('New client connected with session ID:', socket.handshake.sessionID);
  // You can now access session data via socket.handshake.session
});

8. Deploying the Application to the Cloud

Deploying your chat application to the cloud ensures scalability and accessibility. We'll demonstrate deployment using Google Cloud Run, which allows you to run containers in a serverless environment.

a. Containerize the Application

Create a Dockerfile in your project root:

FROM node:14

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
COPY package*.json ./
RUN npm install

# Bundle app source
COPY . .

EXPOSE 8080
CMD [ "node", "server.js" ]

Note: Ensure your server listens on the port specified by the PORT environment variable, as required by Cloud Run:

const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});

b. Deploy to Google Cloud Run

  1. Install and initialize the Google Cloud SDK: Follow the instructions at https://cloud.google.com/sdk/docs/install.
  2. Build and deploy the container:
gcloud builds submit --tag gcr.io/PROJECT-ID/chat-app
gcloud run deploy chat-app --image gcr.io/PROJECT-ID/chat-app --platform managed --region REGION --allow-unauthenticated

Replace PROJECT-ID with your Google Cloud project ID and REGION with your desired region (e.g., us-central1).

After deployment, Google Cloud Run will provide a URL where your chat application is accessible.

9. Conclusion

In this tutorial, we've built a real-time chat application using Node.js and WebSockets, implemented user sessions, stored messages in MongoDB, and deployed the application to the cloud using Google Cloud Run. This foundation can be expanded with features like authentication, chat rooms, and more sophisticated front-end frameworks.

Post a Comment

Previous Post Next Post