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:
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:
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}`));
Project has a set of base configurations:
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 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
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
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 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:
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
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!