Runtime - Node.js
If you are using Lambda Genie in a pure Node.JS project (for example, a NextJS App, or a Express Server, or any other application that runs on Node.js), you would still setup all your rules, feature flags, and configuration values in the Lambda Genie Console. However, you would use a slightly different package to access the runtime. The Lambda Genie NodeJS runtime is a Node.js module that you can add to your project. It provides a simple API to access the values. The runtime is available as an NPM package, and can be installed using the following command:
npm install @euxdt/node-rules-engine
Sample Project
git clone https://github.com/flexicious/node-rules-engine-nextjs
This is a Next.js project bootstrapped with create-next-app
.
This project has a pre-configured Lambda Genie config, and is ready to use. You can use this project to test out the features of Lambda Genie.
npm install
npm run dev
# or
yarn
yarn dev
Open http://localhost:3000 with your browser to see the result.
You can start by registering a few accounts,
- username: test@test.com, password: test@test.com, email: test@test.com, birthdate: 01/01/2000, gender: Male
- username: test1@test.com, password: test1@test.com, email: test1@test.com, birthdate: 01/01/2000, gender: Female
- username: test3@test.com, password: test3@test.com, email: test3@test.com, birthdate: 01/01/2010, gender: Male
- username: test4@test.com, password: test4@test.com, email: test4@test.com, birthdate: 01/01/2010, gender: Female
- username: admin@abc.com, password: admin@abc.com, email: admin@abc.com, birthdate: 01/01/2000, gender: Female
Once you have registered, you can login with each of the credentials, and you should see personalized recommendations in the second slot of the top row of the page. You can also see the next generation features enabled for admin.
All of this configuration is sourced from the config.json which we built using Lambda Genie using the instructions here
Explanation of the Sample Project
Similar to the Lambda Genie runtime for AWS Lambda, the NodeJS runtime provides a simple API to access the values. The runtime is available as an NPM package, and can be installed using the following command:
npm install @euxdt/node-rules-engine
Once you have this installed, you can load the config file you generated using the Lambda Genie Console. The config file is a JSON file that contains all the configuration values, feature flags, and business rules. You can load the config file using the following code:
import { ConfigJson } from "@euxdt/node-rules-engine";
import { getNodeJsConfigApi, ConfigApi } from "@euxdt/node-rules-engine";
import configJson from "../config.json";
import * as fs from "fs";
//Since this is a demo, we are going to store the config in a file.
//In a real world scenario, you would store the config in a database or a key value store.
//You would poll the database/key value store for changes and update the config when it changes.
//for the purpose of this demo, you can overwrite the config.json file and the config will be reloaded.
//Similar to how we check the last modified date of the config file,
//you would check the last modified date of the config in the database/key value store
//and only reload the config if it has changed.
const configFileLocation = process.env.CONFIG_FILE_LOCATION || "/tmp/config.json";
export const loadConfigApi = async (lambdaName:string):Promise<ConfigApi> => {
const configApi = await getNodeJsConfigApi(
{
lambdaName,
loadConfig: async (lastRefreshed?:Date, existingConfig?:ConfigJson) => {
const configExists = fs.existsSync(configFileLocation);
if(configExists && lastRefreshed && existingConfig){
const fileModifiedDate = fs.statSync(configFileLocation).mtime;
if(lastRefreshed && fileModifiedDate && lastRefreshed.getTime() >= fileModifiedDate.getTime()){
console.log("Config is up to date");
return existingConfig;
}
}
console.log("Loading Config");
if(!configExists){
console.log("Config file does not exist, creating");
fs.writeFileSync(configFileLocation, JSON.stringify(configJson));
}
console.log("Config Json", configJson);
const result = (configJson);
console.log("Config Json", { configJson });
return result as unknown as ConfigJson;
},
log: (level, message, extra) => {
console.log(level, message, extra);
}
}
);
return configApi;
};
There is a word wrap button in the top right corner of the code blocks. If you are having trouble reading the code, click the word wrap button to make the code easier to read.
Using the Config API
Once you have the config API, you can use it to access the configuration values, feature flags, and business rules. Let's look at an example of how to use the config API to access the configuration values.
const handler = async (
req: NextApiRequest,
res: NextApiResponse<ProductApiResponse|{message:string}>
) => {
//Ensure the user is logged in
const session = await getServerSession(req, res, authOptions)
const db = await Database.open("database.db");
const user = await db.get(`SELECT [id], [username], [email], [birthdate], [gender]
FROM [users]
WHERE [email] = ?
LIMIT 1`, [session?.user?.email])
if(!user) {
return res.status(401).send({message: "Unauthorized No user"});
}
//This would come from environment variables, but for the sake of the demo, we'll hard code it
const environment = "prd";
//Load the config
const configApi = await loadConfigApi("GET_PRODUCTS");
//Get the slot names from the dynamic config we defined in the lambda genie console
const slotNames = JSON.parse(await configApi.getConfigValue(CONFIG.LAMBDA_CONFIGS.GET_PRODUCTS.SLOT_NAMES, environment)) as SlotNames;
//Default the slot names to some values if they aren't defined in the dynamic config
const slot1 = [slotNames.slot1] || ["Home & Kitchen"];
const slot2 = [slotNames.slot2] || ["Clothing, Shoes & Jewelry"];
const slot3 = [slotNames.slot3] || ["Learning & Education"];
const slot4 = [slotNames.slot4] || ["Hobbies"];
const maxProducts = slotNames.maxProducts || 4;
//Get the featured products from the dynamic config we defined in the lambda genie console
const featuredProducts = JSON.parse(await configApi.getConfigValue(CONFIG.LAMBDA_CONFIGS.GET_PRODUCTS.FEATURED_PRODUCTS, environment)) as Product[];
//Get the slot 2 rules from the rule set we defined in the lambda genie console, which allow
//us to dynamically change the slot 2 category based on the user's age and gender
const slot2Rules = configApi.configJson.ruleSets.find((ruleSet) => ruleSet.name === CONFIG.RULE_SETS.HOME_PAGE_PERSONALIZATION);
const userInfo = {
...user,
name: user.username,
age: user.birthdate ? Math.floor((new Date().getFullYear() - new Date(user.birthdate).getFullYear())) : 25
};
if (slot2Rules) {
//Execute the rules and get the result
const slot2RuleResult = executeRule(slot2Rules,
configApi.configJson.predefinedLists, userInfo, environment);
if (slot2RuleResult && slot2RuleResult.result) {
//this will be one of Men, Women, Boys, or Girls [As defined in the rule set in the lambda genie console]
slot2.push(String(slot2RuleResult.result));
}
}
//Now, lets get the next gen feature flag from the rule set we defined in the lambda genie console
let nextGenFeature = false;
const featureFlagRules = configApi.configJson.ruleSets.find((ruleSet) => ruleSet.name === CONFIG.RULE_SETS.NEXT_GEN_FEATURE);
if (featureFlagRules) {
//Execute the rules and get the result
const featureFlagRuleResult = executeRule(featureFlagRules,
configApi.configJson.predefinedLists, userInfo, environment);
nextGenFeature = featureFlagRuleResult.result ? true : false;
console.log("featureFlagRuleResult", featureFlagRuleResult);
}
//For now, we are just loading the products from a local file, but this could be replaced with a call to a database or an API
const getProducts = (slot: string[]) => {
return PRODUCTS.filter((product) => {
return slot.every((category) => {
return product.categories.includes(category);
});
}).slice(0, maxProducts);
};
//Get the products for each slot
const slot1Products = getProducts(slot1);
const slot2Products = getProducts(slot2);
const slot3Products = getProducts(slot3);
const slot4Products = getProducts(slot4);
//Return the full result
const response ={
slot1: slot1[0],
slot2: slot2[0],
slot3: slot3[0],
slot4: slot4[0],
slot1Products,
slot2Products,
slot3Products,
slot4Products,
featuredProducts,
nextGenFeature
}
res.status(200).json(response);
}
export default handler;
As you can see above, we used the config API to access the configuration values, feature flags, and business rules. We executed these rules against the user's information to determine which products to show them, and whether or not to show them the next gen feature.
Conclusion
In this article, we looked at how to use the Lambda Genie Config API to access configuration values, feature flags, and business rules. We also looked at how to use the Lambda Genie Config API in a Next.js application.