How to Build a Real-Time Messaging App with WebSockets (2026)
By Rafirit Station Editorial Team · Updated 2026 · ⏱ 18 min read
Building a real-time messaging app with WebSockets is no longer optional—it’s expected. According to Statista, over 3.5 billion people use messaging apps daily. In Bangladesh, mobile data consumption grew 30% year-over-year, driving demand for instant communication. Yet most local apps still rely on clunky polling. If you don’t adopt WebSocket-based real-time features, your users will churn to WhatsApp or Telegram. A single second of delay can cost ৳10,000 per day in lost engagement for a Dhaka-based startup with 10,000 daily active users. By the end of this guide, you’ll know how to design, code, and deploy a production-ready messaging app using WebSockets—complete with cost estimates in BDT and scalability tips for the Bangladeshi market.
📚 External Resources (Bookmark These)
- MDN WebSockets API
- Socket.IO Documentation
- MongoDB Change Streams
- Redis Pub/Sub
- JWT Authentication Node.js
- React Official Docs
- DigitalOcean Tutorials
- Cloudflare DNS Guide
- Twilio Chat API
- uWebSockets.js
🔗 Rafirit Station Services
- SEO Services — Full audit & strategy
- SEO Agency Dhaka — Local SEO experts
- Web Analytics — Track your organic rankings
- Content Writing — SEO-optimised copy
- CRO Services — Turn traffic into revenue
- Case Studies — Real SEO results
- Packages & Pricing
- Rafirit Station Bangladesh — Digital Agency
- Rafirit Station Dhaka — Full-Service Agency
🚀 Get Your App to Market 2x Faster
Dhaka-based startups — let our senior developers build your WebSocket backend while you focus on UI. We’ve launched 12 chat apps in the last year.
🗓 Book Your Free Strategy Call →
No commitment · 60-minute session · Bangladeshi clients welcome
Phase 1: Architecture & Setup
Before writing a single line of code, you need to decide on your tech stack. While raw WebSocket API works, we recommend Socket.IO for its built-in reconnection, event broadcasting, and room management. In Dhaka, where internet stability can be variable, Socket.IO’s fallback to HTTP long-polling is a lifesaver. Your server will run Node.js (version 20 LTS) and your client will use React (18) with Socket.IO client.
Tactic 1.1: Choose Your Hosting & Domain
Why this works: A VPS near your users reduces latency. For Bangladeshi users, a Singapore or Mumbai server gives <20ms ping. DigitalOcean droplets cost ৳600/month (equivalent $6) for basic needs.
Exactly how to do it:
- Sign up for DigitalOcean (or local provider like BDCOM).
- Create a droplet with Ubuntu 24.04, 1 vCPU, 1GB RAM (৳600/month).
- Set up a domain (e.g., api.yourchat.com) via Cloudflare for DNS proxying.
- Enable UFW: allow ports 22, 80, 443, and 3000 (internal).
- Install Node.js 20 via NodeSource.
- Clone your repository and install dependencies.
- Use PM2 to keep the server running.
Pro script / template: Use this bash snippet to bootstrap:
curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt-get install -y nodejs && git clone https://github.com/yourrepo && cd yourrepo && npm install
📊 Expected results: Fully provisioned server in under 30 minutes. Cost: ৳600/month.
Tactic 1.2: Initialize the Node.js Project
Why this works: Separating concerns (server, client, shared types) makes scaling easier.
Exactly how to do it:
- Create project folder:
mkdir chat-app && cd chat-app - Initialize npm:
npm init -y - Install dependencies:
npm i express socket.io cors dotenv jsonwebtoken bcrypt - Create folders:
mkdir src src/middleware src/models src/routes src/sockets - Create
.envwith PORT=3000, JWT_SECRET, MONGO_URI. - Create
index.jswith Express server and Socket.IO binding. - Implement a health check endpoint:
GET /health
Pro script / template: Server boilerplate:
const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { origin: '*' } }); io.on('connection', (socket) => { console.log('user connected'); }); server.listen(process.env.PORT || 3000);
📊 Expected results: Server running on port 3000, accepts raw Socket.IO connections.
Tactic 1.3: Create the React Client
Why this works: React’s component model is perfect for chat UIs with messages, input, and user lists.
Exactly how to do it:
- Use Vite:
npm create vite@latest client -- --template react - Install Socket.IO client:
npm i socket.io-client - Create
Chat.jsxcomponent with message state. - Connect to server:
const socket = io('http://localhost:3000') - Listen for ‘message’ event and update state.
- Emit ‘send_message’ on form submit.
- Style with CSS modules or Tailwind.
Pro script / template: Basic connect:
import { io } from 'socket.io-client'; const socket = io('http://localhost:3000'); socket.on('connect', () => console.log('connected'));
📊 Expected results: Client connects to server, logs ‘connected’ in console.
Phase 2: Authentication & Real-Time Messaging
Most tutorials skip auth—don’t. Without it, anyone can impersonate others. We’ll use JWT tokens passed during handshake. Socket.IO allows middleware that runs before connection is established.
Tactic 2.1: User Registration & JWT Generation
Why this works: JWT is stateless; no need for server-side sessions. Perfect for scaling.
Exactly how to do it:
- Create User model with Mongoose: username, password hashed with bcrypt (salt rounds 12).
- Create POST
/api/registerendpoint: validate input, hash password, save user. - Create POST
/api/loginendpoint: compare password, sign JWT with user id. - JWT payload:
{ userId: user._id }, expires in 7 days. - Return token to client.
- Store token in localStorage or httpOnly cookie.
Pro script / template: Login route:
app.post('/api/login', async (req, res) => { const user = await User.findOne({ username: req.body.username }); if (!user || !(await bcrypt.compare(req.body.password, user.password))) return res.status(401).send('Invalid credentials'); const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET); res.json({ token }); });
📊 Expected results: Users can register and login; token stored client-side.
Tactic 2.2: Socket.IO Auth Middleware
Why this works: Prevents unauthorized sockets from joining rooms.
Exactly how to do it:
- Create middleware in
src/middleware/socketAuth.js. - Extract token from handshake query:
socket.handshake.query.token. - Verify token with jwt.verify; if invalid, disconnect socket with error.
- Attach user object to socket:
socket.user = decoded. - Apply middleware:
io.use(socketAuth). - On client, pass token in connection URL:
io('http://localhost:3000?token='+token).
Pro script / template: Middleware code:
io.use((socket, next) => { const token = socket.handshake.query.token; try { const decoded = jwt.verify(token, process.env.JWT_SECRET); socket.userId = decoded.userId; next(); } catch (err) { next(new Error('Authentication error')); } });
📊 Expected results: Only authenticated sockets remain connected; others are rejected.
Tactic 2.3: Implement Room-Based Chat
Why this works: Rooms allow private conversations and group chats without broadcasting to all users.
Exactly how to do it:
- Create a room management system: user joins a room on login (e.g., room_ for private messages).
- When user sends a message, server emits ‘new_message’ to the recipient’s room.
- For group chats, create room_ and join all members.
- Store messages in MongoDB for persistence.
- Implement typing indicators: emit ‘typing’ event with recipient/s group.
- Read receipts: emit ‘read’ when user opens conversation.
- Use Socket.IO callbacks for confirmation (ack).
Pro script / template: Send message:
socket.on('send_message', (data, callback) => { const message = { sender: socket.userId, text: data.text, timestamp: Date.now() }; socket.to(data.room).emit('new_message', message); callback({ status: 'ok' }); });
📊 Expected results: Two clients can message each other in real-time with less than 50ms latency.
🛡️ Need help with authentication & security?
Dhaka-based teams — our security audit covers JWT implementation, rate limiting, and DDoS protection. We’ll review your WebSocket middleware for free.
No commitment · 30-minute session
Phase 3: Persistence & History
Messages must survive server restarts. We’ll use MongoDB with Mongoose. A controversial but effective pattern: store messages in a collection per chat room. This makes pagination easy and keeps documents small. Counterintuitive insight: MongoDB’s change streams can replace Socket.IO for broadcasting if combined with a queue—but stick with Socket.IO for two-way communication.
Tactic 3.1: Design the Message Schema
Why this works: A lean schema improves write performance.
Exactly how to do it:
- Define Mongoose schema:
{ roomId: String, senderId: ObjectId, text: String, timestamp: { type: Date, default: Date.now } }. - Index on
roomIdandtimestampfor fast queries. - Use a capped collection per room? Not necessary; just purge old messages periodically.
- Create model
Messageinsrc/models/Message.js. - Add a TTL index on timestamp to auto-expire messages after 30 days (optional).
- Generate
roomIdas a sorted concatenation of user IDs (e.g., user1_user2 for DMs).
Pro script / template: Schema:
const messageSchema = new mongoose.Schema({ roomId: { type: String, required: true, index: true }, senderId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, text: { type: String, required: true }, timestamp: { type: Date, default: Date.now, index: true } });
📊 Expected results: Messages stored and queryable by room.
Tactic 3.2: Save & Retrieve Messages
Why this works: Offline users see history when they reconnect.
Exactly how to do it:
- In Socket.IO ‘send_message’ handler, after emitting, save to DB.
- Create REST API endpoint:
GET /api/messages/:roomId?limit=50&before=for pagination. - Return messages in reverse chronological order (newest first).
- Client loads initial batch on joining a room.
- Implement infinite scroll: load older messages when user scrolls up.
- Debounce DB writes to avoid overload (batch insert every 100ms).
Pro script / template: Save message:
const newMsg = await Message.create({ roomId, senderId, text });Retrieve:const msgs = await Message.find({ roomId }).sort({ timestamp: -1 }).limit(50);
📊 Expected results: History loads in under 200ms for 1M messages (with index).
Phase 4: Scaling & Production Readiness
Single server won’t cut it for thousands of concurrent users. In Dhaka, during peak hours (9 PM), traffic spikes 300%. We’ll use Redis as a pub/sub broker to sync Socket.IO instances. Also implement rate limiting and monitoring. A hidden cost: egress bandwidth. A chat app sending 100KB per message (images) can cost ৳5,000/month in bandwidth alone.
Tactic 4.1: Redis Adapter for Multi-Server
Why this works: Without Redis, messages sent to one server won’t reach users connected to another. Redis adapter forward events across instances.
Exactly how to do it:
- Install
@socket.io/redis-adapterandioredis. - Create two Redis clients: pub and sub.
- In server entry, attach adapter:
io.adapter(createAdapter(pubClient, subClient)). - Deploy multiple server instances behind a load balancer (e.g., NGINX with sticky sessions).
- Use a managed Redis (e.g., Redis by Upstash) for simplicity; monthly cost ৳400.
- Test by connecting two clients to different ports; messages should sync.
Pro script / template: Adapter setup:
const { createAdapter } = require('@socket.io/redis-adapter'); const { Redis } = require('ioredis'); const pubClient = new Redis('redis://...'); const subClient = pubClient.duplicate(); io.adapter(createAdapter(pubClient, subClient));
📊 Expected results: Horizontal scaling works seamlessly; messages reach all users.
Tactic 4.2: Rate Limiting & DDoS Protection
Why this works: Prevents abuse; a single user sending 1000 messages/sec can crash your DB.
Exactly how to do it:
- Install
express-rate-limitfor REST endpoints. - For Socket.IO, implement custom rate limiting per socket using a Map.
- Allow max 10 messages per second per socket; disconnect if exceeded.
- Use IP-based rate limiting with
rate-limiter-flexible. - Set up Cloudflare WAF (free tier) in front of your server.
- Monitor with
socket.io-statsor Prometheus.
Pro script / template: Simple rate limiter:
const userLimits = new Map(); setInterval(() => userLimits.clear(), 1000); socket.on('send_message', (data) => { const current = userLimits.get(socket.id) || 0; if (current > 10) return socket.emit('rate_limit'); userLimits.set(socket.id, current + 1); });
📊 Expected results: Abuse stops; uptime remains 99.9%.
Tactic 4.3: Monitoring & Debugging
Why this works: Real-time apps fail silently. You need metrics and logging.
Exactly how to do it:
- Install
winstonfor server logging. - Log connection/disconnection events with timestamps.
- Track concurrent sockets with
io.engine.clientsCount. - Expose metrics endpoint for Prometheus (or use
express-prometheus-middleware). - Set up alerts for high disconnect rates (>5% per minute).
- Use browser devtools to inspect WebSocket frames.
Pro script / template: Log setup:
const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.Console()] });
📊 Expected results: Issues detected within seconds; mean time to resolution < 5 minutes.
🏆 Real Case Study: How a Dhaka-Based Business Achieved 50% User Retention with WebSockets
Client: ChitoChit, a Dhaka-based hyperlocal news app that added a chat feature for community discussions.
Before: They used HTTP polling every 5 seconds. Users complained of 10-second delays. Retention was 20% after 7 days. The app had 5,000 daily active users. Server cost was ৳3,000/month on a single VPS.
Strategy:
- Migrated to Socket.IO with Node.js on a 2 vCPU, 4GB RAM VPS (৳2,000/month).
- Implemented JWT auth with handshake middleware.
- Used MongoDB Atlas free tier for message storage.
- Added typing indicators and read receipts.
- Deployed on a Singapore server for low latency.
Results:
- Latency dropped from 10 seconds to 200ms (98% improvement).
- 7-day retention increased from 20% to 55%.
- Daily active users grew to 12,000 within 3 months.
- Revenue from in-app advertisements increased by 120%, adding ৳45,000 monthly.
- Server cost reduced by 33% (from ৳3,000 to ৳2,000/month).
Client quote: “Switching to WebSockets transformed our app. Users now stay for hours discussing news. Our developers in Banani and Gulshan built it in just 4 weeks.” — Farhan H., CTO of ChitoChit
See more Rafirit Station case studies →
✅ WebSocket Messaging App Checklist
| Task | Status |
|---|---|
| Set up Node.js + Express server | ✅ |
| Initialize Socket.IO on server and client | ✅ |
| Implement user registration and JWT | ✅ |
| Socket.IO auth middleware | ✅ |
| Room-based private messaging | ✅ |
| Typing indicators and read receipts | ⚠️ |
| Message persistence in MongoDB | ✅ |
| Pagination and history API | ✅ |
| Redis adapter for horizontal scaling | ❌ |
| Rate limiting and DDoS protection | ⚠️ |
| Logging and monitoring | ✅ |
| Deploy behind Cloudflare CDN | ✅ |
| Cost calculation in BDT | ✅ |
❓ Frequently Asked Questions
🎯 The Bottom Line
Building a real-time messaging app with WebSockets is more than just connecting sockets—it’s about designing for scale, security, and the Bangladeshi market’s unique challenges (unstable internet, growing mobile usage). The counterintuitive takeaway: don’t over-optimize for scalability from day one. Start with a single Node.js server, get your authentication right, and only add Redis when you hit 10,000 concurrent users. Most Dhaka startups never reach that point. Focus on user experience: typing indicators, read receipts, and offline message queues will retain users better than a perfectly scaled backend. Remember, a 100ms delay can cost ৳5,000 per day in lost revenue for a mid-size app.
⚡ Your Next Step (Do This Today)
- Sign up for a free DigitalOcean account (credit: $200 for new users).
- Deploy the Socket.IO server from this guide using the provided code snippets.
- Create a JWT authentication endpoint with bcrypt and store users in MongoDB.
- Build a simple React chat UI with message input and display.
- Test with two browser tabs—you’ll see messages appear in real-time within 30 minutes.
Ready to Get Results?
Our team at Rafirit Station in Dhaka (Gulshan, Banani) can help you build a production-ready real-time app. We offer end-to-end development, from WebSocket architecture to deployment and monitoring. Book a free strategy call to discuss your project.
💬 Drop “build real-time messaging app WebSockets” in the comments and we’ll send you our free WebSocket architecture checklist — no email required.