loading...

. . . . . .

Let’s make something together

Give us a call or drop by anytime, we endeavour to answer all enquiries within 24 hours on business days.

Find us

504, Gala Empire,
Driver-in Road, Thaltej,
Ahmedabad – 380054.

Email us

For Career – career@equalefforts.com
For Sales – sales@equalefforts.com
For More Info – info@equalefforts.com

Phone support

Phone: +91 6357 251 116

Building a React Chat App with Socket.IO in Liferay Client Extensions

  • By Abhishek Ramanandi
  • November 29, 2025
  • 12 Views

Real-time features like chat and notifications are becoming essential in modern web apps. Socket.IO makes it easy to add these with live, two-way communication. In this guide, you’ll learn how to build a real-time chat app using React and Socket.IO, and integrate it into Liferay as a Client Extension. Whether you’re new to real-time or Liferay CE, this step-by-step tutorial will help you get started quickly and efficiently. Let’s build something live! ⚡

Why Socket IO?

Socket.IO allows real-time, bi-directional communication between the browser and server. It’s reliable, fast, and supports features like auto-reconnect and broadcasting-perfect for chat apps.

Think of it this way

  • Socket.IO is like a super-smart walkie-talkie for websites.
  • It helps two people (or more) talk to each other instantly on the internet, like sending messages, emojis, or game moves, right away without waiting or refreshing.

Prerequisites

You should be familiar with:

  • React 18 +
  • Liferay 7.4+
  • Node.js and NPM (Basic usage, installing packages)

Project Setup

Needed Dependencies:

  • Node version: 22.13.1
  • NPM version: 10.9.2

Install both using

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 22.13.1

Set it as the default using (if having multiple versions installed):

nvm alias default 22.13.1
node -v   # should show v22.13.1
npm -v    # 10.9.2

Install Yarn:

sudo npm install -g yarn
yarn --version

Setting Up the Socket.IO Server

To enable real-time communication between users, we need a backend server that can manage WebSocket connections. We’ll use Node.js, Express, and Socket.IO to build a lightweight and efficient Socket.IO server.

Initialize a Node.js Project

First, create a new folder and initialize a Node.js project:

mkdir socket-server
cd socket-server

Install Required Packages

Install Express and Socket.IO:

yarn add express socket.io cors

Or with npm

npm install express socket.io cors
  • express: Minimalist web server framework.
  • socket.io: WebSocket wrapper for real-time communication.
  • cors: Enables cross-origin communication (important when serving React via Liferay domain).

Create the Socket.IO Server

Create a file named index.js:

Add the following code:

import { Server } from "socket.io";
import cors from "cors";
import { createServer } from "http";
import express from "express";

const app = express();
const server = createServer(app);

app.use(cors());

const io = new Server(server, {
    cors: {
        origin: "*",
        methods: ["GET", "POST"]
    }
});

// Simple map of userId -> socketId
let onlineUsers = {};

io.on("connection", (socket) => {
    console.log(`New connection: ${socket.id}`);

    // When a user joins
    socket.on("join_chat", ({ userId, userName }) => {
        if (userId && userName) {
            onlineUsers[userId] = { userName, socketId: socket.id };
            console.log(`${userName} (${userId}) is online`);
            io.emit("update_user_status", onlineUsers);
        }
    });

    // Listen for messages
    socket.on("send_message", (data) => {
        console.log("Message received:", data);
        io.emit("receive_message", data);
    });

    // When a socket disconnects
    socket.on("disconnect", () => {
        console.log(`Socket ${socket.id} disconnected`);

        // Find the user and remove them
        const userId = Object.keys(onlineUsers).find(
            (id) => onlineUsers[id].socketId === socket.id
        );

        if (userId) {
            console.log(`Removing user ${userId} from online users`);
            delete onlineUsers[userId];
            io.emit("update_user_status", onlineUsers);
        }
    });
});

server.listen(3001, '0.0.0.0', () => {
    console.log("Socket.IO Server running on port 3001");
});

Here, I have also added code for showing the online users so you may show the online offline status accordingly, as Socket, when the screen refreshes, disconnects the user and reconnects it.

Run the Server

Use Node to start the server:

node index.js

You should see:

Socket.IO server running on port 3001

Folder Structure Summary:

Create a React Liferay client extension for running the chat application.

Add this to app.jsx in react app

import React, { useEffect, useState } from "react";

import { io } from "socket.io-client";

const socket = io("http://localhost:3001");

export default function ChatApp() {
  const [userId, setUserId] = useState("");
  const [receiverId, setReceiverId] = useState("");
  const [message, setMessage] = useState("");
  const [messages, setMessages] = useState([]);
  const [joined, setJoined] = useState(false);

  useEffect(() => {
    socket.on("receive_message", (data) => {
      if (data.receiverId === userId || data.userId === userId) {
        setMessages((prev) => [...prev, data]);
      }
    });

    return () => {
      socket.off("receive_message");
    };
  }, [userId]);

  const joinChat = () => {
    if (userId.trim()) {
      socket.emit("join_chat", { userId });
      setJoined(true);
    }
  };

  const sendMessage = () => {
    if (message.trim() && receiverId.trim()) {
      const data = {
        userId,
        receiverId,
        message,
        timestamp: new Date().toISOString(),
      };
      socket.emit("send_message", data);
      setMessage("");
    }
  };

  if (!joined) {
    return (
      <div className="container py-5">
        <h2 className="mb-4">Join Chat</h2>

        <input
          type="text"
          className="form-control mb-3"
          placeholder="Enter Your User ID"
          value={userId}
          onChange={(e) => setUserId(e.target.value)}
        />

        <button
          className="btn btn-primary"
          onClick={joinChat}
          disabled={!userId}
        >
          Join
        </button>
      </div>
    );
  }

  return (
    <div className="container py-5">
      <h2 className="mb-4">Welcome {userId}!</h2>

      <input
        type="text"
        className="form-control mb-3"
        placeholder="Enter Receiver User ID"
        value={receiverId}
        onChange={(e) => setReceiverId(e.target.value)}
      />

      <div className="border p-3 mb-3" style={{ height: "300px", overflowY: "auto" }}>
        {messages.map((msg, index) => (
          <div key={index} className="mb-2">
            <b>{msg.userId}</b>: {msg.message}
          </div>
        ))}
      </div>

      <div className="input-group">
        <input
          type="text"
          className="form-control"
          placeholder="Type a message"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && sendMessage()}
        />
        <button className="btn btn-success" onClick={sendMessage}>
          Send
        </button>
      </div>
    </div>
  );
}

Here, I have added just a simple example to show you the basic working of chat. There is no database connection right now. You can create REST API’s(using JAX-RS) for storing the messages, marking messages as read, and showing messages among users, showing unread messages and their count. Also, you can add proper CSS and JS for opening the chat in a drawer and handle it accordingly.

Important Note: Currently, I have added the whole code in app.jsx as the code is small and simple, for more scalability, it’s best practice to organize your code using components, utils, constants file. Create related reusable components, make use of Liferay’s client extension properties to filter data, create a separate file for calling the API, and structure the content properly.

Here, first of all user will be asked ID after entering their ID, and we can click on join. Then, we need to enter the receiverId. Then we are ready for a chat.

You can automate this based on the user and receiver accordingly

At last, your chat will look like the above image.

What you built:

A full working one-to-one chat application inside a Liferay client extension using Socket.IO and React.

What you learned:

  • Setting up Socket.IO client and server
  • Real-time event handling in React
  • Building a clean UI using Bootstrap

Future Enhancements:

  • Group Chat rooms
  • Typing Indicators (“User is typing…”)
  • Read Receipts (“Seen” messages)
  • File Sharing (Images, Documents)

Leave a Reply

Your email address will not be published. Required fields are marked *