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!