@littra/chat-engine-backend
v0.1.1
Published
Multi-tenant chat engine backend package
Readme
@littra/chat-engine-backend
Multi-tenant chat backend package with pluggable storage adapters and optional Socket.IO transport wiring.
Install
npm install @littra/chat-engine-backendCore usage
import { ChatModule } from "@littra/chat-engine-backend";
const runtime = ChatModule.register({
tenantResolver: () => "tenant-a",
storage: { type: "in-memory" }, // postgres | mongo | in-memory | custom
});
const { chatService } = runtime;Storage options
Postgres
ChatModule.register({
tenantResolver: () => "tenant-a",
storage: {
type: "postgres",
connectionString: "postgres://localhost:5432/chat_engine",
},
});Mongo
ChatModule.register({
tenantResolver: () => "tenant-a",
storage: {
type: "mongo",
uri: "mongodb://localhost:27017",
dbName: "chat_engine",
},
});Socket transport (optional)
Use package socket wiring while keeping auth/bootstrap in host app.
import { ChatModule, ChatSocketServer } from "@littra/chat-engine-backend";
import { createServer } from "node:http";
const httpServer = createServer(app);
const runtime = ChatModule.register({
tenantResolver: () => "tenant-a",
storage: { type: "in-memory" },
});
ChatSocketServer.attach({
server: httpServer,
chatService: runtime.chatService,
authProvider: ({ token }) => verifyToken(token), // must return { tenantId, userId }
ioOptions: { cors: { origin: "*" } },
});Default socket events:
join_conversationleave_conversationsend_messagetyping_starttyping_stopmark_read- outputs:
message_received,typing_started,typing_stopped,read_receipt
Socket Contract
All inbound socket events support acknowledgement callbacks.
Ack success envelope:
{ "ok": true, "...": "event-specific fields" }Ack error envelope:
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR | UNAUTHORIZED | FORBIDDEN | NOT_FOUND | INTERNAL_ERROR",
"message": "human-readable reason",
"details": "optional"
}
}Inbound events (with payload):
join_conversation- payload:
{ "conversationId": "string" } - ack success:
{ "ok": true }
- payload:
leave_conversation- payload:
{ "conversationId": "string" } - ack success:
{ "ok": true }
- payload:
send_message- payload:
{ "conversationId": "string", "content": "string", "messageType?": "text | system" } - ack success:
{ "ok": true, "message": ChatMessage }
- payload:
typing_start- payload:
{ "conversationId": "string" } - ack success:
{ "ok": true }
- payload:
typing_stop- payload:
{ "conversationId": "string" } - ack success:
{ "ok": true }
- payload:
mark_read- payload:
{ "conversationId": "string", "messageId": "string" } - ack success:
{ "ok": true, "receipt": ReadReceipt }
- payload:
Outbound events (server emits):
message_received- payload:
{ "tenantId": "string", "conversationId": "string", "message": ChatMessage }
- payload:
typing_started- payload:
{ "tenantId": "string", "conversationId": "string", "userId": "string", "at": "ISO-8601" }
- payload:
typing_stopped- payload:
{ "tenantId": "string", "conversationId": "string", "userId": "string", "at": "ISO-8601" }
- payload:
read_receipt- payload:
ReadReceipt
- payload:
Runtime payload validation:
ChatSocketServervalidates inbound payloads withzod.- Invalid payloads return
VALIDATION_ERRORin ack error envelope.
Custom event names:
ChatSocketServer.attach({
server: httpServer,
chatService: runtime.chatService,
authProvider,
eventNames: {
sendMessage: "send_msg",
messageReceived: "msg_recv",
},
});Auth hardening checklist
- Verify JWT signature and algorithm (never trust decoded payload without verification).
- Validate required claims for every socket connection:
tenantId,userId,exp. - Reject cross-tenant access by deriving room names from auth context, not client payload.
- Keep token TTL short and rotate signing keys (support key-id based verification).
- Add rate limits per user/IP for
send_messageand auth handshake attempts. - Log auth failures with sanitized context (no raw token in logs).
Minimal auth provider example:
import jwt from "jsonwebtoken";
const authProvider = ({ token }: { token?: string }) => {
if (!token) {
throw new Error("unauthorized: missing token");
}
const payload = jwt.verify(token, process.env.CHAT_JWT_SECRET!, {
algorithms: ["HS256"],
}) as { tenantId?: string; sub?: string };
if (!payload.tenantId || !payload.sub) {
throw new Error("unauthorized: missing required claims");
}
return { tenantId: payload.tenantId, userId: payload.sub };
};Nest integration
import { NestChatModule } from "@littra/chat-engine-backend";
@Module({
imports: [
NestChatModule.register({
tenantResolver: () => "tenant-a",
storage: { type: "in-memory" },
}),
],
})
export class AppModule {}Build and test
npm run build
npm test
npm run test:coverage