Skip to main content

Backend Overview

The backend is built with Express 5 and follows a modular architecture with clear separation of concerns.

Tech Stack

ComponentTechnology
FrameworkExpress 5
RuntimeNode.js 20+
AI OrchestrationLangChain (@langchain/core, @langchain/openai, @langchain/qdrant)
LLMAzure OpenAI (GPT-4o-mini) via LangChain
EmbeddingsAzure OpenAI (text-embedding-3-small) via LangChain
Vector StoreQdrant (via @langchain/qdrant)
DatabaseMongoDB (Mongoose ODM)
CacheRedis
QueueBullMQ
Real-TimeSocket.io
Exportxlsx (XLSX workbook generation)
MonitoringLangSmith (LLM tracing)

Directory Structure

backend/
├── config/ # Configuration modules
│ ├── database.js # MongoDB connection
│ ├── redis.js # Redis connection
│ ├── queue.js # BullMQ queues + schedulers
│ ├── llm.js # LLM provider
│ ├── embeddings.js # Embedding model
│ ├── vectorStore.js # Qdrant setup
│ ├── logger.js # Winston logger
│ └── guardrails.js # LLM guardrails config
├── controllers/ # Request handlers
│ ├── ragController.js
│ ├── authController.js
│ ├── conversationController.js
│ ├── workspaceController.js
│ ├── assessmentController.js
│ ├── questionnaireController.js
│ ├── exportController.js # RoI export
│ └── analyticsController.js
├── middleware/ # Express middleware
│ ├── auth.js
│ ├── loadWorkspace.js
│ ├── workspaceAuth.js
│ ├── rateLimiter.js
│ ├── validate.js
│ └── errorHandler.js
├── models/ # Mongoose schemas
│ ├── User.js
│ ├── Conversation.js
│ ├── Message.js
│ ├── Analytics.js
│ ├── Workspace.js # Vendor registry (DORA Article 28)
│ ├── WorkspaceMember.js
│ ├── Assessment.js
│ ├── VendorQuestionnaire.js
│ └── DeadLetterJob.js
├── routes/ # API routes
│ ├── ragRoutes.js
│ ├── authRoutes.js
│ ├── conversationRoutes.js
│ ├── workspaceRoutes.js # /workspaces + /roi-export
│ ├── assessmentRoutes.js
│ ├── questionnaireRoutes.js
│ └── analyticsRoutes.js
├── services/ # Business logic
│ ├── rag.js # Core RAG service
│ ├── alertMonitorService.js # Compliance monitoring alert checks
│ ├── roiExportService.js # EBA RoI XLSX workbook generator
│ ├── emailService.js # Resend email sending
│ ├── notificationService.js # Dual-channel notifications
│ ├── deadLetterQueue.js # Failed job tracking
│ ├── intent/ # Intent classification
│ ├── rag/ # RAG sub-modules
│ ├── memory/ # Conversation memory
│ ├── context/ # Context management
│ └── metrics/ # Observability
├── utils/ # Utilities
│ ├── core/ # Core utilities
│ ├── rag/ # RAG utilities
│ └── security/ # Security utilities
├── workers/ # Background workers
│ ├── index.js # Worker entry + graceful shutdown
│ ├── assessmentWorker.js # DORA gap analysis
│ ├── questionnaireWorker.js # LLM questionnaire scoring
│ └── monitoringWorker.js # 24h compliance alert scheduler
├── prompts/ # LLM prompts
│ └── ragPrompt.js
├── validators/ # Request validators
│ └── schemas.js
├── app.js # Express app setup
└── index.js # Entry point

Entry Points

index.js

Server initialization:

// index.js

import { connectDB } from './config/database.js';
import { createServer } from './app.js';
import { ragService } from './services/rag.js';
import { scheduleMonitoringJob } from './config/queue.js';
import './workers/monitoringWorker.js';

async function startServer() {
// 1. Connect to MongoDB
await connectDB();

// 2. Pre-warm RAG system
await ragService.init();

// 3. Schedule compliance monitoring job (24h)
await scheduleMonitoringJob().catch((err) =>
logger.error('Failed to schedule monitoring job (non-critical)', { error: err.message })
);

// 4. Start Express server
const app = await createServer();
const server = app.listen(process.env.PORT || 3007);

// Graceful shutdown
process.on('SIGTERM', () => gracefulShutdown(server));
}

startServer();

app.js

Express configuration:

// app.js

import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import cookieParser from 'cookie-parser';

export async function createServer() {
const app = express();

// Security middleware
app.use(helmet());
app.use(cors(corsOptions));

// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// Rate limiting
app.use(rateLimiter);

// Routes
app.use('/api/v1/rag', ragRoutes);
app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/conversations', conversationRoutes);
app.use('/api/v1/workspaces', workspaceRoutes); // includes /roi-export
app.use('/api/v1/assessments', assessmentRoutes);
app.use('/api/v1/questionnaires', questionnaireRoutes);
app.use('/api/v1/analytics', analyticsRoutes);

// Health check
app.get('/health', healthCheck);

// Error handling
app.use(errorHandler);

return app;
}

Request Flow

Request


┌─────────────────┐
│ Security │ ◀─── helmet, cors, rate limiting
│ Middleware │
└────────┬────────┘


┌─────────────────┐
│ Body Parsing │ ◀─── JSON, cookies
└────────┬────────┘


┌─────────────────┐
│ Authentication │ ◀─── JWT validation
└────────┬────────┘


┌─────────────────┐
│ Authorization │ ◀─── Workspace access, permissions
└────────┬────────┘


┌─────────────────┐
│ Validation │ ◀─── Request body validation
└────────┬────────┘


┌─────────────────┐
│ Controller │ ◀─── Request handling
└────────┬────────┘


┌─────────────────┐
│ Service │ ◀─── Business logic
└────────┬────────┘


┌─────────────────┐
│ Response │ ◀─── Formatted response
└─────────────────┘

Code Conventions

ES6 Modules

// Use ES6 imports
import express from 'express';
import { ragService } from './services/rag.js';

// Named exports preferred
export const myFunction = () => {};
export default class MyClass {}

Async/Await

// Always use async/await
export const getUser = async (id) => {
const user = await User.findById(id);
return user;
};

// Use catchAsync wrapper for controllers
export const createUser = catchAsync(async (req, res) => {
const user = await User.create(req.body);
sendSuccess(res, { user }, 201);
});

Error Handling

import { AppError } from '../utils/index.js';

// Throw AppError for known errors
if (!user) {
throw new AppError('User not found', 404);
}

// Error handler middleware catches all
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
status: 'error',
message: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
});
});

Response Formatting

import { sendSuccess, sendError } from '../utils/index.js';

// Success response
sendSuccess(res, { user: userData }, 200, 'User created');

// Error response (use AppError instead)
throw new AppError('Validation failed', 400);

Logging

import logger from '../config/logger.js';

logger.info('Operation completed', {
service: 'rag',
userId: req.user._id,
duration: Date.now() - startTime,
});

logger.error('Operation failed', {
service: 'rag',
error: error.message,
stack: error.stack,
});

Configuration

Key Environment Variables

# Server
PORT=3007
NODE_ENV=development

# MongoDB
MONGODB_URI=mongodb://localhost:27017/enterprise_rag

# Redis
REDIS_URL=redis://localhost:6378

# Qdrant
QDRANT_URL=http://localhost:6333
QDRANT_COLLECTION_NAME=documents

# Azure OpenAI
LLM_PROVIDER=azure_openai
EMBEDDING_PROVIDER=azure
AZURE_OPENAI_API_KEY=your-api-key
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_LLM_DEPLOYMENT=gpt-4o-mini
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small

# JWT
JWT_ACCESS_SECRET=your-secret
JWT_REFRESH_SECRET=your-refresh-secret
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d

# Compliance monitoring
MONITORING_INTERVAL_HOURS=24
INSTITUTION_NAME=Financial Entity

See Environment Variables for the full reference.

File Size Limit

Keep files under 500 lines. If exceeded, extract functionality into separate modules.

Testing

# Unit tests
npm run test:unit

# Integration tests
npm run test:integration

# All tests
npm test

Common Commands

# Development
npm run dev

# Production
npm start

# Qdrant utilities
npm run qdrant:list
npm run qdrant:info
npm run qdrant:collections

# Compliance knowledge base
npm run seed:compliance
npm run seed:compliance:reset