Flexiple Logo
  1. Home
  2. Blogs
  3. Developers
  4. Building a Web and Voice App ecosystem (Amazon Alexa, Google Home, React, Node)

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

Author image

Akash Joshi

Software Developer

Published on Wed Mar 16 2022

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

In this article, we will look at how you can build a complete ecosystem with Voice apps where you have an interface for Alexa and 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 and APIs to serve all of the interfaces!

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

Let’s get started then!

What are we building

We will build a superhero search app that lets you search for your favorite 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 and use and allows you to create apps that can be deployed to both the Amazon Alexa 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 and 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>
  );

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 and if something like this is logged, it’s working:

Screenshot_2019-06-04_at_60121_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 to check the similarity of input and 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 => {
    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');
  }

By using trial and error for checking the 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_62906_PM-1180x738-1isas

By observing the received 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');
  }

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_104849_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_110621_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-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.

We work with skilled developers to build amazing products. Do check out our services.

Related Blogs

Browse Flexiple's talent pool

Explore our network of top tech talent. Find the perfect match for your dream team.