Building a Rule Engine for Bottom Sheets on App Homepage Using Nest.js and MongoDB
In today’s fast-paced digital world, personalization and dynamic content delivery are key to engaging users effectively. One way to achieve this is by using a rule engine to control the display of bottom sheets on your app’s homepage. A bottom sheet is a UI component that slides up from the bottom of the screen, often used to display additional information, actions, or prompts to users.
In this blog, we’ll explore how to build a rule engine for bottom sheets that executes rules based on business priorities, PNLs (Profit and Loss), and customer attention/needs. We’ll also design an end-to-end architecture, discuss the data structure, and choose the right database for the job. The tech stack we’ll use is Nest.js, MongoDB, and Mongoose as the ORM.
Why a Rule Engine for Bottom Sheets?
A rule engine allows you to define and execute business rules dynamically. For bottom sheets, this means:
- Prioritization: Display the most important bottom sheet based on business priorities.
- Personalization: Show relevant bottom sheets based on user behavior, preferences, or needs.
- Dynamic Updates: Change the rules without redeploying the app.
- Analytics-Driven Decisions: Use data like PNLs or user engagement metrics to decide which bottom sheet to display.
End-to-End Architecture
Here’s a high-level architecture for the rule engine:
- Frontend (App): Displays the bottom sheet based on the rule engine’s response.
- Backend (Nest.js):
- Rule Engine Service: Executes rules and decides which bottom sheet to display.
- API Endpoints: Expose endpoints for the app to fetch the bottom sheet and for admins to manage rules.
- Database (MongoDB):
- Stores rules, bottom sheet configurations, user data, and analytics.
Architecture Diagram
+-------------------+ +-------------------+ +-------------------+
| Frontend | | Backend | | Database |
| (App Homepage) | <---> | (Nest.js) | <---> | (MongoDB) |
+-------------------+ +-------------------+ +-------------------+
| | |
| | |
v v v
+-------------------+ +-------------------+ +-------------------+
| Fetch Bottom | | Rule Engine | | Rules Collection |
| Sheet API | | Service | | (MongoDB) |
+-------------------+ +-------------------+ +-------------------+
| | |
| | |
v v v
+-------------------+ +-------------------+ +-------------------+
| Display Bottom | | Execute Rules | | Bottom Sheets |
| Sheet | | (Priority, PNL, | | Collection |
| | | User Behavior) | | (MongoDB) |
+-------------------+ +-------------------+ +-------------------+
Data Structure in MongoDB
We’ll use MongoDB as our database because of its flexibility and scalability. Here’s how we’ll structure the data:
1. Rules Collection
Stores the rules for determining which bottom sheet to display.
{
_id: ObjectId,
ruleName: String, // e.g., "HighPriorityPromo"
priority: Number, // Higher number = higher priority
conditions: [
{
field: String, // e.g., "userSegment"
operator: String, // e.g., "equals", "greaterThan"
value: Mixed // e.g., "premium", 100
}
],
bottomSheetId: ObjectId, // Reference to the bottom sheet
isActive: Boolean,
createdAt: Date,
updatedAt: Date
}
2. Bottom Sheets Collection
Stores the configurations for each bottom sheet.
{
_id: ObjectId,
title: String,
content: String,
cta: {
text: String,
action: String // e.g., "navigate", "dismiss"
},
metadata: {
createdBy: String,
createdAt: Date,
updatedAt: Date
}
}
3. User Data Collection
Stores user-specific data for personalization
{
_id: ObjectId,
userId: String,
segment: String, // e.g., "premium", "newUser"
behavior: {
lastLogin: Date,
engagementScore: Number
},
preferences: {
language: String,
theme: String
}
}
4. Analytics Collection
Stores data for PNLs and user engagement.
{
_id: ObjectId,
bottomSheetId: ObjectId,
impressions: Number,
clicks: Number,
conversions: Number,
revenueGenerated: Number,
date: Date
}
Rule Engine Logic
The rule engine will execute the following steps:
- Fetch User Data: Retrieve the user’s segment, behavior, and preferences.
- Fetch Active Rules: Retrieve all active rules from the database.
- Evaluate Conditions: For each rule, evaluate its conditions against the user data.
- Prioritize Rules: Sort the matching rules by priority.
- Select Bottom Sheet: Return the bottom sheet with the highest priority.
Implementation in Nest.js
1. Rule Engine Service
@Injectable()
export class RuleEngineService {
constructor(
@InjectModel('Rule') private readonly ruleModel: Model<Rule>,
@InjectModel('BottomSheet') private readonly bottomSheetModel: Model<BottomSheet>,
@InjectModel('User') private readonly userModel: Model<User>
) {}
async getBottomSheetForUser(userId: string): Promise<BottomSheet | null> {
// Fetch user data
const user = await this.userModel.findOne({ userId }).exec();
if (!user) return null; // Fetch active rules
const rules = await this.ruleModel.find({ isActive: true }).exec(); // Evaluate rules
const matchingRules = rules.filter(rule => this.evaluateConditions(rule.conditions, user)); // Sort by priority
matchingRules.sort((a, b) => b.priority - a.priority); // Return the highest priority bottom sheet
if (matchingRules.length > 0) {
const bottomSheet = await this.bottomSheetModel.findById(matchingRules[0].bottomSheetId).exec();
return bottomSheet;
} return null;
} private evaluateConditions(conditions: any[], user: User): boolean {
return conditions.every(condition => {
switch (condition.operator) {
case 'equals':
return user[condition.field] === condition.value;
case 'greaterThan':
return user[condition.field] > condition.value;
// Add more operators as needed
default:
return false;
}
});
}
}
2. API Endpoint
@Controller('bottom-sheets')
export class BottomSheetController {
constructor(private readonly ruleEngineService: RuleEngineService) {}
@Get('for-user')
async getBottomSheet(@Query('userId') userId: string): Promise<BottomSheet | null> {
return this.ruleEngineService.getBottomSheetForUser(userId);
}
}
Why MongoDB?
- Flexibility: MongoDB’s schema-less design allows us to easily modify rules and bottom sheet configurations.
- Scalability: It can handle large volumes of user data and analytics.
- Performance: MongoDB’s indexing and aggregation framework make it ideal for querying and evaluating rules.
Conclusion
By building a rule engine for bottom sheets, you can dynamically control what your users see based on business priorities, PNLs, and user behavior. Using Nest.js and MongoDB, we’ve created a scalable and flexible solution that can be easily extended to support more complex rules and conditions.
This approach not only improves user engagement but also empowers business teams to make data-driven decisions without requiring constant developer intervention.
Let me know if you need further assistance or additional features for your rule engine! 🚀