@enfin/chat-server
v1.3.2
Published
Drop-in NestJS chat module — Socket.IO gateway, MongoDB persistence, file uploads, presence, calls. Mount via ChatModule.forRoot({ apiKey }).
Readme
@enfin/chat-server
NestJS chat module — Socket.IO gateway, MongoDB persistence, file uploads, presence, and audio calls.
Install
npm install @enfin/chat-server @enfin/chat-sharedQuick Start (CLI)
For quick local testing:
npx chat-server start --apiKey=chat_xxx --port=3002Options:
--apiKey(required): Your API key--port(default: 3002): Server port--mongoUri(default: mongodb://localhost:27017/chat_sdk): MongoDB connection string--mode(default: managed):managedorexternal-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 '@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 '@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 the admin BE. The admin BE URL is read from the ADMIN_API_VALIDATION_URL env var (or API_VALIDATION_URL / VALIDATION_URL as fallbacks). The admin BE is a separate NestJS service that owns the API key registry — see the admin/ directory or use the hosted instance at https://prtl-ms.mr-coder.io to generate keys.
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). |
| ADMIN_API_VALIDATION_URL | Optional. URL of the admin BE that owns your tenant's API key registry. Defaults to https://api-ms.mr-coder.io (the hosted platform). Override in your chat-server's .env only if you self-host the admin BE or use a private white-label deployment. Resolution chain: ADMIN_API_VALIDATION_URL → API_VALIDATION_URL → VALIDATION_URL → default. |
| API_VALIDATION_URL | Fallback name for ADMIN_API_VALIDATION_URL |
| VALIDATION_URL | Fallback name for ADMIN_API_VALIDATION_URL |
Configuring the upload directory
Resolution order: options.uploadDir → process.env.UPLOAD_DIR → uploads (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.jsOr 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).
API key validation
Every API key sent by a client (HTTP x-api-key header, socket auth, or body) is validated against an admin BE that owns the API key registry. The chat-server calls POST ${ADMIN_API_VALIDATION_URL}/api/validation/validate with { apiKey, clientVersion } and caches the result for 5 minutes.
Where ADMIN_API_VALIDATION_URL points
It depends on who issued your API key:
| You got your API key from | ADMIN_API_VALIDATION_URL should be |
|---|---|
| The hosted platform at https://prtl-ms.mr-coder.io | Leave unset — https://api-ms.mr-coder.io is the default |
| Your own self-hosted admin BE (running the admin/back-end source from this repo) | The URL where you deployed it, e.g. https://admin.yourcompany.com or http://localhost:3001 for local dev |
| A private white-label deployment of the platform | Whatever the platform operator gave you |
The default is https://api-ms.mr-coder.io (the hosted platform), defined in src/constants.ts. Most consumers can leave ADMIN_API_VALIDATION_URL unset — it's only required if you self-host the admin BE. To change the default for the whole package, edit src/constants.ts.
Example .env
# chat-server/.env
# Database
MONGO_URI=mongodb://localhost:27017/chat_sdk
# API key validation — who issued your key
ADMIN_API_VALIDATION_URL=https://api-ms.mr-coder.io
# Server
PORT=3002
CHAT_MODE=managedValidation cache
Results are cached in-memory for 5 minutes per key (VALIDATION_CACHE_TTL_MS). To invalidate early (e.g. after a key is revoked), call apiKeyService.clearCache() from your own code.
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 '@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
fileUploadHandleris 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
fileUploadHandlertoChatModule.forRootthat 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> /FMongoDB connection failed
Ensure MongoDB is running and the URI is correct. If using Docker:
docker run -d -p 27017:27017 mongoInvalid 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.
ADMIN_API_VALIDATION_URL not configured
This error is no longer possible in v1.3.1+. The chat-server always has a default (https://api-ms.mr-coder.io from src/constants.ts) and only needs the env var if you're overriding the default — for example, to point at a self-hosted admin BE.
# chat-server/.env (only needed for self-hosted admin BE)
ADMIN_API_VALIDATION_URL=https://admin.yourcompany.comFor frontend SDK, see @enfin/chat.
