npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

mern-chatkit

v1.0.3

Published

A plug-and-play real-time chat system for MERN stack applications with Socket.io support

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.

npm version License: MIT

✨ 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

npm install mern-chatkit

Then initialize the chat system in your project:

npx mern-chatkit init

This 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_database

2. 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 counts

Fetching 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_SECRET to your .env file
  • Ensure you're loading env variables before initializing ChatKit

2. "chatkit/ folder not found"

  • Run npx mern-chatkit init to 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