Back to Blog
Development & Programming
Quick Tip: Use Gesture Handler in Expo React Native
Master React Native gestures in 30 seconds. Code snippet for smooth mobile interactions.
TechGeekStack TeamOctober 28, 2025 3 min read
💬 Build a Modern Chat App from Scratch
Learn to build a real-time chat application using React, Socket.IO, and Node.js. Complete with user authentication, message history, and modern UI components.
🎯 What We're Building
✨ Features:
- ✅ Real-time messaging with Socket.IO
- ✅ User authentication & profiles
- ✅ Message history & persistence
- ✅ Online/offline status indicators
- ✅ Typing indicators
- ✅ Responsive design for mobile/desktop
- ✅ Emoji support 😊
- ✅ Dark/light theme toggle
🛠️ Technology Stack
Frontend 🎨
- • React 18 with hooks
- • Socket.IO client
- • Tailwind CSS for styling
- • React Router for navigation
Backend ⚙️
- • Node.js + Express
- • Socket.IO server
- • MongoDB for data storage
- • JWT for authentication
🚀 Step 1: Project Setup
# Create project structure mkdir chat-app cd chat-app # Backend setup mkdir server cd server npm init -y npm install express socket.io mongoose bcryptjs jsonwebtoken cors dotenv # Frontend setup cd .. npx create-react-app client cd client npm install socket.io-client react-router-dom lucide-react # Project structure: # chat-app/ # ├── server/ # Node.js backend # └── client/ # React frontend
⚙️ Step 2: Backend Setup
Server Configuration (server/index.js):
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"]
}
});
app.use(cors());
app.use(express.json());
// MongoDB connection
mongoose.connect('mongodb://localhost:27017/chatapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// Message model
const Message = mongoose.model('Message', {
username: String,
message: String,
timestamp: { type: Date, default: Date.now },
room: { type: String, default: 'general' }
});
// User model
const User = mongoose.model('User', {
username: { type: String, unique: true, required: true },
email: { type: String, unique: true, required: true },
password: { type: String, required: true },
isOnline: { type: Boolean, default: false },
lastSeen: { type: Date, default: Date.now }
});
// Socket connection handling
const activeUsers = new Map();
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// User joins
socket.on('userJoined', async (userData) => {
socket.username = userData.username;
activeUsers.set(socket.id, userData.username);
// Update user online status
await User.findOneAndUpdate(
{ username: userData.username },
{ isOnline: true }
);
// Broadcast user joined
socket.broadcast.emit('userJoined', {
username: userData.username,
message: `${userData.username} joined the chat`
});
// Send online users list
io.emit('onlineUsers', Array.from(activeUsers.values()));
});
// Handle new messages
socket.on('sendMessage', async (messageData) => {
const message = new Message({
username: messageData.username,
message: messageData.message,
room: messageData.room || 'general'
});
await message.save();
// Broadcast message to all users
io.emit('receiveMessage', {
id: message._id,
username: message.username,
message: message.message,
timestamp: message.timestamp
});
});
// Typing indicators
socket.on('typing', (data) => {
socket.broadcast.emit('userTyping', {
username: data.username,
isTyping: data.isTyping
});
});
// Handle disconnect
socket.on('disconnect', async () => {
if (socket.username) {
activeUsers.delete(socket.id);
// Update user offline status
await User.findOneAndUpdate(
{ username: socket.username },
{ isOnline: false, lastSeen: new Date() }
);
socket.broadcast.emit('userLeft', {
username: socket.username,
message: `${socket.username} left the chat`
});
// Update online users list
io.emit('onlineUsers', Array.from(activeUsers.values()));
}
});
});
// REST API routes
app.post('/api/messages', async (req, res) => {
try {
const messages = await Message.find()
.sort({ timestamp: -1 })
.limit(50);
res.json(messages.reverse());
} catch (error) {
res.status(500).json({ error: error.message });
}
});
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
🎨 Step 3: Frontend Components
Main Chat Component (client/src/components/ChatRoom.js):
import React, { useState, useEffect, useRef } from 'react';
import io from 'socket.io-client';
import { Send, Smile, Users } from 'lucide-react';
const socket = io('http://localhost:5000');
function ChatRoom({ username }) {
const [messages, setMessages] = useState([]);
const [inputMessage, setInputMessage] = useState('');
const [onlineUsers, setOnlineUsers] = useState([]);
const [typingUsers, setTypingUsers] = useState([]);
const messagesEndRef = useRef(null);
const typingTimeoutRef = useRef(null);
useEffect(() => {
// Join chat
socket.emit('userJoined', { username });
// Load message history
fetch('http://localhost:5000/api/messages')
.then(res => res.json())
.then(data => setMessages(data));
// Socket event listeners
socket.on('receiveMessage', (message) => {
setMessages(prev => [...prev, message]);
});
socket.on('userJoined', (data) => {
setMessages(prev => [...prev, {
id: Date.now(),
username: 'System',
message: data.message,
timestamp: new Date(),
isSystem: true
}]);
});
socket.on('userLeft', (data) => {
setMessages(prev => [...prev, {
id: Date.now(),
username: 'System',
message: data.message,
timestamp: new Date(),
isSystem: true
}]);
});
socket.on('onlineUsers', (users) => {
setOnlineUsers(users);
});
socket.on('userTyping', (data) => {
if (data.isTyping) {
setTypingUsers(prev => [...prev.filter(u => u !== data.username), data.username]);
} else {
setTypingUsers(prev => prev.filter(u => u !== data.username));
}
});
return () => {
socket.off('receiveMessage');
socket.off('userJoined');
socket.off('userLeft');
socket.off('onlineUsers');
socket.off('userTyping');
};
}, [username]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const sendMessage = (e) => {
e.preventDefault();
if (inputMessage.trim()) {
socket.emit('sendMessage', {
username,
message: inputMessage.trim()
});
setInputMessage('');
// Stop typing indicator
socket.emit('typing', { username, isTyping: false });
}
};
const handleTyping = (e) => {
setInputMessage(e.target.value);
// Send typing indicator
socket.emit('typing', { username, isTyping: true });
// Clear typing after 2 seconds
clearTimeout(typingTimeoutRef.current);
typingTimeoutRef.current = setTimeout(() => {
socket.emit('typing', { username, isTyping: false });
}, 2000);
};
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit'
});
};
return (
{/* Sidebar */}
Online ({onlineUsers.length})
{onlineUsers.map((user, index) => (
{user}
))}
{/* Chat Area */}
{/* Header */}
General Chat
{/* Messages */}
{messages.map((msg) => (
{!msg.isSystem && (
{msg.username} • {formatTime(msg.timestamp)}
)}
{msg.message}
))}
{/* Typing indicators */}
{typingUsers.length > 0 && (
{typingUsers.join(', ')} {typingUsers.length === 1 ? 'is' : 'are'} typing...
)}
{/* Message Input */}
);
}
export default ChatRoom;
💡 Pro Tips:
- • Use MongoDB Atlas for cloud database
- • Add message encryption for security
- • Implement file/image sharing
- • Add push notifications for mobile
- • Use Redis for scaling Socket.IO across servers
🚀 Step 4: Running the Application
# Start MongoDB (make sure it's installed) mongod # Start backend server cd server npm run dev # Start frontend (in new terminal) cd client npm start # Open your browser to http://localhost:3000
✨ Enhancement Ideas
- 🔐 Authentication: Add JWT-based login system
- 🏠 Rooms: Create separate chat rooms
- 📱 Mobile App: Convert to React Native
- 🔔 Notifications: Browser push notifications
- 📁 File Sharing: Upload images and documents
- 🎨 Themes: Customizable color schemes
- 🌍 Internationalization: Multiple language support
📊 Performance Optimization
⚡ Optimization Techniques:
- • Message Pagination: Load messages in chunks
- • Virtual Scrolling: Handle thousands of messages
- • Debounced Typing: Reduce socket events
- • Connection Pooling: Efficient database queries
- • CDN: Serve static assets fast
💻 Master Full-Stack Development
Learn to build real-time applications, APIs, databases, and modern React apps. Our comprehensive web development course covers everything from basics to advanced deployment.
Explore Web Development Course →🎯 What You Learned
- ✅ Real-time communication with Socket.IO
- ✅ React hooks and state management
- ✅ Node.js server setup and API design
- ✅ MongoDB integration and data modeling
- ✅ Responsive UI design with Tailwind CSS
- ✅ User authentication concepts
Congratulations! You've built a production-ready chat application. Now customize it and make it your own! 🚀
Tags
#React Native#Expo#Gesture Handler#Mobile Dev#Quick Tips
Related Articles
Development & Programming
Vibe Coding - The New Wave of Coding with LLMs
6 min readDevelopment & Programming
How to Use ChatGPT for .NET Core API Development
4 min readDevelopment & Programming
Comparison: React vs Vue in 45 Seconds
5 min read💡 Want to learn more?
Explore our comprehensive courses on AI, programming, and robotics.
Browse Courses