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
- Initialize Project
mkdir chat-app
cd chat-app
npm init -y
- Install Dependencies
npm install express socket.io
- 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
- 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>
- 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;
}
- 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
- 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);
}
});
});
- 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);
});
});
- 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
- Connection Errors
// server.js
io.on('connection', (socket) => {
socket.on('error', (error) => {
console.error('Socket error:', error);
socket.emit('error', 'An error occurred');
});
});
- Reconnection Logic
// public/app.js
socket.on('connect_error', (error) => {
console.error('Connection error:', error);
// Implement reconnection logic
setTimeout(() => {
socket.connect();
}, 5000);
});
Best Practices
- 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);
- 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
- 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();
});
});
});
- Load Testing
// test/load.test.js
const autocannon = require('autocannon');
autocannon({
url: 'http://localhost:3000',
connections: 100,
duration: 10
}, console.log);
Deployment
- 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
}
}
};
- 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
Socket.IO Documentation
WebSocket API
Node.js Documentation
Express.js Documentation
Citations
WebSocket Protocol
Socket.IO Best Practices
Node.js Security Best Practices
Real-time Web Applications
🚀 Ready to kickstart your tech career?
👉 [Apply to 10000Coders]
🎓 [Learn Web Development for Free]
🌟 [See how we helped 2500+ students get jobs]
Top comments (0)