Build a Interactive Voice Response (IVR) application with Africa's Talking, HapiJS, Docker and Heroku

Introduction

Editorial Note: This is a JavaScript version of an earlier article. You can check it out if you're interested in getting started with Africa's Talking's Voice platform with Python. It was really fun writing this, enjoy!

Welcome back! This is a brand new series on creating amazing experiences with just phone calls. No fancy UI. No React. No Angular. Just a voice call. It may seem super simplistic but in reality, everyone makes calls. In light of this, is it possible to create apps that run on phone calls? Yes! That's what we'll be doing today.

We'll be using Flask, a python micro-framework, and deploying our app on Heroku. We don't have a database as of now but in a future series we'll be saving the data from user responses so stay tuned in for that.

What the app will do

We'll be building a simple guessing game that allows peeps to guess how old Africa's Talking is! This is the current user flow:

  1. A user will call our number and listen to the instructions
  2. They will dial in their response on the in-call dial pad
  3. The app will direct them on whether they have chosen the right answer or not

There are some challenges with the app, we aren't able to keep a user in a loop until they get the right answer but we'll cover this in the next series as we look at slightly mode advanced techniques.

Getting Started

To get your phone call set up, we are going to be using the Africa's Talking platform. Africa's Talking provides APIs that tap into telco infrastructure and makes it easier for developers across Africa to integrate services such as SMS, Voice, USSD, Payments and Airtime. There's also a brand new IoT platform that you could checkout too.

To get started, what you need is a programmable number. Africa's Talking provides these numbers at a cost however, for this tutorial, you can get a number for free. All you have to do is tell us what idea you would like to build on the Voice platform and we'll get you a number free of charge! Fill out this form to share your ideas.

How the Africa's Talking Voice API works

The platform provides a pretty simple approach to building our Interactive Voice Application through a REST API. Here's a simple diagram showing how it works:

Screen Shot 2019-11-06 at 1.14.59 PM.png

Project Setup

Let's get started building our app. We'll get back to Africa's Talking in a few moments. In the mean time, we'll be setting up our project with HapiJS. HapiJS is built off the idea that code should be more of configuration than anything else and it will shortly become apparent as we get our project set up. To get started, run the following commands on your terminal:

mkdir hapi-voice
cd hapi-voice

Once done, its time to initialize our NodeJS project with NPM:

npm init -y
npm install @hapi/hapi xmlbuilder

Here, we install two key packages:

  1. @hapi/hapi - This installs the core HapiJS library which comes with quite a few features. We'll use just the basic ones but you can learn more here.
  2. xmlbuilder - this library allows us to build out our XML without having to write the actual XML which can be a pain. (Check the Python tutorial to see how it works).

Next up, fire up your favorite code editor. I use VS Code for JavaScript. TBH, it's hard to find a more complete editor right now in the market.

Create a new file server.js that will be our main file. Add in the following code to set up a basic server:

'use strict'

// Import our modules

const Hapi = require('@hapi/hapi')
const xmlbuilder = require('xmlbuilder')

// Initialize our server
const init = async () => {
   const server = Hapi.Server({
       port: 3000,
       host: 'localhost'
   })

   // Basic route set up
   server.route({
       method: 'GET',
       path: '/',
       handler: async (req, h) => {
           return h.response('Everything is okay').code(200)
       }
    })
    await server.start()
    console.log(`Server is running on: ${server.info.uri}`)
}

// Start our server
init()

With this code, we've set up a server that when loaded up should show you Server running on port: http://localhost:3000 on your terminal.

Building your XML

We now need to define what our user should here once they dial in their answer to our question. How do we do this? This is where the xmlbuilder library comes in. All it really does is help us write less XML. You can read more about it here.

Lets get to building our XML responses. Right under our plugin imports, write the following code:

// When to say what
const entrySayActionXMl = xmlbuilder
  .create('Response')
  .ele('GetDigits', { timeout: '30', finishOnKey: '#', callbackURL: '<YOUR_APP_URL>/voice/say' })
  .ele(
    'Say',
    { voice: 'woman' },
    'Hi, welcome to the Africas Talking Freelance Developer Program demo app. We have a little question for you. How old is Africas Talking? Dial in your guess and press hash'
  )
  .end({ pretty: true })

const successSayActionXMl = xmlbuilder
  .create('Response')
  .ele(
    'Say',
    { voice: 'woman' },
    'Awesome! You got it right! Africas Talking has been around for almost a decade!'
  )
  .end({ pretty: true })

const errorHighSayActionXML = xmlbuilder
  .create('Response')
  .ele(
    'Say',
    { voice: 'woman' },
    'Hi, sorry, thats not quite it. Guess a little lower. Call back to try again. Goodbye.'
  )
  .end({ pretty: true })

const errorLowSayActionXML = xmlbuilder
  .create('Response')
  .ele(
    'Say',
    { voice: 'woman' },
    'Hi, sorry, thats not quite it. Guess a little higher. Call back to try again. Goodbye.'
  )
  .end({ pretty: true })

const errorSayActionXML = xmlbuilder
  .create('Response')
  .ele(
    'Say',
    { voice: 'woman' },
    'Hi, sorry, thats not quite it. Something is wrong with the input you provided. Call back to try again. Goodbye.'
  )
  .end({ pretty: true })

Lets break down how the XML builder works:

  1. We create a root XML tag: xmlbuilder.create('Response') which will look something like this <Response></Response
  2. We need to create nested elements and we use the ele method to do so: .ele('GetDigits', { timeout: '30', finishOnKey: '#', callbackURL: '' }) this outputs something like this: <GetDigits timeout="30" finishOnKey="#" callbackURL="<YOUR_APP_URL>/voice/say">
  3. We then close a tag using .end and we add in something to the method, { pretty: true } which makes sure our XML is formatted properly.

You can nest as many elements as you want depending on your XML data structure

Routes and Logic

Since we have our responses already laid out, we'll get to defining some routes and logic that will serve as a flow for your app. There are two key routes we'll be setting up:

  1. "/" POST - this route will be hit the first time our app is loaded up
  2. "/voice/say" POST - this route is loaded once the user hits the # key. The callbackURL will be executed at this time since this is the route we'll define as our callback URL

Let's actually write these routes! Just below the first route that we set up, add this code in:

server.route({
    method: 'POST',
    path: '/',
    handler: async (req, h) => {
      callAction = entrySayActionXMl
      return h.response(callAction)
    }
  })

server.route({
    method: 'POST',
    path: '/voice/say',
    handler: async (req, h) => {
      try {
        digits = parseInt(req.payload.dtmfDigits)
        // Adding checks and tasks
        if (digits == 9) {
          return h.response(successSayActionXMl)
        } else if (digits < 9) {
          return h.response(errorLowSayActionXML)
        } else if (digits > 9) {
          return h.response(errorHighSayActionXML)
        } else {
          return h.response(errorSayActionXML)
        }
      } catch (e) {
        h.response(e).code(500)
      }
    }
  })

Let's break down a couple of things around what we've just done:

  1. The first index route runs when the user first makes the call. We then read out our text. Once the user dials in their answer and hits the # key, we redirect the user using the callbackURL link we provide which follows this pattern: <YOUR_APP_URL>/voice/say.
  2. Once we are redirected to the /voice/say route, we check if the user entered the right input and provide feedback based on the answer. We get dtmfDigits from the Africa's Talking API to make the comparisons.

Once done, we basically have our voice application ready. Next up, we need to prep it for deployment. You can test your endpoints with Postman or, as I found out today courtesy of Hashnode, Postwoman.

Deploying to Heroku with a Docker container

We'll be using Heroku in this case to deploy our app and best of all we'll be using Docker to containerize our application. To get this done, you'll need a Heroku account and Docker installed on your machine. Once you have your accounts created, install the Heroku CLI and run the following commands:

heroku login
heroku container:login

This logs us into the Heroku CLI and the second command gets our Heroku container registry setup. To confirm that you have Docker properly installed, run:

docker --version

You should see something like Docker version 19.03.4, build 9013bf5. That's what I'm running currently.

Let's get to deploying this awesome app of ours!

Docker and containers

Docker basically ends the "Works on my machine but not in production" problem that so many of us have faced. To make our app a containerized application, we need to create a Dockerfile. To do this, create a new file on your terminal with:

touch Dockerfile

Open the file and add the following lines:

FROM node:12

#CREATE APP DIRECTORY
WORKDIR /usr/src/app

#Install dependencies
COPY package*.json ./
RUN npm install

#Bundle app source
COPY . .

EXPOSE 8080

CMD ["node","server.js"]

We have essentially finished setting up our container. We just need to run a few commands to get up and running to talk to the Africa's Talking service.

Firstly, we will build our container into an image. We ask docker to build this container, tag it voiceapp and use the Dockerfile in this folder.

docker build -t hapivoiceapp .

Secondly, we will create a heroku app.:

heroku create

We now have an project on Heroku that can actually host our application. The Heroku CLI displays it on your terminal. We then set up heroku to take our container for deployment:

heroku container:push web --app <YOUR_HEROKU_APP_NAME>

The container will be published but it's still not live. Next, we make it live with this command:

heroku container:release web --app <YOUR_HEROKU_APP_NAME>

We are now live!!!!

You can view your application on your Heroku dashboard or view logs from the container using:

heroku logs --app <YOUR_HEROKU_APP_NAME> --tail

Remember to add in the link to your /voice/say route as soon as you're live

Connecting to the Africa's Talking Voice Dashboard

Now that our app is live, we head to the Africa's Talking dashboard . If you don't have an account, signing up is super easy. Once its all set up, create an team and an app that you would use. We currently are still working on getting the Voice sandbox out hence we'll be heading to a live account immediately. Head over to the Voice tab and you should be able to see the number assigned to you. You won't need an API key for this product.

Screen Shot 2019-11-06 at 4.30.12 PM.png

Using the link that we have from Heroku, we set it as the callback URL. And that's it. Give your number a call and enjoy a nice interactive voice response system right there!

Summary

Today, we built an IVR application with some basic functionality. This is a taste of the power of Africa's Talking APIs. I would love to know what awesome apps you can build using this tech and we'll be giving away numbers to developers and start-ups with great ideas on how to use this tech. You can fill out this form and we'll get back to you ASAP! Can't wait to see what awesome software comes out of this.

Next Steps

Next up, we'll be covering a little more advanced techniques around Voice and USSD. If you have cool apps you've built on Africa's Talking, share it in an article and we'll give you a shout-out! (and some swag too!!! ๐Ÿ˜)

Important links

To learn more about Africa's Talking products:

  1. AT Build Documentation: https://build.at-labs.io/discover
  2. More on our culture on our blog: https://blog.africastalking.com
  3. Join our Freelance Developer community: WhatsApp , Facebook
1
Subscribe to my newsletter

Read articles from Anthony Kiplimo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anthony Kiplimo
Anthony Kiplimo

I am a developer, developer relations expert and a user experience designer at Africa's Talking and Elarian. I believe that the next wave of wealth for Africa will be unlocked through the digital economy.