javascriptroom guide

Building a Real-Time Chat Application with React and Socket.io

In today’s digital age, real-time communication has become a cornerstone of modern web applications. From social media platforms to customer support tools, the ability to exchange messages instantly enhances user engagement and collaboration. In this tutorial, we’ll explore how to build a fully functional real-time chat application using **React** (for the frontend) and **Socket.io** (for real-time communication). React, a popular JavaScript library for building user interfaces, will help us create a dynamic and responsive chat interface. Socket.io, on the other hand, simplifies real-time communication by leveraging WebSockets (with fallbacks for older browsers) to enable bidirectional, event-driven communication between clients and servers. By the end of this guide, you’ll have a chat app that supports: - Real-time message exchange between multiple users. - User presence tracking (online/offline status). - A clean, responsive UI. Let’s dive in!

Table of Contents

  1. Prerequisites
  2. Project Setup
  3. Building the Socket.io Server
  4. Creating the React Frontend
  5. Styling the Chat Interface
  6. Testing the Application
  7. Deployment Considerations
  8. Conclusion and Next Steps
  9. References

Prerequisites

Before getting started, ensure you have the following tools installed:

  • Node.js (v14+ recommended) and npm (v6+): To run the backend and frontend servers.
  • Git: (Optional) For version control.
  • Basic knowledge of:
    • JavaScript (ES6+).
    • React (components, hooks like useState and useEffect).
    • Express.js (basic server setup).

Project Setup

We’ll split our project into two parts: a backend (Node.js/Express/Socket.io) to handle real-time communication and a frontend (React) for the user interface. Let’s set up the project structure.

Backend Setup (Node.js + Express + Socket.io)

First, create a project folder and initialize the backend:

# Create project folder  
mkdir react-socketio-chat  
cd react-socketio-chat  

# Initialize backend folder  
mkdir backend  
cd backend  
npm init -y  

Install required dependencies:

npm install express socket.io cors  
  • express: Fast, unopinionated web framework for Node.js.
  • socket.io: Enables real-time, bidirectional communication.
  • cors: Handles Cross-Origin Resource Sharing (to allow frontend-backend communication).

Frontend Setup (React)

Next, set up the React frontend using create-react-app (or Vite, if preferred):

# Navigate back to the project root  
cd ..  

# Create React app  
npx create-react-app frontend  
cd frontend  

# Install Socket.io client (to connect to the backend)  
npm install socket.io-client  

Building the Socket.io Server

The backend will manage WebSocket connections, broadcast messages to all clients, and track connected users. Let’s create the server.

Step 1: Create the Server File

In the backend folder, create server.js:

const express = require('express');  
const http = require('http');  
const { Server } = require('socket.io');  
const cors = require('cors');  

// Initialize Express app  
const app = express();  
// Enable CORS to allow frontend access  
app.use(cors());  

// Create HTTP server (required for Socket.io)  
const server = http.createServer(app);  

// Configure Socket.io with CORS (allow frontend origin)  
const io = new Server(server, {  
  cors: {  
    origin: "http://localhost:3000", // React frontend runs here  
    methods: ["GET", "POST"]  
  }  
});  

// Track connected users (socket.id -> username)  
const connectedUsers = {};  

// Handle Socket.io connections  
io.on('connection', (socket) => {  
  console.log(`User connected: ${socket.id}`);  

  // Listen for "set username" event (sent by client on connect)  
  socket.on('set username', (username) => {  
    connectedUsers[socket.id] = username;  
    // Broadcast to all clients that a new user joined  
    io.emit('user connected', username);  
    // Send updated user list to all clients  
    io.emit('active users', Object.values(connectedUsers));  
  });  

  // Listen for "chat message" events (sent by clients when sending messages)  
  socket.on('chat message', (data) => {  
    // Broadcast message to ALL connected clients (including sender)  
    io.emit('chat message', {  
      username: data.username,  
      message: data.message,  
      timestamp: new Date().toLocaleTimeString()  
    });  
  });  

  // Handle disconnection  
  socket.on('disconnect', () => {  
    const username = connectedUsers[socket.id];  
    if (username) {  
      console.log(`User disconnected: ${username}`);  
      delete connectedUsers[socket.id];  
      // Broadcast user disconnection  
      io.emit('user disconnected', username);  
      // Update active users list  
      io.emit('active users', Object.values(connectedUsers));  
    }  
  });  
});  

// Start server on port 5000  
const PORT = process.env.PORT || 5000;  
server.listen(PORT, () => {  
  console.log(`Backend server running on port ${PORT}`);  
});  

How the Server Works

  • CORS Configuration: Allows the React frontend (running on localhost:3000) to connect to the backend.
  • Socket.io Events:
    • connection: Triggered when a client connects.
    • set username: Clients send their username here; the server stores it and broadcasts the new user.
    • chat message: Clients send messages here; the server broadcasts them to all clients.
    • disconnect: Triggered when a client leaves; the server removes the user and updates the user list.

Creating the React Frontend

Now, let’s build the React interface to send/receive messages and display online users.

Step 1: Clean Up Default React Files

Delete frontend/src/App.css, frontend/src/logo.svg, and update frontend/src/App.js to be empty (we’ll rebuild it).

Step 2: Create the Chat Component

Create frontend/src/Chat.js (our main chat component):

import { useState, useEffect } from 'react';  
import io from 'socket.io-client';  
import './Chat.css';  

// Connect to Socket.io backend (update URL in production!)  
const socket = io('http://localhost:5000');  

const Chat = () => {  
  // State for messages, active users, username, and input message  
  const [messages, setMessages] = useState([]);  
  const [activeUsers, setActiveUsers] = useState([]);  
  const [username, setUsername] = useState('');  
  const [messageInput, setMessageInput] = useState('');  

  // Prompt for username on component mount  
  useEffect(() => {  
    const name = prompt('Enter your username:');  
    if (name) {  
      setUsername(name);  
      socket.emit('set username', name); // Send username to server  
    }  
  }, []);  

  // Socket.io event listeners (run once on component mount)  
  useEffect(() => {  
    // Listen for new chat messages  
    socket.on('chat message', (data) => {  
      setMessages(prev => [...prev, data]);  
    });  

    // Listen for active users updates  
    socket.on('active users', (users) => {  
      setActiveUsers(users);  
    });  

    // Cleanup: remove event listeners on unmount  
    return () => {  
      socket.off('chat message');  
      socket.off('active users');  
    };  
  }, []);  

  // Handle message submission  
  const handleSendMessage = (e) => {  
    e.preventDefault();  
    if (!messageInput.trim() || !username) return;  

    // Emit "chat message" event to server  
    socket.emit('chat message', {  
      username: username,  
      message: messageInput  
    });  

    // Clear input field  
    setMessageInput('');  
  };  

  return (  
    <div className="chat-container">  
      <div className="chat-sidebar">  
        <h3>Online Users ({activeUsers.length})</h3>  
        <ul className="user-list">  
          {activeUsers.map((user, index) => (  
            <li key={index} className={user === username ? 'current-user' : ''}>  
              {user} {user === username && '(You)'}  
            </li>  
          ))}  
        </ul>  
      </div>  

      <div className="chat-main">  
        <div className="chat-header">  
          <h2>Real-Time Chat</h2>  
        </div>  

        <div className="messages-container">  
          {messages.map((msg, index) => (  
            <div key={index} className={`message ${msg.username === username ? 'own-message' : ''}`}>  
              <strong>{msg.username}</strong>  
              <p>{msg.message}</p>  
              <small>{msg.timestamp}</small>  
            </div>  
          ))}  
        </div>  

        <form onSubmit={handleSendMessage} className="message-form">  
          <input  
            type="text"  
            value={messageInput}  
            onChange={(e) => setMessageInput(e.target.value)}  
            placeholder="Type your message..."  
            required  
          />  
          <button type="submit">Send</button>  
        </form>  
      </div>  
    </div>  
  );  
};  

export default Chat;  

Key Frontend Logic

  • Socket Connection: The io('http://localhost:5000') call connects to the backend.
  • Username Prompt: On component mount, a prompt asks for the user’s name, which is sent to the server via set username.
  • State Management:
    • messages: Stores chat history.
    • activeUsers: Tracks online users (updated via active users events from the server).
    • messageInput: Binds to the input field for typing messages.
  • Event Handling:
    • chat message: Emitted when the user sends a message; the server broadcasts it to all clients.
    • user connected/user disconnected: Update the active users list.

Styling the Chat Interface

Create frontend/src/Chat.css to style the chat:

.chat-container {  
  display: flex;  
  height: 100vh;  
  max-width: 1200px;  
  margin: 0 auto;  
  border: 1px solid #ddd;  
}  

.chat-sidebar {  
  width: 250px;  
  background: #f5f5f5;  
  padding: 20px;  
  border-right: 1px solid #ddd;  
}  

.user-list {  
  list-style: none;  
  padding: 0;  
}  

.user-list li {  
  padding: 8px 0;  
  border-bottom: 1px solid #eee;  
}  

.current-user {  
  font-weight: bold;  
  color: #2c3e50;  
}  

.chat-main {  
  flex: 1;  
  display: flex;  
  flex-direction: column;  
}  

.chat-header {  
  padding: 20px;  
  background: #2c3e50;  
  color: white;  
  margin: 0;  
}  

.messages-container {  
  flex: 1;  
  padding: 20px;  
  overflow-y: auto;  
  background: #ecf0f1;  
}  

.message {  
  background: white;  
  padding: 10px 15px;  
  border-radius: 15px;  
  margin-bottom: 15px;  
  max-width: 70%;  
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);  
}  

.own-message {  
  margin-left: auto;  
  background: #3498db;  
  color: white;  
}  

.message-form {  
  display: flex;  
  padding: 20px;  
  background: #f5f5f5;  
}  

.message-form input {  
  flex: 1;  
  padding: 10px;  
  border: 1px solid #ddd;  
  border-radius: 20px;  
  margin-right: 10px;  
}  

.message-form button {  
  padding: 10px 20px;  
  background: #3498db;  
  color: white;  
  border: none;  
  border-radius: 20px;  
  cursor: pointer;  
}  

.message-form button:hover {  
  background: #2980b9;  
}  

Step 3: Update App.js

Replace frontend/src/App.js with:

import Chat from './Chat';  

function App() {  
  return (  
    <div className="App">  
      <Chat />  
    </div>  
  );  
}  

export default App;  

Testing the Application

Let’s run the backend and frontend to test the chat!

Step 1: Start the Backend

In the backend folder:

node server.js  

You should see: Backend server running on port 5000.

Step 2: Start the Frontend

In a new terminal, navigate to the frontend folder:

npm start  

The React app will open at http://localhost:3000.

Step 3: Test Real-Time Features

  • Send Messages: Enter a username when prompted. Type a message and click “Send”—it will appear in real-time.
  • Multi-User Chat: Open http://localhost:3000 in another browser tab/window, enter a different username, and send messages. Both tabs will see each other’s messages instantly.
  • User Presence: The “Online Users” sidebar updates when users join/leave.

Deployment Considerations

To deploy your chat app:

Backend Deployment

  • Platforms: Heroku, Render, or AWS Elastic Beanstalk.
  • Notes:
    • Set process.env.PORT for the server port (most platforms assign dynamic ports).
    • Update CORS origin to your frontend’s production URL (e.g., https://your-chat-app.vercel.app).

Frontend Deployment

  • Platforms: Vercel, Netlify, or GitHub Pages.
  • Notes:
    • Replace http://localhost:5000 with your deployed backend URL (e.g., https://your-backend.herokuapp.com).
    • Use environment variables (e.g., REACT_APP_BACKEND_URL) to store the backend URL.

Conclusion and Next Steps

Congratulations! You’ve built a real-time chat app with React and Socket.io. Here are ideas to enhance it:

  • Message Persistence: Add a database (e.g., MongoDB) to store messages.
  • File Attachments: Allow users to send images/files via Socket.io binary events.
  • Authentication: Add user login (e.g., with Firebase or Auth0).
  • Typing Indicators: Emit “user typing” events to show when someone is typing.

References