Build a multi-tenant SaaS application with:
- Dynamic Database Connections: Each tenant has its own isolated database.
- Event-Driven Architecture: Use RabbitMQ to handle asynchronous events like order processing.
- Real-Time Notifications: Use WebSockets to notify clients about updates in real time.
- MongoDB: Running locally or in Docker.
- RabbitMQ: Running locally or in Docker.
- Dependencies: Install the required packages:
npm install mongoose amqplib ws npm install --save-dev @types/ws
Each tenant has its own database. Use middleware to dynamically connect to the appropriate database based on the x-tenant-id header.
// filepath: /src/middleware/tenantMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import mongoose from 'mongoose';
import config from '../config/app';
const connections: { [key: string]: mongoose.Connection } = {};
export const tenantMiddleware = async (req: Request, res: Response, next: NextFunction) => {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
return res.status(400).json({ error: 'Tenant ID is required' });
}
if (!connections[tenantId]) {
try {
const dbUri = `mongodb://${config.mongodb.username}:${config.mongodb.password}@${config.mongodb.host}:${config.mongodb.port}/${tenantId}?authSource=${config.mongodb.authSource}`;
const connection = await mongoose.createConnection(dbUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
connections[tenantId] = connection;
} catch (error) {
return res.status(500).json({ error: 'Failed to connect to tenant database' });
}
}
req['dbConnection'] = connections[tenantId];
next();
};Use RabbitMQ to handle asynchronous events like order processing.
// filepath: /src/events/publisher.ts
import amqp from 'amqplib';
let channel: amqp.Channel;
export const connectToBroker = async () => {
const connection = await amqp.connect('amqp://localhost');
channel = await connection.createChannel();
await channel.assertQueue('orderQueue');
};
export const publishEvent = async (event: string, data: any) => {
if (!channel) {
throw new Error('Message broker not connected');
}
channel.sendToQueue('orderQueue', Buffer.from(JSON.stringify({ event, data })));
};// filepath: /src/events/consumer.ts
import amqp from 'amqplib';
export const consumeEvents = async () => {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
await channel.assertQueue('orderQueue');
channel.consume('orderQueue', (message) => {
if (message) {
const event = JSON.parse(message.content.toString());
console.log('Received event:', event);
channel.ack(message);
}
});
};Use WebSockets to notify clients about updates in real time.
// filepath: /src/websockets/notificationServer.ts
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
console.log('Received:', message.toString());
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
export const sendNotification = (data: any) => {
wss.clients.forEach((client) => {
if (client.readyState === 1) { // WebSocket.OPEN
client.send(JSON.stringify(data));
}
});
};Create an endpoint to fetch analytics data for admins.
// filepath: /src/controllers/analyticsController.ts
import { Request, Response } from 'express';
export const getAnalytics = async (req: Request, res: Response) => {
try {
// Simulate fetching analytics data
const analytics = {
totalOrders: 100,
totalRevenue: 5000,
activeUsers: 50,
};
res.status(200).json({
status: 'success',
data: analytics,
});
} catch (error) {
res.status(500).json({
status: 'error',
message: 'Failed to fetch analytics',
});
}
};- Start MongoDB and RabbitMQ using Docker:
docker-compose up -d
- Start the application:
npm run dev
- Start the WebSocket server:
ts-node src/websockets/notificationServer.ts
- Start the RabbitMQ event consumer:
ts-node src/events/consumer.ts
- Use Postman or curl to send requests with the
x-tenant-idheader.
- Publish an event using the
publishEventfunction and verify it is consumed by the consumer.
- Connect a WebSocket client to
ws://localhost:8080and send/receive messages.
- Use
zodfor request validation. - Use
mongoosefor database operations. - Use RabbitMQ for event-driven communication.
- Use WebSockets for real-time notifications.