Table of Contents
- Prerequisites
- Project Setup
- Building the Socket.io Server
- Creating the React Frontend
- Styling the Chat Interface
- Testing the Application
- Deployment Considerations
- Conclusion and Next Steps
- 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
useStateanduseEffect). - 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 viaactive usersevents 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:3000in 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.PORTfor 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).
- Set
Frontend Deployment
- Platforms: Vercel, Netlify, or GitHub Pages.
- Notes:
- Replace
http://localhost:5000with 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.
- Replace
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.