Building a Web and Voice App ecosystem (Amazon Alexa, Google Home, React, Node)

Akash, author for building a web and voice app ecosystem blog
Akash Joshi
Content writer at Flexiple. Passionate about sales. Loves reading.

The introduction of voice platforms like Google Home & Amazon Alexa have changed households across the world. Unlike the entirely futuristic technologies like Virtual Reality & Augmented Reality, these are things which already exist in people’s homes. It is just their potential which has not yet been completely utilised.

In this article, we will particularly look at how you can build a complete ecosystem with Voice apps where you have an interface for Alexa & Google along with your usual web interface. Effectively, the user has a choice to interact with your system either via Alexa, Google Home or the traditional input fields in a browser. The amazing thing about this is that we use the same backend architecture & APIs to serve all of the interfaces!

Building this application across platforms will help us understand how an app can be made for both the voice platforms & the web at the same time. We will do this by consuming & using the same API on both platforms for our app.

Let’s get started then!


TABLE OF CONTENTS

  1. What are we building
  2. Backend for the superhero search app
  3. Creating the Voice App using Voiceflow
  4. Web Frontend Tutorial
    1. Setting Up
    2. API Calls
    3. Adding Search Functionality
    4. Displaying Received Data
  5. What Next?

What are we building


We will build a superhero search app that lets you search for your favourite superhero and fetch details on the same. We will be using the Open Source Superhero API for this purpose.

The completed app will look like this:


Backend for the superhero search app


As mentioned earlier, we will be using the Open Source Superhero API to fetch details about the superhero(es) being searched.

Additionally, I have added a wrapper on top of this API which can then be used as a serverless backend or deployed to a server. You can find the complete code in this gist.


Creating the Voice App using Voiceflow


VoiceFlow is a great tool that helps you create voice apps in a visual way. It is extremely easy to understand & use and allows you to create apps that can be deployed to both the Amazon Alex and Google Home ecosystem.

I have written a complete step-by-step tutorial for building our superhero search app using Voiceflow in this freeCodeCamp article.


Web Frontend Tutorial


Libraries Used:

axios for API requests & create-react-app for React.

We have written extensive tutorials for new and veteran developers about React Hooks. You can find them here and here.


1. Setting Up

Replace

import './App.css';

with

import './index.css';

to use some good inbuilt styles.

Delete the content inside function App() and replace it with:

const searchHandle = async e => {
    e.preventDefault();
  }

  return (
    <div style={{textAlign: 'center'}}>
      <h4>Type the name of a Hero !</h4>
      <form onSubmit={e=>searchHandle(e)}>
      <input id="hero" type="text" /> <button type="submit">Search</button>
      </form>
    </div>
  );
Screenshot_2019-06-04_at_6.00.08_PM-1180x738-qz2j3

We are preventing a refresh on form submission by using e.preventDefault();


2. API Calls

To make calls to the API, we will be using the axios library.

Install it by running the following command on the CLI.

npm add axios

Import axios into your app by adding this piece of code to the top:

import axios from 'axios';

Call the API and log the data to check whether it’s working

const searchHandle = async e => {
    e.preventDefault();
    const resp = await axios.get('<https://akabab.github.io/superhero-api/api/all.json>');
		console.log(resp.data);
  }

Click the search button & if something like this is logged, it’s working:

Screenshot_2019-06-04_at_6.01.21_PM-hdglt

3. Adding Search Functionality

We loop over the JSON data and check the name field of each element.

We do this by changing the function like:

const searchHandle = async e => {
    e.preventDefault();
    const resp = await axios.get('<https://akabab.github.io/superhero-api/api/all.json>');
    const hero = document.querySelector("#hero").value;

    let foundData;
    resp.data.map( elem => {
      if(elem.name === hero) {
        foundData = elem;
      }
    });

    foundData ? console.log(foundData) : console.log('Not Found');
  }

We see that by searching the exact name as in the json, we get the result returned. Otherwise, the logic doesn’t detect a match.

We have used Levenshtein distance check similarity of input & detected string. You can read more about it on this StackOverflow link. By adding this to our code, it looks like this:

...
function App() {

  function similarity(s1, s2) {
    var longer = s1;
    var shorter = s2;
    if (s1.length < s2.length) {
      longer = s2;
      shorter = s1;
    }
    var longerLength = longer.length;
    if (longerLength == 0) {
      return 1.0;
    }
    return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
  }

  function editDistance(s1, s2) {
    s1 = s1.toLowerCase();
    s2 = s2.toLowerCase();

    var costs = new Array();
    for (var i = 0; i <= s1.length; i++) {
      var lastValue = i;
      for (var j = 0; j <= s2.length; j++) {
        if (i == 0)
          costs[j] = j;
        else {
          if (j > 0) {
            var newValue = costs[j - 1];
            if (s1.charAt(i - 1) != s2.charAt(j - 1))
              newValue = Math.min(Math.min(newValue, lastValue),
                costs[j]) + 1;
            costs[j - 1] = lastValue;
            lastValue = newValue;
          }
        }
      }
      if (i > 0)
        costs[s2.length] = lastValue;
    }
    return costs[s2.length];
  }

  const searchHandle = async e => {
...

We will use the similarity code while checking in our map function:

resp.data.map( elem => {
      if(similarity(hero,elem.name)>=.8) {
        foundData = elem;
      }
    });

By using trial & error for checking value of similarity for spiderman as an input string, we see a value ≤ .75 returns spiderwoman as a result, while ≥ .8 returns spiderman correctly.

Screenshot_2019-06-04_at_6.29.06_PM-1180x738-1isas

By observing the recieved JSON, we see that there is a fullName field under biography :

...
"biography": {
      "fullName": "Bruce Wayne",
      "alterEgos": "No alter egos found.",
      "aliases": [
        "Insider",
        "Matches Malone"
      ],
...

We use it to implement Name search into our code too.

const searchHandle = async e => {
    e.preventDefault();
    const resp = await axios.get('<https://akabab.github.io/superhero-api/api/all.json>');
    const hero = document.querySelector("#hero").value;

    let foundData;
    resp.data.map( elem => {
      if((similarity(hero,elem.name)>=.8 || similarity(hero,elem.biography.fullName)>=.8) && !foundData) {
        foundData = elem;
      }
    });

    foundData ? console.log(foundData) : console.log('Hero Not Found');
  }

Try a Top Quality Freelance Developer for 7 Days. Pay Only If Satisfied.


4. Displaying Received Data

Now, we will display the superhero data that we receive. We use the useState hook to set the data when it is received.

import React, { useState } from 'react';
...

...
function App() {

  const [hero,setHero] = useState('');
...

Set hero as the foundElement, whenever found.

...
resp.data.map( elem => {
      if((similarity(hero,elem.name)>=.8 || similarity(hero,elem.biography.fullName)>=.8) && !foundData) {
        foundData = elem;
      }
    });

    foundData ? setHero(foundData) : alert('Hero Not Found');
...
Screenshot_2019-06-05_at_10.48.49_AM-1180x738-t46jg

Create a component to display more data about the hero based on the JSON.

const Hero = props => (
  <>
  <h3>Hero Name : {props.hero.name}</h3>
  <h3>Real Name : {props.hero.biography.fullName}</h3>
  <h3>Born in : {props.hero.biography.placeOfBirth}</h3>
  <h3>Alignment : {props.hero.biography.alignment}</h3>
  <h3>He works as a {props.hero.work.occupation} from {props.hero.work.base}</h3>
  </>
);

Add conditional rendering based on whether the hero exists.

...
</form>
      <br />
      {hero ? 
        <Hero hero={hero} />
        : 'Search for a hero above'}
    </div>
...

Finally, the file looks like this:

import React, { useState } from 'react';
import axios from 'axios';

import './index.css';

const Hero = props => (
  <>
  <h3>Hero Name : {props.hero.name}</h3>
  <h3>Real Name : {props.hero.biography.fullName}</h3>
  <h3>Born in : {props.hero.biography.placeOfBirth}</h3>
  <h3>Alignment : {props.hero.biography.alignment}</h3>
  <h3>He works as a {props.hero.work.occupation} from {props.hero.work.base}</h3>
  </>
);

function App() {

  const [hero,setHero] = useState('');

  function similarity(s1, s2) {
    var longer = s1;
    var shorter = s2;
    if (s1.length < s2.length) {
      longer = s2;
      shorter = s1;
    }
    var longerLength = longer.length;
    if (longerLength == 0) {
      return 1.0;
    }
    return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
  }

  function editDistance(s1, s2) {
    s1 = s1.toLowerCase();
    s2 = s2.toLowerCase();

    var costs = new Array();
    for (var i = 0; i <= s1.length; i++) {
      var lastValue = i;
      for (var j = 0; j <= s2.length; j++) {
        if (i == 0)
          costs[j] = j;
        else {
          if (j > 0) {
            var newValue = costs[j - 1];
            if (s1.charAt(i - 1) != s2.charAt(j - 1))
              newValue = Math.min(Math.min(newValue, lastValue),
                costs[j]) + 1;
            costs[j - 1] = lastValue;
            lastValue = newValue;
          }
        }
      }
      if (i > 0)
        costs[s2.length] = lastValue;
    }
    return costs[s2.length];
  }

  const searchHandle = async e => {
    e.preventDefault();
    const resp = await axios.get('<https://akabab.github.io/superhero-api/api/all.json>');
    const hero = document.querySelector("#hero").value;

    let foundData;
    resp.data.map( elem => {
      if((similarity(hero,elem.name)>=.8 || similarity(hero,elem.biography.fullName)>=.8) && !foundData) {
        foundData = elem;
      }
    });

    foundData ? setHero(foundData) : alert('Hero Not Found');
  }

  return (
    <div style={{textAlign: 'center'}}>
      <h4>Type the real name or superhero name of a Hero !</h4>
      <form onSubmit={e=>searchHandle(e)}>
      <input id="hero" type="text" /> <button type="submit">Search</button>
      </form>
      <br />
      {hero ? 
        <Hero hero={hero} />
        : 'Search for a hero above'}
    </div>
  );
}

export default App;
Screenshot_2019-06-05_at_11.06.21_AM-1180x738-hwvln

  The complete app is deployed here, and you can find the Github repo here.  


What Next?


You would have already figured out that you can now build a voice app extension to your existing web apps or vice-a-versa. In one of our past articles, we built a fully functional group chat application with React Hooks. You can now add voice functionality to it using Voiceflow that can enable users to send and read messages!

Also, we regularly write about JavaScript concepts ranging from advanced to basic topics and break them down so that they can be easily consumed. Check them out if that’s something you find interesting

Here are a few useful links specific to Voice apps that you may want to explore:

https://getvoiceflow.com

https://developer.amazon.com/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html

https://learn.voiceflow.com

That’s all for now! Watch out this space for more exciting articles.