Production-grade Node.js + Express backend for a multi-tenant AI customer support platform
SupportFlow AI is a multi-tenant AI-powered customer support platform. Each registered business gets a fully isolated workspace — with its own agents, customers, tickets, conversations, knowledge base, and analytics.
This repository is the Node.js + Express backend server. It exposes a REST API, a Socket.io real-time layer, and an AI orchestration service that processes every inbound customer message through OpenAI GPT-4o-mini or Google Gemini 1.5 Flash.
Customer message → AI triage → Direct AI answer or human ticket → Agent notified in real time → Resolution
The AI is embedded in the core workflow — not bolted on. It affects every customer interaction by answering FAQs, summarising conversations, scoring urgency, suggesting agent replies, and escalating complex issues automatically.
Frontend client: supportflow-client · Live app: supportflowai.vercel.app
https://supportflow-backend-server.up.railway.app
Returns { "status": "ok" } when the server is running.
- Retrieves relevant knowledge base entries per business using keyword and tag matching
- Calls OpenAI GPT-4o-mini or Gemini 1.5 Flash with dynamic system prompt + KB context
- Returns structured JSON:
answer,confidence(0.0–1.0),sentiment,priority,handoffNeeded,summary - If
confidence >= 0.75andhandoffNeeded = false→ AI answer delivered directly to customer - If
confidence < 0.75orhandoffNeeded = true→ ticket auto-created with AI summary + suggested reply - Full fallback handler: LLM timeout or bad JSON → ticket created silently, customer sees a polite message, no 500 error ever surfaced
- Every data record carries a
businessIdfield requireTenantmiddleware attachesbusinessIdfrom JWT to every request- Every database query filters by
businessId— zero cross-tenant data access possible - Returns
403 ForbiddenifbusinessIdis missing or mismatched
- Auto-created on AI handoff with priority, sentiment, summary, and
aiSuggestedReplypre-filled - Full lifecycle:
open → in_progress → resolved → closed - Full audit trail:
updatedBy(userId) +updatedAton every status change - On-demand AI reply regeneration via
POST /api/tickets/:id/suggest-reply
- Agents join their business room on connect (
join_business) ticket_created— emitted immediately after AI handoff creates a ticketticket_updated— broadcast on every status changenew_message— emitted when a customer sends a messageagent_reply— emitted when an agent sends a reply
- JWT (HS256) with
userId,role, andbusinessIdembedded in the payload bcryptpassword hashing with cost factor ≥ 10- Four roles:
superadmin,admin,agent,customer - Role guards applied per route — no extra DB call needed
passwordHashfield never returned in any API response
- Admin CRUD for FAQ and policy entries with title, content, tags, and
isActiveflag - Only
isActive: trueentries are injected into the AI context - In-memory cache per
businessId(5-minute TTL) avoids repeated DB reads on every message - Cache invalidated automatically on any admin create, update, or delete
- Per-business ticket counts by status
- AI-handled conversation rate
- Date-range filtering support
- Scoped strictly to the authenticated business — no platform-wide data leakage
| Layer | Technology | Version |
|---|---|---|
| Runtime | Node.js | 20 LTS |
| Framework | Express | 4.x |
| Real-time | Socket.io | 4.x |
| Database | MongoDB Atlas | 7.x |
| ODM | Mongoose | 8.x |
| Authentication | jsonwebtoken | 9.x |
| Password hashing | bcryptjs | 2.x |
| Environment | dotenv | 16.x |
| Primary AI | OpenAI API (GPT-4o-mini) | Latest |
| Alternate AI | Google Gemini 1.5 Flash | Latest |
| Deployment | Railway (free tier) | — |
src/
├── config/
│ ├── db.js # MongoDB Atlas connection
│ └── env.js # Environment variable validation
│
├── models/
│ ├── User.model.js # Admins and agents (role, businessId, isActive)
│ ├── Business.model.js # Tenant workspace (ownerId, plan, settings)
│ ├── Customer.model.js # External support user (scoped to businessId)
│ ├── Conversation.model.js # Chat history with embedded messages[]
│ ├── Ticket.model.js # Human-trackable support issue
│ └── KnowledgeBase.model.js # FAQ/policy source material for AI
│
├── controllers/
│ ├── auth.controller.js
│ ├── business.controller.js
│ ├── agent.controller.js
│ ├── chat.controller.js
│ ├── ticket.controller.js
│ ├── ai.controller.js
│ ├── knowledge.controller.js
│ └── analytics.controller.js
│
├── routes/
│ ├── auth.routes.js
│ ├── business.routes.js
│ ├── agent.routes.js
│ ├── chat.routes.js
│ ├── ticket.routes.js
│ ├── ai.routes.js
│ ├── knowledge.routes.js
│ └── analytics.routes.js
│
├── services/
│ ├── ai.service.js # AI orchestration: KB fetch → prompt → LLM → parse → route
│ ├── ticket.service.js # Ticket lifecycle, assignment, suggested reply
│ ├── chat.service.js # Conversation management, message storage
│ └── knowledge.service.js # KB retrieval with in-memory cache
│
├── middlewares/
│ ├── auth.middleware.js # verifyToken — JWT verification, req.user
│ ├── tenant.middleware.js # requireTenant — businessId attachment + guard
│ ├── role.middleware.js # requireRole('admin' | 'agent') — RBAC
│ └── error.middleware.js # Global error handler
│
├── utils/
│ ├── ApiResponse.js # Standardised { success, data } response factory
│ ├── ApiError.js # Custom error class with HTTP status
│ ├── asyncHandler.js # Wraps async controllers — no try/catch repetition
│ └── generateToken.js # JWT issuance helper
│
├── socket/
│ └── index.js # Socket.io server setup and event handlers
│
├── app.js # Express app setup, CORS, middleware mounting
└── server.js # HTTP server + Socket.io bootstrap
Development: http://localhost:5000/api
Production: https://supportflow-backend-server.up.railway.app
All endpoints return a consistent envelope:
// Success
{ "success": true, "data": { ... } }
// Error
{ "success": false, "error": "message string" }All protected endpoints require:
Authorization: Bearer <jwt_token>
| Method | Endpoint | Body | Description |
|---|---|---|---|
POST |
/auth/register |
name, email, password |
Register a business admin account |
POST |
/auth/login |
email, password |
Authenticate and receive JWT |
GET |
/auth/me |
— | Load current authenticated user profile |
| Method | Endpoint | Body | Description |
|---|---|---|---|
POST |
/business |
name, industry? |
Create a business workspace |
GET |
/business/me |
— | Load current business workspace details |
| Method | Endpoint | Body | Description |
|---|---|---|---|
POST |
/agents |
name, email, password |
Create an agent account |
GET |
/agents |
— | List all agents for the business |
PATCH |
/agents/:id |
isActive: boolean |
Activate or deactivate an agent |
| Method | Endpoint | Auth | Body | Description |
|---|---|---|---|---|
POST |
/chat/message |
Optional | message, businessId, customerInfo? |
Process customer message through AI |
GET |
/chat/conversations |
Agent JWT | ?page=1&limit=20 |
List conversations for the business |
GET |
/chat/conversations/:id |
Agent JWT | — | Load full conversation with messages |
Response — AI answered directly:
{ "data": { "type": "ai_answer", "answer": "Here is the answer..." } }Response — Handed off to agent:
{ "data": { "type": "handoff", "ticketId": "...", "message": "An agent will reply shortly." } }| Method | Endpoint | Body / Query | Description |
|---|---|---|---|
GET |
/tickets |
?status=open&priority=high&page=1 |
List tickets with filters |
POST |
/tickets |
title, summary?, customerId, priority? |
Create ticket manually |
GET |
/tickets/:id |
— | Get single ticket (populated) |
PATCH |
/tickets/:id/status |
status: in_progress|resolved|closed |
Update ticket status |
PATCH |
/tickets/:id/assign |
agentId |
Assign ticket to an agent |
POST |
/tickets/:id/suggest-reply |
— | Generate fresh AI-suggested reply |
| Method | Endpoint | Body | Description |
|---|---|---|---|
POST |
/knowledge |
title, content, tags[] |
Create a knowledge base entry |
GET |
/knowledge |
?isActive=true |
List knowledge base entries |
PATCH |
/knowledge/:id |
title?, content?, tags?, isActive? |
Update an entry |
DELETE |
/knowledge/:id |
— | Delete an entry |
| Method | Endpoint | Query | Description |
|---|---|---|---|
GET |
/analytics |
?from=2026-01-01&to=2026-04-30 |
Ticket counts and AI-handled rate for the business |
| Event | Direction | Payload | When it fires |
|---|---|---|---|
join_business |
Client → Server | { businessId } |
Agent connects to their business room |
new_message |
Server → Client | { conversationId, message } |
Customer sends a message |
ticket_created |
Server → Client | { ticket } |
AI handoff creates a new ticket |
ticket_updated |
Server → Client | { ticketId, status, updatedBy } |
Any agent updates a ticket status |
agent_reply |
Server → Client | { conversationId, message } |
Agent sends a reply |
| Collection | Index | Purpose |
|---|---|---|
users |
{ email: 1 } unique |
Fast login lookup |
tickets |
{ businessId: 1, status: 1, priority: 1 } |
Agent dashboard filtered queries |
conversations |
{ businessId: 1, customerId: 1, updatedAt: -1 } |
Sorted conversation history |
knowledgebase |
{ businessId: 1, isActive: 1, tags: 1 } |
Fast AI context retrieval |
customers |
{ businessId: 1, email: 1 } |
Returning customer identification |
- Node.js 20 LTS
- npm 9+
- MongoDB Atlas account (free tier is sufficient)
- OpenAI API key or Google Gemini API key
git clone https://github.com/FrontifybyHB/supportflow-server.git
cd supportflow-server
npm installCreate a .env file in the root directory (use .env.example as a template):
# Server
PORT=5000
# Database
MONGODB_URI=mongodb+srv://<user>:<password>@cluster0.mongodb.net/supportflow
# Authentication
JWT_SECRET=your_minimum_32_character_random_secret
JWT_EXPIRES_IN=7d
# AI Provider — set to 'openai' or 'gemini'
AI_PROVIDER=openai
OPENAI_API_KEY=sk-...
GEMINI_API_KEY=AIza... # Only needed if AI_PROVIDER=gemini
# CORS
CLIENT_URL=http://localhost:5173 # Your frontend URLNever commit
.envto version control. The.env.examplefile documents all required keys without values.
# Development (with nodemon hot-reload)
npm run dev
# Production
npm startServer starts on http://localhost:5000. Health check: GET /api/health
No code changes needed. Set one environment variable:
AI_PROVIDER=openai # Uses GPT-4o-mini
AI_PROVIDER=gemini # Uses Gemini 1.5 FlashEach provider is a separate file exporting the same complete(systemPrompt, userMessage) interface. The AI Orchestration Service loads the correct provider at runtime.
Every protected request passes through this chain in order:
Request
↓
verifyToken — JWT verification → attaches req.user
↓
requireTenant — businessId extraction → attaches req.businessId
↓
requireRole(...) — role check (admin / agent)
↓
Controller — request validation + service call + response
POST /api/chat/message
↓
Auth + Tenant middleware
↓
ChatController → ChatService.handleMessage()
↓
KnowledgeService.getContext(businessId, message) ← top 5 active KB entries
↓
AIOrchestrationService.processMessage()
↓
Build system prompt + inject KB context → call LLM API
↓
Parse JSON response: { answer, confidence, sentiment, priority, handoffNeeded, summary }
↓
confidence >= 0.75 AND handoffNeeded = false?
├── YES → return AI answer to customer | mark conversation aiHandled = true
└── NO → TicketService.createFromHandoff() → emit ticket_created via Socket.io
↓
LLM error / timeout?
└── catch block → createFromHandoff() with fallback summary | customer sees polite message
- Go to render.com → New Web Service → connect your GitHub repo
- Set Root Directory to
/(or your backend folder) - Build Command:
npm install - Start Command:
node src/server.js - Add all environment variables from the
.envsection above - Enable Auto-Deploy on the
mainbranch - Copy the Render URL and set it as
VITE_API_BASE_URLin your frontend
Free tier cold starts: Add a cron job or uptime monitor to ping
GET /api/healthevery 10 minutes to keep the instance warm during your demo.
| Check | How to Verify |
|---|---|
| Server is live | GET /api/health returns { "status": "ok" } |
| Database connected | POST /api/auth/register returns a JWT |
| Auth flow | Login → GET /api/auth/me returns correct user with businessId |
| KB creation | POST /api/knowledge (admin JWT) → entry appears in GET /api/knowledge |
| AI answers | POST /api/chat/message with a FAQ question → type: "ai_answer" |
| Handoff works | POST /api/chat/message with unknown question → type: "handoff" + ticket in DB |
| Real-time | Open agent dashboard → send chat in another tab → ticket appears without refresh |
| Tenant isolation | Business A JWT calling Business B's /api/tickets → empty array or 403 |
| Target | Goal | How it's achieved |
|---|---|---|
| AI response time | < 5 seconds end-to-end | GPT-4o-mini with max_tokens: 500, temperature: 0.3 |
| Ticket notification | < 2 seconds chat → agent dashboard | Socket.io emit inside TicketService immediately after DB insert |
| Tenant isolation | 100% — zero cross-tenant data | businessId filter on every query enforced by tenant middleware |
| Query performance | < 100ms on indexed fields | Compound indexes on businessId + status + priority etc. |
| Password security | Zero plaintext passwords | bcrypt.hash() on register, bcrypt.compare() on login, select: false on passwordHash field |
| AI fallback | 100% coverage | Every LLM call wrapped in try/catch → ticket always created on failure |
SupportFlow AI was built for the Sheryians Coding School Hackathon 2026 under Problem Statement #1: AI Customer Support Platform for Multi-Tenant Businesses.
The project demonstrates: multi-tenant architecture with businessId isolation, four-role RBAC with JWT, practical Generative AI embedded in the support workflow, real-time agent notifications via Socket.io, and a complete end-to-end support lifecycle — all within a 48-hour build window.
- 🖥️ Frontend client — supportflow-client
- 🌐 Live app — supportflowai.vercel.app
- 📄 SRS Document — Software Requirements Specification v1.0
- 🗂️ System Design — HLD + LLD + 48-Hour Build Plan v2.0
Built with ❤️ for Sheryians Coding School Hackathon 2026