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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shuhaib-enfin/chat-server

v1.1.0

Published

Drop-in NestJS chat module — Socket.IO gateway, MongoDB persistence, file uploads, presence, calls. Mount via ChatModule.forRoot({ apiKey }).

Readme

@shuhaib-enfin/chat-server

NestJS chat module — Socket.IO gateway, MongoDB persistence, file uploads, presence, and audio calls.

Install

npm install @shuhaib-enfin/chat-server @shuhaib-enfin/chat-shared

Quick Start (CLI)

For quick local testing:

npx chat-server start --apiKey=chat_xxx --port=3002

Options:

  • --apiKey (required): Your API key
  • --port (default: 3002): Server port
  • --mongoUri (default: mongodb://localhost:27017/chat_sdk): MongoDB connection string
  • --mode (default: managed): managed or external-db

Mount in Your NestJS App

Option 1: Fresh MongoDB (no existing Mongoose)

Use ChatMongooseModule to create the connection, then mount ChatModule:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ChatMongooseModule, ChatModule } from '@shuhaib-enfin/chat-server';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    ChatMongooseModule.forRoot(process.env.MONGO_URI || 'mongodb://localhost:27017/myapp'),
    ChatModule.forRoot({
      apiKey: process.env.CHAT_API_KEY || 'chat_xxx',
    }),
  ],
})
export class AppModule {}

Option 2: Already Have Mongoose

If your app already calls MongooseModule.forRoot(uri), skip ChatMongooseModule:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { ChatModule } from '@shuhaib-enfin/chat-server';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    MongooseModule.forRoot('mongodb://localhost:27017/myapp'),
    ChatModule.forRoot({
      apiKey: process.env.CHAT_API_KEY || 'chat_xxx',
    }),
  ],
})
export class AppModule {}

ChatModule registers its schemas on whatever Mongoose connection exists.

ChatModuleOptions

| Option | Type | Required | Description | |--------|------|----------|-------------| | apiKey | string | Yes | Your platform API key | | mongoUri | string | No | Customer-provided DB (external-db mode) | | mode | 'managed' | 'external-db' | No | Default: managed | | version | string | No | API version string | | platformMongoUri | string | No | Platform DB for key validation | | uploadDir | string | No | Where uploaded files are written. Relative paths resolve against process.cwd(). Overrides UPLOAD_DIR env. | | fileUploadHandler | function | No | Custom upload handler. Receives the parsed file and returns the URL. Use for S3 / Azure / GCS. See Custom file upload handler. |

Modes

managed (default)

The chat server validates API keys against its own database. Use this when you control the keys and want the server to manage everything.

external-db

The customer provides their own MongoDB. The server stores chat data there but still validates keys against your platform. Use this for customers who want isolation.

Environment Variables

| Variable | Description | |----------|-------------| | MONGO_URI | Fallback MongoDB URI (used if not provided in options) | | PORT | Server port (default: 3002) | | UPLOAD_DIR | Where uploaded files are written. Relative paths resolve against process.cwd() (e.g. public/images<cwd>/public/images). Absolute paths are used as-is. Default: uploads (i.e. <cwd>/uploads). |

Configuring the upload directory

Resolution order: options.uploadDirprocess.env.UPLOAD_DIRuploads (relative to cwd).

# Default: <cwd>/uploads
node dist/main.js

# Relative path: <cwd>/public/images  (the file URL is still /uploads/<uuid>.<ext>)
UPLOAD_DIR=public/images node dist/main.js

# Absolute path
UPLOAD_DIR=/var/data/chat-uploads node dist/main.js

Or in NestJS options:

ChatModule.forRoot({
  apiKey: process.env.CHAT_API_KEY!,
  uploadDir: 'public/images',   // resolves to <cwd>/public/images
}),

The directory is created automatically on startup if it does not exist. Uploaded files are served at GET /uploads/<filename> (handled by ChatModule — no extra useStaticAssets needed in your main.ts).

Custom file upload handler

For production deployments you usually want files in object storage (S3, Azure Blob, Google Cloud Storage) instead of on the chat server's disk. Pass a fileUploadHandler to ChatModule.forRoot to take over the upload step.

The handler receives the file (already parsed by multer — file.path is on local disk and file.buffer is in memory) plus the validated tenantId and roomId, and returns the URL where the file is now reachable. The URL is stored in the Message exactly like the default behaviour — no frontend changes are needed.

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { ChatModule, FileUploadHandler } from '@shuhaib-enfin/chat-server';

const s3 = new S3Client({ region: 'us-east-1' });

const uploadToS3: FileUploadHandler = async ({ file, tenantId, roomId }) => {
  const key = `${tenantId}/${roomId}/${Date.now()}-${file.originalname}`;
  await s3.send(new PutObjectCommand({
    Bucket: 'my-chat-uploads',
    Key: key,
    Body: file.buffer ?? require('fs').createReadStream(file.path),
    ContentType: file.mimetype,
  }));
  return {
    fileUrl: `https://my-chat-uploads.s3.amazonaws.com/${key}`,
    fileName: file.originalname,
    fileType: file.mimetype,
    fileSize: file.size,
  };
};

ChatModule.forRoot({
  apiKey: process.env.CHAT_API_KEY!,
  fileUploadHandler: uploadToS3,
});

Notes:

  • If fileUploadHandler is not provided, the default multer-to-disk behaviour is unchanged.
  • The handler is called after API key and room validation, so it only ever sees authorised uploads.
  • Return any URL the frontend can fetch — absolute (S3, CDN) or relative (your own CDN in front of the bucket).
  • For tenant isolation, namespace the storage key with tenantId (as shown above) so tenants cannot read each other's files.
  • The built-in GET /uploads/<filename> route is still registered but will be unused when you return external URLs. It is harmless to leave in place.

REST Endpoints

All endpoints require x-api-key header unless noted.

| Method | Path | Description | |--------|-----|-------------| | POST | /api/validation/validate | Validate API key | | POST | /api/users/register | Register a user | | GET | /api/users | List all users (with presence) | | GET | /api/rooms | List rooms for a user | | POST | /api/rooms/direct | Open/get direct room | | GET | /api/rooms/:roomId/messages | Get room messages | | POST | /api/upload | Upload file (multipart/form-data) |

Socket Events

Namespace: /chat

Connect

Handshake auth required:

socket = io('http://localhost:3002/chat', {
  auth: { apiKey: 'chat_xxx', userId: 'u1', userName: 'Alice' }
});

Client → Server

| Event | Payload | Description | |-------|--------|-------------| | message | { roomId, content } | Send message | | typing | { roomId, isTyping } | Typing indicator | | presence | { status } | Set presence (online/away/offline) | | join-room | { roomId } | Join a room | | leave-room | { roomId } | Leave a room | | call | { to, offer } | WebRTC offer | | call-accept | { callId, answer } | WebRTC answer | | call-reject | { callId } | Reject call | | call-end | { callId } | End call |

Server → Client

| Event | Payload | Description | |-------|--------|-------------| | message | { roomId, messages[] } | New message(s) | | typing | { roomId, userId, isTyping } | User typing | | presence:changed | { userId, status } | Presence update | | room:updated | { room } | Room updated | | call | { callId, from, offer } | Incoming call | | call-accepted | { callId, answer } | Call accepted | | call-rejected | { callId } | Call rejected | | call-ended | { callId } | Call ended | | error | { code, message } | Error event | | users:list | { users[] } | User list on join |

File Uploads

POST to /api/upload with multipart/form-data:

  • Field: file
  • Header: x-api-key
  • Body: { roomId: string }

Response:

{
  "success": true,
  "fileUrl": "/uploads/uuid-filename.jpg",
  "fileName": "photo.jpg",
  "fileType": "image/jpeg",
  "fileSize": 12345
}

Max file size: 50MB. Files are served at GET /uploads/<filename> by the SDK's built-in controller — you do not need to register useStaticAssets in your main.ts.

Production: the built-in disk storage is for development and small deployments. For production pass a fileUploadHandler to ChatModule.forRoot that streams the file to S3 / Azure Blob / GCS and returns the public URL. See Custom file upload handler.

Troubleshooting

EADDRINUSE

Another process is on your port. Kill it or use a different port:

# Find process
netstat -ano | findstr :3002
# Kill on Windows
taskkill /PID <pid> /F

MongoDB connection failed

Ensure MongoDB is running and the URI is correct. If using Docker:

docker run -d -p 27017:27017 mongo

Invalid apiKey

Keys must exist in your platform database. In managed mode, the server looks up the key on startup. In external-db mode, provide valid keys in your customer's DB.


For frontend SDK, see @shuhaib-enfin/chat.