Building a Simple Chat App with WebSocket in Node.js
A step-by-step guide to creating a real-time chat application using WebSocket, Node.js, and Express.
Understanding WebSocket
What is WebSocket?
Traditional HTTP vs WebSocket
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Client │────▶│ Server │◀────│ Client │
└─────────┘ └─────────┘ └─────────┘
▲ │ ▲
│ ▼ │
└──────────────┴──────────────┘
Key Characteristics
- Full-duplex communication
- Persistent connection
- Real-time data transfer
- Low latency
- Bi-directional communication
Project Setup
1. Initialize Project
mkdir chat-app
cd chat-app
npm init -y
2. Install Dependencies
npm install express socket.io
3. Basic Server Setup
// server.js
const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);
// Serve static files
app.use(express.static('public'));
// Socket.io connection handling
io.on('connection', (socket) => {
console.log('A user connected');
socket.on('disconnect', () => {
console.log('User disconnected');
});
});
const PORT = process.env.PORT || 3000;
http.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Frontend Implementation
1. HTML Structure
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Chat</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h2>WebSocket Chat</h2>
</div>
<div class="chat-messages" id="messages"></div>
<div class="chat-input">
<input type="text" id="message" placeholder="Type a message...">
<button onclick="sendMessage()">Send</button>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="app.js"></script>
</body>
</html>
2. CSS Styling
/* public/style.css */
.chat-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.chat-messages {
height: 400px;
border: 1px solid #ccc;
overflow-y: auto;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin: 10px 0;
padding: 10px;
border-radius: 5px;
}
.message.sent {
background-color: #e3f2fd;
margin-left: 20%;
}
.message.received {
background-color: #f5f5f5;
margin-right: 20%;
}
.chat-input {
display: flex;
gap: 10px;
}
input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
padding: 10px 20px;
background-color: #2196f3;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
3. Client-side JavaScript
// public/app.js
const socket = io();
function sendMessage() {
const messageInput = document.getElementById('message');
const message = messageInput.value;
if (message.trim()) {
socket.emit('chat message', message);
messageInput.value = '';
}
}
socket.on('chat message', (msg) => {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.classList.add('message');
messageElement.classList.add('received');
messageElement.textContent = msg;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
});
Enhanced Features
1. User Authentication
// server.js
const users = new Map();
io.on('connection', (socket) => {
socket.on('user join', (username) => {
users.set(socket.id, username);
io.emit('user joined', username);
});
socket.on('disconnect', () => {
const username = users.get(socket.id);
if (username) {
io.emit('user left', username);
users.delete(socket.id);
}
});
});
2. Message History
// server.js
const messageHistory = [];
io.on('connection', (socket) => {
// Send message history to new users
socket.emit('message history', messageHistory);
socket.on('chat message', (msg) => {
const username = users.get(socket.id);
const message = {
user: username,
text: msg,
time: new Date().toISOString()
};
messageHistory.push(message);
io.emit('chat message', message);
});
});
3. Typing Indicators
// server.js
io.on('connection', (socket) => {
socket.on('typing', () => {
const username = users.get(socket.id);
socket.broadcast.emit('user typing', username);
});
socket.on('stop typing', () => {
const username = users.get(socket.id);
socket.broadcast.emit('user stop typing', username);
});
});
Error Handling
1. Connection Errors
// server.js
io.on('connection', (socket) => {
socket.on('error', (error) => {
console.error('Socket error:', error);
socket.emit('error', 'An error occurred');
});
});
2. Reconnection Logic
// public/app.js
socket.on('connect_error', (error) => {
console.error('Connection error:', error);
// Implement reconnection logic
setTimeout(() => {
socket.connect();
}, 5000);
});
Best Practices
1. Security
- Input Validation
function validateMessage(message) {
return message.trim().length > 0 && message.length <= 1000;
}
- Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
app.use(limiter);
2. Performance
- Message Batching
let messageQueue = [];
const BATCH_INTERVAL = 100;
setInterval(() => {
if (messageQueue.length > 0) {
io.emit('message batch', messageQueue);
messageQueue = [];
}
}, BATCH_INTERVAL);
- Connection Pooling
const pool = require('socket.io-redis');
io.adapter(pool({ host: 'localhost', port: 6379 }));
Testing
1. Unit Tests
// test/chat.test.js
const { expect } = require('chai');
const io = require('socket.io-client');
describe('Chat Application', () => {
let clientSocket;
beforeEach((done) => {
clientSocket = io('http://localhost:3000');
clientSocket.on('connect', done);
});
afterEach(() => {
clientSocket.close();
});
it('should receive message', (done) => {
clientSocket.emit('chat message', 'Hello');
clientSocket.on('chat message', (msg) => {
expect(msg).to.equal('Hello');
done();
});
});
});
2. Load Testing
// test/load.test.js
const autocannon = require('autocannon');
autocannon({
url: 'http://localhost:3000',
connections: 100,
duration: 10
}, console.log);
Deployment
1. Environment Configuration
// config.js
module.exports = {
development: {
port: 3000,
redis: {
host: 'localhost',
port: 6379
}
},
production: {
port: process.env.PORT,
redis: {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
}
}
};
2. Docker Setup
# Dockerfile
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Conclusion
This guide has covered:
- WebSocket basics
- Real-time chat implementation
- User authentication
- Message history
- Typing indicators
- Error handling
- Security measures
- Performance optimization
- Testing strategies
- Deployment considerations
Next Steps
- Add user authentication
- Implement message persistence
- Add file sharing
- Implement private messaging
- Add emoji support
Resources
Citations
🚀 Ready to kickstart your tech career?
🎓 [Learn Web Development for Free]
🌟 [See how we helped 2500+ students get jobs]
Comments