Creating a USSD application with NodeJS and Redis [Part 1]
Moses Odhiambo
14 Jun 2021
•
4 min read
Building USSD applications in NodeJS may seem straightforward and even a library away from getting everything up and running. After building several USSD applications with different libraries and not getting what I wanted, I came up with a custom solution. Hopefully, this can be helpful to you.
Several considerations need to be made when building an effective USSD solution. They include:
- Menu Management — Manage how each screen is presented to the user and ensure dynamic menu builds.
- Session Management — Manage how information is passed across different menus as the user traverses the application.
- Dynamic Configuration — USSD applications should have multi-language support, support for different Mobile Network Operators(MNOs) requirements and should be highly customizable.
Lets build the solution
In this article we will build a USSD app with NodeJS and Redis. Redis is an open source in-memory data structure store, it will be used for session management.
Project Requirements:
Install NodeJS
Install typescript globally **npm install -g typescript** and **npm install -g ts-node
**[Install a Redis server](https://redis.io/download)
Let’s set up the project:
mkdir ussd-project
cd ussd-project
npm init -y
tsc --init
Update tsconfig.json file with the following
{
"compilerOptions": {
"module": "commonjs",
"resolveJsonModule": true,
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": false,
"sourceMap": false,
"outDir": "build"
},
"exclude": [
"node_modules",
"**/*.spec.ts",
"**/*.test.ts",
"./src/**/__tests__/*.ts"
],
"include": [
"src/**/*.ts"]
}
Install the following packages on the project:
npm i express nodemon redis
npm i [@types/express](http://twitter.com/types/express) [@types/node](http://twitter.com/types/node) [@types/redis](http://twitter.com/types/redis) -D
Add nodemon.json file on the project base
{
"restartable": "rs",
"ignore": [".git", "node_modules/", "build/", "coverage/"],
"watch": ["src/"],
"env": {
"NODE_ENV": "development"
},
"ext": "js,json,ts"
}
Update scripts on your package.json folder as shown:
"scripts": {
"start": "ts-node src/app.ts",
"live": "nodemon --config nodemon.json src/app.ts"
}
Create this folder structure for the project:
ussd-project
└───scr
│ └───menu-builder
│ │ └───configs
│ │ └───core
│ │ └───lang
│ │ └───menus
│ │ └───states
│ │ │ └───controllers
│ │ └───typings
│ │ └───index.ts
│ └───app.ts
└───node\_modules
└───nodemon.json
└───package.json
└───package-lock.json
└───tsconfig.json
Menu builder contains the following folders:
- configs folder — Sets the base configurations for the project
- core folder — Manage project input and session handling.
- lang folder — Manage different menu languages.
- menus folder — Manage creation of menus.
- states folder — Manage menu states.
- typings folder — Manage menu types (TypeScript)
Create the http server
Lets create a http server with express JS. Add a route to accept USSD requests and call the menu builder function. The menu builder function accepts the request body and a redis client as parameters.
import express from 'express';
import redis from 'redis';
import ussdMenuBuilder from './menu-builder'
const app: express.Application = express();
app.disable('etag').disable('x-powered-by');
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// CONNECT TO REDIS SERVER
const redisClient = redis.createClient({
host: "localhost",
// password: "pass123",
port: 6379
})
redisClient.on('connect', () => {
console.log('Connected to redis server');
})
redisClient.on('error', (err) => {
console.log('Redis connection error', err);
})
// USSD ROUTE
app.post('/ussd', async (req, res) => {
let menu_res;
try{
// RUN THE MENU BUILDER
// PASS REQ BODY AND REDIS CLIENT
menu_res = await ussdMenuBuilder(req.body, redisClient);
} catch(e){
console.log("MENU ERROR");
return res.send(e)
}
res.send(menu_res);
});
const port = 4000;
app.listen(port, () => console.log(`Server listening at port ${port}`));
Create the menu builder configuration file
Project has a set of base configurations:
- session_prefix — This unique identifier that is used to create sessions
- default_lang — Default language used in the project
- session_time — Lifetime of the session menu in seconds
- start_state —The 1st state that runs
- sequential_requests — Set whether the project should allow user to run sequential queries eg. *144*_1*3*_6#. It’s important since some MNOs do not allow sequential queries.
export default {
session_prefix: "keapp:",
default_lang: "en", // ENSURE NAME MATCHES LANGUAGE CONFIG NAME
session_time: 180,
start_state: 'start_state',
sequential_requests: false,
}
src/menu-builder/configs/index.ts
Create the language config
Create an English language file and add the following generic properties:
export default {
generic: {
fatal_error: "An error occurred executing your request, please try again later.",
deny_sequential_requests: "Sequential requests eg: *1*2*3 are not allowed",
input_violation: "Input pattern is not supported.",
bad_input: "Invalid input\n",
}
}
src/menu-builder/lang/en.ts
Create a file to manage all language options:
import en from './en'
export default{
en: en
}
src/menu-builder/lang/index.ts
Create the menu builder types
Add TypeScript interface for the incoming request body.
export interface RequestBody{
phoneNumber: string,
serviceCode: string,
text: string,
sessionId: string
}
src/menu-builder/typings/global.ts
Create the menu builder main file
With all base configurations set let us create the menu builder file. This is the entry point of our application.
import {RedisClient} from 'redis'
import {RequestBody} from './typings/global'
import languages from './lang'
import configs from './configs'
export default function(args: RequestBody, redis: RedisClient){
return new Promise(async(resolve, reject) => {
try{
// BUILD INPUT VARIABLE
let buildInput = {
current_input: args.text,
full_input: args.text,
masked_input: args.text,
active_state: configs.start_state,
sid: configs.session_prefix+args.sessionId,
language: configs.default_lang,
phone: args.phoneNumber,
hash: ""
}
resolve("CON Welcome to the USSD Redis App")
return
}catch(e) {
// SOMETHING WENT REALLY WRONG
reject("END " + languages[configs.default_lang].generic.fatal_error )
return
}
});
}
src/menu-builder/index.ts
Run the Project
Run npm run start. Lets send a POST request to our server “http://localhost:4000/ussd”. You will get a response from the menu builder as shown below:
Wrapping Up
That wraps up the first session of building the USSD application. Stay tuned for the rest of the articles.
Have fun and boost the article if you liked it 👍
Moses Odhiambo
A Software Engineer based in Nairobi, Kenya. I am on a mission to discover and build technology that can shape the future of society.
See other articles by Moses