mern-chatkit
v1.0.3
Published
A plug-and-play real-time chat system for MERN stack applications with Socket.io support
Maintainers
Readme
📦 MERN ChatKit
A plug-and-play real-time chat system for MERN stack applications. Add a complete, professional, scalable chat system to your backend with just a few lines of code.
✨ Features
- 🚀 One-command setup - Initialize with
npx mern-chatkit init - 💬 Real-time messaging - Powered by Socket.io
- 👥 Direct & Group chats - Support for 1-on-1 and group conversations
- 📎 File attachments - Upload images, videos, documents
- ✅ Read receipts - Seen/unseen message tracking
- ⌨️ Typing indicators - Real-time typing status
- 🟢 Online status - User online/offline tracking
- 🔐 JWT Authentication - Secure API and socket connections
- 📝 Message editing - Edit and delete messages
- 🔄 Reply to messages - Thread-style conversations
📋 Table of Contents
- Installation
- Quick Start
- Configuration
- Folder Structure
- API Endpoints
- Socket Events
- Client Integration
- Examples
- Troubleshooting
📥 Installation
npm install mern-chatkitThen initialize the chat system in your project:
npx mern-chatkit initThis will create a chatkit/ folder in your project with all the necessary files.
🚀 Quick Start
1. Add Environment Variables
Add these to your .env file:
CHATKIT_JWT_SECRET=your_super_secret_jwt_key_here
CHATKIT_MONGO_URI=mongodb://localhost:27017/your_database2. Integrate with Your Server
// server.js
const express = require('express');
const http = require('http');
const { initChatKit } = require('mern-chatkit');
const app = express();
const server = http.createServer(app);
// Your existing middleware
app.use(express.json());
// Initialize ChatKit
initChatKit(server, app);
// Your other routes...
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});That's it! Your chat system is now ready. 🎉
⚙️ Configuration
Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| CHATKIT_JWT_SECRET | Yes | Secret key for JWT token verification |
| CHATKIT_MONGO_URI | No | MongoDB connection string (uses existing connection if not provided) |
Options
You can pass options to initChatKit:
initChatKit(server, app, {
chatkitPath: './chatkit', // Path to chatkit folder
corsOrigin: '*', // CORS origin for Socket.io
uploadDir: './chatkit/uploads', // Directory for file uploads
maxFileSize: 10 * 1024 * 1024 // Max file size (10MB)
});📁 Folder Structure
After running npx mern-chatkit init, you'll have:
chatkit/
├── controllers/
│ └── chat.controller.js # API request handlers
├── models/
│ ├── conversation.model.js # Conversation schema
│ └── message.model.js # Message schema
├── routes/
│ └── chat.routes.js # Express routes
├── sockets/
│ └── chat.socket.js # Socket.io event handlers
├── utils/
│ └── jwtAuth.js # JWT authentication middleware
├── uploads/ # File uploads directory
└── index.js # ChatKit initialization🔌 API Endpoints
All endpoints require authentication via Bearer token in the Authorization header.
Conversations
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /api/chatkit/conversations | Create a new conversation |
| GET | /api/chatkit/conversations | Get user's conversations |
| GET | /api/chatkit/conversations/:id | Get single conversation |
Messages
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /api/chatkit/conversations/:id/messages | Send a message |
| GET | /api/chatkit/conversations/:id/messages | Get messages |
| PUT | /api/chatkit/conversations/:id/seen | Mark messages as seen |
| PUT | /api/chatkit/messages/:id | Edit a message |
| DELETE | /api/chatkit/messages/:id | Delete a message |
Group Management
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /api/chatkit/conversations/:id/participants | Add participant |
| DELETE | /api/chatkit/conversations/:id/participants/:pid | Remove participant |
API Request Examples
Create Direct Conversation
POST /api/chatkit/conversations
{
"participantIds": ["user_id_here"]
}Create Group Conversation
POST /api/chatkit/conversations
{
"participantIds": ["user1_id", "user2_id"],
"isGroup": true,
"groupName": "Project Team",
"groupAvatar": "https://example.com/avatar.png"
}Send Message
POST /api/chatkit/conversations/:conversationId/messages
{
"text": "Hello, world!",
"replyTo": "message_id" // optional
}Send Message with Attachments
// Use multipart/form-data
POST /api/chatkit/conversations/:conversationId/messages
FormData:
- text: "Check out this file"
- attachments: [file1, file2, ...]🔔 Socket Events
Client → Server Events
| Event | Payload | Description |
|-------|---------|-------------|
| join:room | conversationId | Join a conversation room |
| leave:room | conversationId | Leave a conversation room |
| message:send | { conversationId, text, replyTo?, attachments? } | Send a message |
| message:seen | { conversationId, messageIds? } | Mark messages as seen |
| typing:start | { conversationId } | Start typing indicator |
| typing:stop | { conversationId } | Stop typing indicator |
| users:online:get | { userIds: [] } | Get online status of users |
Server → Client Events
| Event | Payload | Description |
|-------|---------|-------------|
| message:receive | { message, conversationId } | New message received |
| message:seen:update | { conversationId, userId, seenAt } | Message seen status update |
| message:deleted | { messageId, conversationId } | Message was deleted |
| message:edited | { message, conversationId } | Message was edited |
| typing:update | { conversationId, userId, isTyping } | Typing status update |
| user:online | { userId, timestamp } | User came online |
| user:offline | { userId, timestamp } | User went offline |
| user:joined | { userId, conversationId } | User joined room |
| user:left | { userId, conversationId } | User left room |
| users:online:response | [{ userId, isOnline }] | Online status response |
| error | { message } | Error occurred |
💻 Client Integration
React Example with Socket.io Client
// Install: npm install socket.io-client
import { io } from 'socket.io-client';
import { useEffect, useState } from 'react';
const SOCKET_URL = 'http://localhost:5000';
function useChat(token) {
const [socket, setSocket] = useState(null);
const [messages, setMessages] = useState([]);
const [typingUsers, setTypingUsers] = useState({});
useEffect(() => {
// Connect with authentication
const newSocket = io(SOCKET_URL, {
auth: { token }
});
newSocket.on('connect', () => {
console.log('Connected to chat server');
});
// Listen for new messages
newSocket.on('message:receive', ({ message, conversationId }) => {
setMessages(prev => [...prev, message]);
});
// Listen for typing updates
newSocket.on('typing:update', ({ conversationId, userId, isTyping }) => {
setTypingUsers(prev => ({
...prev,
[conversationId]: {
...prev[conversationId],
[userId]: isTyping
}
}));
});
// Listen for online status
newSocket.on('user:online', ({ userId }) => {
console.log(`User ${userId} is online`);
});
newSocket.on('user:offline', ({ userId }) => {
console.log(`User ${userId} is offline`);
});
setSocket(newSocket);
return () => newSocket.close();
}, [token]);
const joinRoom = (conversationId) => {
socket?.emit('join:room', conversationId);
};
const leaveRoom = (conversationId) => {
socket?.emit('leave:room', conversationId);
};
const sendMessage = (conversationId, text, replyTo = null) => {
socket?.emit('message:send', { conversationId, text, replyTo });
};
const startTyping = (conversationId) => {
socket?.emit('typing:start', { conversationId });
};
const stopTyping = (conversationId) => {
socket?.emit('typing:stop', { conversationId });
};
const markAsSeen = (conversationId) => {
socket?.emit('message:seen', { conversationId });
};
return {
socket,
messages,
typingUsers,
joinRoom,
leaveRoom,
sendMessage,
startTyping,
stopTyping,
markAsSeen
};
}
export default useChat;Chat Component Example
import React, { useState, useEffect } from 'react';
import useChat from './useChat';
function ChatRoom({ conversationId, token }) {
const {
messages,
typingUsers,
joinRoom,
leaveRoom,
sendMessage,
startTyping,
stopTyping,
markAsSeen
} = useChat(token);
const [inputText, setInputText] = useState('');
const [isTyping, setIsTyping] = useState(false);
useEffect(() => {
joinRoom(conversationId);
return () => leaveRoom(conversationId);
}, [conversationId]);
const handleInputChange = (e) => {
setInputText(e.target.value);
if (!isTyping) {
setIsTyping(true);
startTyping(conversationId);
}
// Stop typing after 2 seconds of inactivity
clearTimeout(window.typingTimeout);
window.typingTimeout = setTimeout(() => {
setIsTyping(false);
stopTyping(conversationId);
}, 2000);
};
const handleSend = () => {
if (inputText.trim()) {
sendMessage(conversationId, inputText);
setInputText('');
stopTyping(conversationId);
}
};
return (
<div className="chat-room">
<div className="messages">
{messages.map(msg => (
<div key={msg._id} className="message">
<strong>{msg.senderId.name}:</strong> {msg.text}
</div>
))}
</div>
{Object.values(typingUsers[conversationId] || {}).some(Boolean) && (
<div className="typing-indicator">Someone is typing...</div>
)}
<div className="input-area">
<input
value={inputText}
onChange={handleInputChange}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
placeholder="Type a message..."
/>
<button onClick={handleSend}>Send</button>
</div>
</div>
);
}
export default ChatRoom;📚 Examples
Fetching Conversations
// Using fetch
const response = await fetch('/api/chatkit/conversations', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const { data } = await response.json();
console.log(data); // Array of conversations with unread countsFetching Messages with Pagination
// Get older messages (before a certain date)
const response = await fetch(
`/api/chatkit/conversations/${conversationId}/messages?limit=50&before=${lastMessageDate}`,
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
const { data, hasMore } = await response.json();Uploading Files
const formData = new FormData();
formData.append('text', 'Check out these files!');
formData.append('attachments', file1);
formData.append('attachments', file2);
const response = await fetch(
`/api/chatkit/conversations/${conversationId}/messages`,
{
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: formData
}
);🔧 Troubleshooting
Common Issues
1. "CHATKIT_JWT_SECRET not set"
- Make sure you've added
CHATKIT_JWT_SECRETto your.envfile - Ensure you're loading env variables before initializing ChatKit
2. "chatkit/ folder not found"
- Run
npx mern-chatkit initto create the folder - Make sure you're running it in your project root
3. Socket connection fails
- Verify the token is being passed correctly in socket auth
- Check CORS settings if connecting from a different origin
4. MongoDB connection issues
- If using
CHATKIT_MONGO_URI, ensure the connection string is correct - If using existing connection, make sure Mongoose is connected before initializing ChatKit
Debug Mode
Enable debug logging by checking the console for [ChatKit] prefixed messages.
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
MIT © 2024 AdilSagheer
Made with ❤️ by AdilSagheer for the MERN community
