Last Updated: 2021-01-27

What is BNV?

BVN is an acronym for Bank Verification Number. It is an initiative of the Central Bank of Nigeria (CBN) to give customers a unique identification number that can be verified and used across the Nigerian banking industry

Benefits of BVN

With BVN Nigerian bank account holder can enjoy valuable benefits like protection of their accounts from unauthorized access, easy access to banking operations, and unified means of identity verification across all Nigerian banks

BVN makeup and how are they used

Your BVN is a sequence of 11 numbers and it represents a code attached to your personal details — your name, fingerprint, date of birth and a photo of your face. The Central Bank of Nigeria uses those details to protect your identity from people who may want to pretend they're you so they can steal your money or steal other people's money in your name.

When you provide your BVN to a bank or a fintech service, they use it to run a check on the central BVN registry to make sure the number you provided is correct and is yours.

Confirming (i.e verifying) your BVN helps such services protect your identity and keep your money and financial exchanges safe

What you'll build

In this codelab, you're going to build a BVN verifier web app in Javascript. Your app will:

What you'll learn

This codelab is focused on using the NIBSS API to verify a BVN. Non-relevant concepts and code blocks are glossed over and are provided for you to simply copy and paste.

What you'll need

Get a Sandbox API key from the FSI Sandbox

Our BVN verifier app will be built with APIs from the FSI sandbox which require an API key that is available only to registered sandbox users

Sign up to use the FSI Sandbox

After registering and successfully verifying your account, you can access your API key from your Sandbox profile page. More on this later.

Setup your developer environment

Our BVN-Verifier is a JavaScript web app, so we need to have npm (or yarn) installed on our local machine so that we can create a proper project and manage our dependencies. To check and verify that you have an existing npm installation, open a terminal and type :

$ npm --version

You will also need a code editor. If you don't already have one, download and install VSCode, which is what we will be using for this project.

Download & Install VSCode

To keep this codelab simple, we will be running the app locally on our machine instead of deploying it online. We will use special NPM packages to run our backend and frontend apps locally, and thus will be installing them as dev dependencies.

Finally, create a folder called bvn-apps as the home directory where we will create the frontend and backend projects of the app.

Now that you have set up your developer environment, let us proceed to create the backend of our BNV verifier app. Shall we!

Within the bvn-apps folder, create a folder called bvn-verifier-server-app-js, go into the folder with our terminal

$ cd bvn-verifier-server-app-js

Once in, we initialize a standard project with npm init and accept all the default options (which can be adjusted later)

$ npm init -y 

This will initialize our project and create a package.json file that will hold information about the project and its dependencies.

We can now install the dependencies as indicated in the sets of commands below:

$ npm i @babel/cli @babel/core @babel/node @babel/preset-env
$ npm i cors dotenv express morgan innovation-sandbox

The @babel/* dependencies allows us to write our application with modern JavaScript syntax even if it is not yet fully supported in Node. The innovation-sandbox dependency is the FSI API SDK that allows us to easily code against the FSI APIs without the drudgery of directly handling HTTP.

Next, let's install some dependencies that we will use mostly as we build our application.

$ npm i -D nodemon eslint eslint-config-airbnb-base eslint-plugin-import

To make running our app easier, let's now add some scripts for building and starting the backend app. Open the project in VSCode by opening VSCode and dragging the folder into it, then double-click the package.json file to edit it. Place your cursor at the end of the "license" line (after the comma), hit enter, and paste the following code:

bvn-verifier-server-app-js/package.json

"scripts": {
   "dev": "nodemon --exec babel-node ./src/index.js",
   "start": "node ./dist/index.js",
   "build": "npx babel src --out-dir dist --copy-files"
 },

With these in place, your package.json file should look something like below, especially the scripts, dependencies and devDependencies portions:

bvn-verifier-server-app-js/package.json

{
 "name": "backend",
 "version": "1.0.0",
 "main": "index.js",
 "license": "MIT",
 "scripts": {
   "dev": "nodemon --exec babel-node ./src/index.js",
   "start": "node ./dist/index.js",
   "build": "npx babel src --out-dir dist --copy-files"
 },
 "devDependencies": {
   "eslint": "^7.18.0",
   "eslint-config-airbnb-base": "^14.2.1",
   "eslint-plugin-import": "^2.22.1",
   "nodemon": "^2.0.7"
 },
 "dependencies": {
   "@babel/cli": "^7.12.10",
   "@babel/core": "^7.12.10",
   "@babel/node": "^7.12.10",
   "@babel/preset-env": "^7.12.11",
   "cors": "^2.8.5",
   "dotenv": "^8.2.0",
   "express": "^4.17.1",
   "innovation-sandbox": "^1.3.1",
   "morgan": "^1.10.0"
 }
}

Finally, let's setup eslint and prettier so that we are able to write high quality JavaScript code in this project. In the root of your project, create a .prettierrc file and add the following contents

bvn-verifier-server-app-js/.prettierrc

{
  "singleQuote": true,
  "semi": true,
  "printWidth": 120,
  "tabWidth": 2,
  "trailingComma": "none",
  "overrides": [
    {
      "files": "*.json",
      "options": {
        "parser": "json"
      }
    }
  ]
}

Also, create a .eslintrc.js file with the following contents

bvn-verifier-server-app-js/.eslintrc.js

module.exports = {
  env: {
    es2021: true,
    node: true
  },
  extends: [
    'airbnb-base'
  ],
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module'
  },
  rules: {
    'comma-dangle': ['error', 'never'],
    'no-param-reassign': ['error', { props: false }]
  }
};

With the project open in VSCode and in the root folder, create a src folder where we will put the code we intend to write. Also on the root folder, create a .env file that will contain configurations for the app.

After registering and successfully verifying your FSI account, you can access your API key from your Sandbox profile page. Once there, click on the eye icon to toggle the display of key so that you can copy it

Get your API Key

Now that you have the key, open the .env file, set the default PORT you want the backend app to run on (usually 3000), and also set your Sandbox API Key. Our app will load the configurations from this file and use it where necessary. See the sample below:

bvn-verifier-server-app-js/.env

PORT=3000
SANDBOX_KEY=your-fsi-sandbox-key-goes-here

In the src folder, create server.js with the following contents

src/server.js

import cors from 'cors';
import express from 'express';
import morgan from 'morgan';

const app = express();

// Add critical middleware
app.use(cors());
app.use(express.json());
app.use(morgan('tiny'));


// Catch-all error handler
app.use((err, req, res, next) => {
  console.log(err.status, req.path, err.message);
  res.status(err.status || 500).json({
    message: err.message
  });
});

export default app;

server.js contains the actual code that creates an ExpressJS web server which will handle and route incoming HTTP requests to the appropriate API endpoints for our backend app. We will soon be adding the endpoints and routing instructions, but for now, we simply create and export the server instance which our index.js file will import so it can start the server and begin listening for requests on the configured PORT

It's time to create index.js with the following contents:

src/index.js

import path from 'path';
import dotenv from 'dotenv';

const configFile = path.join(__dirname, '../.env');
dotenv.config({ path: configFile });

const runServer = async () => {
  const { default: server } = await import('./server');
  const PORT = process.env.PORT || 5000;
  server.listen(PORT, () => {
    console.log(`server started on /:${PORT}`);
  });
};

runServer();

index.js basically uses the path and dotenv node modules to load our .env file so it can read in our PORT and Sandbox Key configuration settings. It then starts our Express web server instance by importing it and listening on the configured PORT or 5000.

Open your terminal in VSCode (CTRL or Command + ~) and type npm run dev, hit Enter. This will run the "dev" script we created in our package.json file.

You should see output like the following in the terminal window

[nodemon] 2.0.7 
[nodemon] to restart at any time, enter `rs` 
[nodemon] watching path(s): *.* 
[nodemon] watching extensions: js,mjs,json 
[nodemon] starting `babel-node ./src/index.js`
server started on /:5000

The nodemon module allows us to keep the server running and have it automatically restart each time we save changes to our backend code. This way we don't have to manually rerun the commands to start/restart the server.

As part of our backend app's skeleton, let's make sure we are able to catch errors that may arise from using the sandbox APIs. Within the src folder, create a commons folder and then create an errors folder inside it. Add the following files to the errors folder:

src/commons/errors/api-error.js

class APIError extends Error {
 constructor({ message, status }) {
   super(message);
   this.status = status;
   this.message = message;
   this.name = this.constructor.name;
   Error.captureStackTrace(this, this.constructor);
 }
}

export default APIError;

src/commons/errors/nibss-error.js

import APIError from './api-error';

class NIBSSError extends APIError {}

export default NIBSSError;

src/commons/errors/index.js

import NIBSSError from './nibss-error';

export default {
 NIBSSError
};

With the above three files in the errors folder, we've created a system that allows us to throw app-specific errors in response to errors from our use of the Sandbox APIs/SDK. In this case, we intend to catch all errors emanating from using the NIBSS API, and then throw our own NIBSSError with the necessary information so that the rest of our app can act accordingly.

We will be putting NIBSSError to use in the section where we create the BVN verifier endpoint, so watch out!

Before we get into the weeds of handling requests for BVN validation by creating an endpoint for it, lets first create a simple default endpoint for our backend app. This will serve as a fallback endpoint and helps us validate that we are ready to build the BVN verification endpoint.

We will follow this process to create and expose endpoints in our backend app

  1. Create the endpoint file inside src/endpoints folder e.g src/endpoints/ping.js
  2. Ensure src/endpoints/index.js exposes the endpoint to the rest of the application
  3. Ensure the endpoint is used in src/server.js to listen to the appropriate HTTP requests

Let's follow the above steps to create a PING endpoint

Inside the src folder, create a endpoints folder and add the following into a ping.js file

src/endpoints/ping.js

import { Router } from 'express';

const router = Router();
const defaultResponse = `BVN Verifier: ${new Date()}`;
const defaultHandler = (req, res) => res.status(200).send({ message: defaultResponse });

// handle requests if no path or /ping was specified
router.get('/', defaultHandler);
router.post('/', defaultHandler);

export default router;

We are using the Express router to handle HTTP GET and POST requests that are sent to a /ping endpoint. We will also use this endpoint to handle requests where there is no specified endpoint, effectively making it our fallback endpoint.

Next, let's create an index.js file inside the endpoints folder and use it to expose the PING endpoint to the rest of the app

src/endpoints/index.js

import ping from './ping';

export default {
 ping
};

With the above, we can now import src/endpoints/index.js (the file above) into src/server.js to complete registering the PING endpoint which will handle requests to /ping and / (the root of the server)

Modify src/server.js and add the following lines where stated

src/server,js

// after the line that imports morgan, add
import endpoints from './endpoints';

// below the app.use(...) section, 
// hit enter twice (to make space) then add the following
app.use('/', endpoints.ping);
app.use('/ping', endpoints.ping);

If you don't already have your local dev server running, open your terminal in VSCode (CTRL or Command + ~) and type npm run dev, then hit Enter. This will run the "dev" script we created in our package.json file.

You should see output like the following in the terminal window

[nodemon] 2.0.7 
[nodemon] to restart at any time, enter `rs` 
[nodemon] watching path(s): *.* 
[nodemon] watching extensions: js,mjs,json 
[nodemon] starting `babel-node ./src/index.js`
server started on /:5000

If you already have the dev server running, or after starting it like we just did above, take note of the PORT the server is running on (the number on the last line from the above output, e.g 5000) and type http://localhost:PORT into the Chrome URL bar. Since our PORT is 5000, we will be typing http://localhost:5000 into Chrome

You should see a response similar to the one below, in your browser screen:

We are able to see the formatted JSON response from the server because we installed JSONViewer.

All of this confirms that our backend is running and able to handle HTTP requests.

It is now time to create our BVN verification endpoint. See you in the next section!

Again, we will follow the earlier process to create and expose endpoints in our backend app

  1. Create the endpoint file and place it inside src/endpoints e.g src/endpoints/verify-bvn.js
  2. Make sure src/endpoints/index.js exposes the endpoint to the rest of the application
  3. Make sure the endpoint is used in src/server.js to listen to the appropriate HTTP requests

Let's follow the above steps to create a BVN verification endpoint

Inside the endpoints folder and add the following into a verify-bvn.js file

src/endpoints/verify-bvn.js

import { Router } from 'express';
import { nibss } from 'innovation-sandbox';

import errors from '../commons/errors/nibss-error';

const router = Router();
const { NIBSSError } = errors;

const handleVerificationReq = async (req, res) => {
 // TODO validate the bvn input from the client
 const { bvn } = req.body;

 try {
   // TODO use imported NIBSS SDK to verify the bvn input
   let verification;

   const message = verification ? 'verification completed' : `BVN ${bvn} could not be found / verified`;
   return res.status(200).json({
     message,
     verification
   });
 } catch (error) {
   console.log(error);
   res.status(500).json({
     message: 'Unable to handle your verification request. Pls try again or contact support'
   });
 }
};

router.post('/', handleVerificationReq);

export default router;

The above shows that we import the NIBSS API from the innovation-sandbox npm module (the FSI sandbox SDK for JavaScript, which we installed while setting up this project). We also import the NIBSSError error utility we created in the Build The Backend Skeleton section earlier.

We then create an async handleVerificationReq function that we will use to handle HTTP POST requests for BVN verification. It extracts the BVN to be verified from the HTTP request body and then enters a try/catch block (in case there are errors) where it declares a verification variable that will hold the BVN verification status/result after we do the actual verification call.

If an error occurs during the verification flow, handleVerificationReq returns HTTP status code 500, with an appropriate error message. It will return the verification data and HTTP status 200 if the verification flow completes successfully.

The src/endpoints/index.js can now be modified to include and expose the BVN verify endpoint we have just created:

src/endpoints/index.js

import ping from './ping';
import verifyBVN from './verify-bvn';

export default {
 ping,
 verifyBVN
};

Since we've already imported src/endpoints/index.js into src/server.js, all we need to do now is to modify src/server.js so that it has an entry where HTTP requests to verify BVNs are handled by the verifyBVN endpoint exposed by src/endpoints/index.js

src/server.js

// after the last app.use(...) line for /ping, add the following


// Route to the business of this platform!
app.use('/verify/bvn', endpoints.verifyBVN);

With the above setup, we are basically saying all requests to /verify/bvn on our backend app will be routed to, and handled by verifyBVN which we will be fleshing out in the next section

First, edit the .env file in the root of your project folder, add a configuration entry for NIBSS_ORG_CODE. And set the value to 11111 (as stated in the NIBSS API documentation here).

.env

...
NIBSS_ORG_CODE=11111

Next, in src/endpoints/verify-bvn.js file, just above the handleVerificationReq function, add the following :

src/endpoints/verify-bvn.js

const generateNIBSSCredentials = async () => {
 // use the sandbox key and NIBSS org code
 // safely stored locally in our .env file
 const credentials = await nibss.Bvnr.Reset({
   sandbox_key: process.env.SANDBOX_KEY,
   organisation_code: process.env.NIBSS_ORG_CODE
 });

 const { status } = credentials;
 if (status && status >= 400) {
   const message = 'Request likely has invalid Sandbox key and/or NIBSS ORG code';
   console.error(`[HTTP:${status}] ${message}`);
   throw new NIBSSError({
     status,
     message
   });
 }

 return credentials;
};

const getNIBSSCredentials = async () => {
 // TODO call generateNIBSSCredentials as few times as possible
 // and save certs in memory. E.g save to Redis
 const certs = await generateNIBSSCredentials();
 return certs;
};

const verifyBVN = async (data) => {
 const verification = await nibss.Bvnr.VerifySingleBVN({
   ...data,
   sandbox_key: process.env.SANDBOX_KEY,
   organisation_code: process.env.NIBSS_ORG_CODE
 });
 return verification;
};

The generateNIBSSCredentials function uses the NIBSS BVN Reset API to obtain the credentials with which we can securely make subsequent calls to the NIBSS APIs, e.g to verify a BVN. We get the credentials cheaply by using the sandbox SDK, since it saves us the headache of having to know or handle the required encryption involved.

generateNIBSSCredentials returns the NIBSS credentials or uses our NIBSSError class to throw a custom error that makes sense to our backend application

The getNIBSSCredentials function currently just delegates to the generateNIBSSCredentials function to obtain the credentials and returns it. getNIBSSCredentials is a great place to do important things like saving the credentials so that our backend app does not always have to generate a new one for every single request for BVN validation.

The verifyBVN function is where the magic happens. It will be called with data needed for the validation (including the BVN in question), and it uses the SDK to make a call to the verifySingleBVN API. It then returns the verification result payload it gets from the API call.

With all of these specialized utility functions out of the way, we are now ready to modify the handleVerificationReq function to complete the verification flow for our application. Inside the try/catch block in the function, replace the let verification; line in src/endpoints/verify-bvn.js file with the following snippet:

src/endpoints/verify-bvn.js

const { ivkey, aes_key: aesKey, password } = await getNIBSSCredentials();
const { data: verification } = await verifyBVN({
  bvn,
  ivkey,
  password,
  aes_key: aesKey
});

The above basically calls getNIBSSCredentials to get credentials to securely call the NIBSS API. We get back a credential payload which is destructured to ivkey, aesKey, and password

These are then used to call verifyBVN while also passing in the bvn to be verified. The result of that call is further destructured to obtain the verification payload which the rest of handleVerificationReq needs to complete its task.

The complete src/endpoints/verify-bvn.js should look like the following:

src/endpoints/verify-bvn.js

import { Router } from 'express';
import { nibss } from 'innovation-sandbox';

import errors from '../commons/errors/nibss-error';

const router = Router();
const { NIBSSError } = errors;

const generateNIBSSCredentials = async () => {
 const credentials = await nibss.Bvnr.Reset({
   sandbox_key: process.env.SANDBOX_KEY,
   organisation_code: process.env.NIBSS_ORG_CODE
 });

 const { status } = credentials;
 if (status && status >= 400) {
   const message = 'Request likely has invalid Sandbox key and/or NIBSS ORG code';
   console.error(`[HTTP:${status}] ${message}`);
   throw new NIBSSError({
     status,
     message
   });
 }

 return credentials;
};

const getNIBSSCredentials = async () => {
 // TODO call generateNIBSSCredentials as few times as possible
 // and save certs in memory. E.g save to Redis
 const certs = await generateNIBSSCredentials();
 return certs;
};

const verifyBVN = async (data) => {
 const verification = await nibss.Bvnr.VerifySingleBVN({
   ...data,
   sandbox_key: process.env.SANDBOX_KEY,
   organisation_code: process.env.NIBSS_ORG_CODE
 });
 return verification;
};

const handleVerificationReq = async (req, res) => {
 // TODO validate the bvn input
 const { bvn } = req.body;

 try {
   const { ivkey, aes_key: aesKey, password } = await getNIBSSCredentials();
   const { data: verification } = await verifyBVN({
     bvn,
     ivkey,
     password,
     aes_key: aesKey
   });

   const message = verification ? 'verification completed' : `BVN ${bvn} could not be found / verified`;
   return res.status(200).json({
     message,
     verification
   });
 } catch (error) {
   console.log(error);
   res.status(500).json({
     message: 'Unable to handle your verification request. Pls try again or contact support'
   });
 }
};

router.post('/', handleVerificationReq);

export default router;

It was definitely fun building the backend of our BVN verifier app. Now let's run it in our local machine so we can do some very basic BVN verification tests.

Like we did before, you can run the backend app from the VSCode terminal (with the project open in VSCode) by hitting Ctrl or Cmd + ~ and typing npm start or npm run dev (to run it in dev mode). We will go with npm run dev in this example and the output should be similar to:

[nodemon] 2.0.7 
[nodemon] to restart at any time, enter `rs` 
[nodemon] watching path(s): *.* 
[nodemon] watching extensions: js,mjs,json 
[nodemon] starting `babel-node ./src/index.js`
server started on /:5000

If you go to localhost:5000 on Chrome (since the PORT in the last line of the above output is 5000), and you installed the JSONViewer extension, you should see something like the following output in your browser. This is coming from our PING endpoint, and it confirms that our backend app is running and able to handle HTTP requests.

Open Postman and make a HTTP POST request to localhost:5000/verify/bvn making sure to supply the sample BVN number as a JSON request data payload like in the screenshot below

In the annotated sample above, (2) sets the request URL to the URL where your server is running (e.g localhost:5000). Clicking the Send button will send the request to the backend application which handles the verification request and returns the response displayed in (8). If there are any errors, you can head back to the terminal in VSCode to see what hints are available there.

Within the bvn-apps folder you created in an earlier step, create a folder called bvn-verifier-client-app-js, and go into the folder with our terminal

$ cd bvn-verifier-client-app-js

Once in, we initialize a standard project with npm init and accept all the default options (which can be adjusted later)

$ npm init -y 

This will initialize our project and create a package.json file which will hold information about the project and its dependencies.

We can now install the dev dependencies as indicated in the commands below:

$ npm i -D lite-server eslint eslint-config-airbnb-base eslint-plugin-import

Open the project in VSCode and edit the package.json file. Add the following as the scripts section, which will allow us locally run our web app with the lite-server npm module

package. json

"scripts": {
   "dev": "lite-server --baseDir='src'"
 },

Feel free to set up and configure ESlint and Prettier with the .eslintrc.js and .prettierrc files in the app repo on GitHub.

Next, create a src folder in the root of the project folder, then create an images folder within src. Download and copy the files from the images folder in the repo into your new images folder.

In the src folder, create index.html with the following contents:

src/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="shortcut icon" href="./images/fsi-favicon.ico" type="image/x-icon">
    <title>BVN Verifier</title>

    <style>
        input {
            padding: 0.5em;
            padding-right: 1.3em;
            letter-spacing: 10px;
            background-repeat: no-repeat;
            background-position: right center;
        }

        input:placeholder-shown {
            letter-spacing: normal;
        }

        input.busy {
            background-image: url(./images/busy.svg);
        }
        
        input.valid {
            background-image: url(./images/valid.svg);
        }
        
        input.invalid {
            background-image: url(./images/invalid.svg);
        }

        input.error {
            background-image: url(./images/feedback.svg);
        }

        img {
            width:100px;
            height: auto;
            display: block;
        }
    </style>
</head>
<body>

    <div data-app>
        <img src="./images/bvn-logo.webp" alt="BVN" />
        <input data-bvn-input
               placeholder="Enter a valid BVN" 
               type="text">
    </div>

    <script type="module" src="./app.js"></script>
</body>
</html>

Finally, create app.js inside the src folder:

src/app.js

const startApp = () => {
  
};

document.addEventListener('DOMContentLoaded', startApp);

So far, we have a very simple web page displaying an image and an input field. These are styled with inline CSS (for simplicity and speed) and controlled by our currently very simple app.js file that runs the startApp function when the browser is done loading our web page.

Let's try to serve our app and see how it looks in a browser. While in the frontend app's project in VSCode, open the command line and type npm run dev. This will use lite-server to open our src/index.html in a new browser tab to display to following

First let's update the markup of the input field in the HTML file and give it some super powers:

src/index.html

...

<input data-bvn-input
       placeholder="Enter a valid BVN"
       type="text"
       inputmode="numeric"
       pattern="[0-9]*" />

...

Our BVN input field has a data-bvn-input attribute which we will use to identify and select it. You can set an id for it if you prefer.

We've given it a pattern to improve validation by restricting input to only numbers, and also set the new magic inputmode attribute to numeric so that only a numeric keypad is displayed when trying to make an entry on the app on mobile devices. These allow us to customize the text input field for our use case, instead of using a number input field that introduces controls for incrementing and decrementing the input number, which is not what we want in our app.

Moving on, we now need to update src/app.js so that we can handle what the user types into our input field

src/app.js

const digitPattern = /^\d+$/;
const bvnPattern = /^\d{11}$/;
const BACKEND = 'localhost:5000';

let alreadyChecking = false;

const verifyBVN = async (bvn) => {
 
};

const handleInput = async (e) => {
 const { target } = e;
 const { value } = target;

 if (alreadyChecking
     || !digitPattern.test(value)
     || !bvnPattern.test(value)) return;

 alreadyChecking = true;
 requestAnimationFrame(() => {
   target.classList.remove('invalid', 'valid');
   target.classList.add('busy');
 });

 const { verification, error } = await verifyBVN(value);

 alreadyChecking = false;
 requestAnimationFrame(() => {
   target.classList.remove('busy');
   target.removeAttribute('disabled');
   let status = verification ? 'valid' : 'invalid';
   status = error ? 'error' : status;
   target.classList.add(status);
 });
};

const startApp = () => {
 const field = document.querySelector('[data-bvn-input]');
 field.addEventListener('input', handleInput);
};

From the bottom up, startApp grabs the input field and calls handleInput as the user is typing into it. handleInput gets what the user has typed up to that point, checks that it is only a sequence of numbers and exits if it isn't.

After the user has entered 11 digits (a potential valid BVN), handleInput sets alreadyChecking to true to indicate that it now wants to validate the user's entry and prevent unnecessary subsequent checks until this one is completed. It then proceeds to update the app user about the check it is about to initiate by updating the UI and displaying a busy icon in the input field.

The UI update is wrapped and scheduled with requestAnimationFrame to ensure the update is effectively rendered.

After that, we pass the bvn entry to verifyBVN and wait for it to give us a verification or an error response so that we can reset alreadyChecking and update the user again by displaying a valid or invalid icon.

The verifyBVN function simply makes a POST call to our backend server and returns the response or any raised error

src/app.js

...

const verifyBVN = async (bvn) => {
 try {
   const response = await fetch(`${BACKEND}/verify/bvn`, {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json'
     },
     body: JSON.stringify({ bvn })
   });
   const data = await response.json();
   return data;
 } catch (error) {
   console.warn(error);
   return {
     error: error.message
   };
 }
};

...

The complete app.js file that powers our frontend app looks like below. Make sure your BACKEND variable is pointing to where your local server is running, which is likely localhost:3000 or localhost:5000

src/app.js

const digitPattern = /^\d+$/;
const bvnPattern = /^\d{11}$/;
const BACKEND = 'http://localhost:5000';

let alreadyChecking = false;

const verifyBVN = async (bvn) => {
 try {
   const response = await fetch(`${BACKEND}/verify/bvn`, {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json'
     },
     body: JSON.stringify({ bvn })
   });
   const data = await response.json();
   return data;
 } catch (error) {
   console.warn(error);
   return {
     error: error.message
   };
 }
};

const handleInput = async (e) => {
 const { target } = e;
 const { value } = target;

 if (alreadyChecking
     || !digitPattern.test(value)
     || !bvnPattern.test(value)) return;

 alreadyChecking = true;
 requestAnimationFrame(() => {
   target.classList.remove('invalid', 'valid');
   target.classList.add('busy');
 });
 const { verification, error } = await verifyBVN(value);

 alreadyChecking = false;
 requestAnimationFrame(() => {
   target.classList.remove('busy');
   target.removeAttribute('disabled');
   let status = verification ? 'valid' : 'invalid';
   status = error ? 'error' : status;
   target.classList.add(status);
 });
};

const startApp = () => {
 const field = document.querySelector('[data-bvn-input]');
 field.addEventListener('input', handleInput);
};

document.addEventListener('DOMContentLoaded', startApp);

We've done so much work to build our backend and frontend apps, it's time to put them to the test. Let's do that in the next section.

If you have previous terminals running any of the apps (e.g VSCode terminal), exit from all of them (type Ctrl+C in the terminal) so that we are sure there aren't multiple instances of our apps running at the same time.

Open a terminal, move into the backend app's folder and run the dev script to start the server. Note the PORT the server is running on, incase you need to change the PORT of the BACKEND variable in the frontend app, as stated in the previous section:

$ npm run dev

Open another terminal (or a new tab in the previous terminal if tabs are supported), move into the frontend app's folder and run the dev script which should open the app in the browser

$ npm run dev

Type 12345678901 as the sample BVN into the input field and observe what happens in the frontend app. Feel free to open the Chrome developer console to see if any additional details, warnings, or errors are logged there.

The editorial team built and deployed a reference frontend app to http://bvn-verifier.netlify.app in case you want to load it up and also give it a try. On mobile, the app looks like below

Congratulations, you've successfully built and tested your FSI powered BVN verifier web app which uses the JavaScript SDK to interact with the NIBSS API

You now know the key steps required to turn a simple idea into an MVP, powered by the FSI Innovation Sandbox

What's next?

Check out some more codelabs...

Join The FSI Community & Grow With Other Innovators