Building a Simple App with Kairos Facial Recognition API in Javascript

Building a Simple App with Kairos Facial Recognition API in Javascript

In this post, i'll share How to Building a Simple App with Kairos Facial Recognition API in Javascript

What is Facial Recognition?

Facial recognition is a system of processes or algorithms that translates the characteristics of a face from a digital source into numbers that can be used to later identify the face.

Face detection, and the ethics surrounding the practice, has become an important topic in tech and a valid topic in political discussions. Facial recognition has a few different names (i.e image processing, computer vision) and falls into the larger category of artificial intelligence and machine learning. It has grown hand-in-hand with the rise of data science practices, the popular programming language Python, and the aggregation of massive data stores. One of the data science techniques that can be used to identify a face is a neural network.

Neural networks are a bit beyond the scope of this article, but we should have the general idea before using the API. If we were to submit an image to a neural network it would be processed at different levels. The network would be trying to answer a question, for example, “Is there a face in this image?“. Sections of the photograph would be selected and the system would gather information on whether the lighting and colors reveal edges, curves—or an object. This information means nothing unless the system has something to compare it with.

The system, hypothetically, has patterns of data points that have been positively identified as resembling faces. If the data points collected from sections of the submitted photograph are consistent with the data point patterns of faces, then the neural network can answer, “Yes, there is a face in this photograph!”, or, “I don’t think there is anything here”. This is typically done with ‘confidence intervals’ that are between 0 and 1. We will be able to glimpse some of these data points in the response object from the Kairos Facial Recognition API.

Kairos API

In using the Kairos API, we remove the need to build our own face database and understand complicated statistical algorithms. This can save a company a lot of time and money. In addition, Kairos is simple. I was able to grasp the use cases for the nine endpoints listed in the dashboard with ease.

With Kairos, images that contain faces (that you hope to identify later) are put into galleries. For example, if I had three pictures of three different friends, I would add each of them to the gallery ‘Friends’. Later, I could use different endpoints (‘/recognize’, ‘/verify’) to identify my friends in group pictures or different individual pictures. I could check all my galleries, or submit an image and have the API search a specific gallery.

Kairos only accepts a few parameters across it’s endpoints:

  • “image”
  • “gallery_name”
  • “subject_id”
  • “selector”

This means that we can quickly build uncomplicated HTTP requests.

Furthermore, the “image” parameter accepts a publicly accessible URL, file upload or base64 encoded photo.

I hope to show you how simple it is to create galleries, upload pictures, and recognize images in the example javascript app below. We will build a ReactJS frontend that allows users to upload images to either the ‘friend’ gallery, or the ‘foe’ gallery. Next, the frontend will send the image data to our node.js backend that will fire off the request to the Kairos API for processing and return the data to the user in the frontend. We will enroll subjects in both galleries, then identify our friends and foes in later images.

Connect to the Kairos Face Recognition API

How to Build a Facial Recognition App with JavaScript

Prerequisites

In order the complete the application there are a few items that need to be completed.

First, you will need a RapidAPI account. Second, after setting up an account you will need to subscribe to the Kairos API. You can’t beat the basic subscription. We get 1000 API calls a month, and after that, it’s $0.0001 each request. That will be plenty for us in this example. Next, make sure that you have:

  • Node >= 8.10 and npm >= 5.6 on your machine
  • some familiarity with how to open and use a command-line tool
  • an internet connection
  • a code editor (I recommend VS Code)
  • some familiarity with Javascript, specifically React and Node.
  • I will be using the bash terminal on macOS but most of the commands should be the same with the Windows command prompt.

We will also need React >=16.8.0, however, if you are following the instructions, we will be downloading an acceptable version in the process.

Steps

Setting Up the Project

1.Open up a new terminal

2. Navigate to a good directory using cd.

3. Make a new directory named face_recognition:

mkdir face_recognition

4. Move into that directory:

cd face_recognition

5. Run npx create-react-app client. Npx is a program that ships with npm. We are creating a new React project using the popular project Create React App and placing it in a folder named ‘client’. After a few minutes, you should see the new folder appear in face_recognition.

6. In face_recognition, make a new directory server and cd into the empty folder.

7. Initialize a new npm module:

npm init -y

The -y flag fills out the information that we would be prompted to answer that describes the package. Filling out this information is unnecessary in this tutorial. We now have our front end folder and our server folder ready for installations and modifications!

Set Up Express Server

1.Open up your code editor. I am using VS Code with the command line interface installed, so if I am in the root of our project (face_recognition) I can execute code . and VS Code will open in that directory. Keep the terminal open because we will be installing packages soon.

2. In the terminal, navigate into server and install express and dotenv:

npm install express dotenv --save

We will use express as our web application and dotenv to store our RapidAPI key so we don’t accidentally commit it to a repository. We will not be pushing the code base to Github, but it’s a good habit to have.

2. Install nodemon for development:

npm install nodemon --save-dev

3. Add a dev script to server/package.json that will make it easier for us to use nodemon in development.

// server/package.json

{
  ...
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js -e js",  // our new script
    ...
}

4. Make a new file server.js in the folder server. Add the below code to it.

// server.js
const express = require('express');

//const path = require('path');

// Import routes

// Create App
const app = express();
const port = process.env.PORT || 3001;

// Adds a parsed json body object to req
app.use(express.json({ limit: '10000kb' }));

// Future route
// app.use('/api/', router);

// DEPLOYMENT: for express to find static assets in the React build
// app.use(express.static(path.resolve('client', 'build')));

// DEPLOYMENT: so express can return an actual webpage
// app.get('*', (req, res) => {
      res.sendFile(path.resolve('client', 'build', 'index.html'));
// });

// console.log that your server is up and running
app.listen(port, () => console.log(`Listening on port ${port}`));

The majority of the code is commented out because it’s not time for it to be used. The code blocks that are commented out and labeled “DEPLOYMENT” will not be used in this app, but would be used if you were to deploy to a server. They allow the express application to find the React frontend and serve static files.

In this file, so far, we are creating the express app, adding JSON parsing middleware, and telling the app to listen for requests on port 3001. Easy enough! Next, we’ll go to the front end for general set up, and make sure that it can communicate with our backend.

Set Up Frontend

In the client folder there is another package.json file. However, it has different contents and is not related to the package.json in the server folder because, although our client and server folders are in the same directory, they are separate entities.

What we are going to do is open up a path of communication between the two folders by adding a directive in package.json. The directive will proxy the requests that we make on the frontend to port 3001 (the port we specified in the express application).

1.Add proxy to front-end

// client/package.json

{
  ...
  },
  "proxy": "http://locahost:3001"
}

Now, when we send requests on the front end to the root url they will be received by our backend.

2. We are going to do some basic clean up in our client folder and install a few packages that we will use later. Open App.css and fill it with the code below:

.App {
  display:flex;
  flex-direction: column;
  min-height: 100vh;
}

.App-header {
  background-color: #282c34;
  min-height: 300px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

main {
  flex-grow: 1;
  text-align: center;
  padding: 25px;
}

footer {
  padding: 2px;
  min-height: 100px;
  display: flex!important;
  flex-direction: column;
  justify-content: center;
  background: #EEEEEE;
  font-size: small;
  text-align: center;
}

header {
  padding: 25px;
}

Explaining the CSS is beyond the scope of the article. The code above will give us a nice layout to build our app.

3. Create React App ships with some example content that is displayed in App.js, but we are going to replace it with our own set up. Open App.js and add the code:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Friend or Foe</h1>
      </header>
      <main>
        
      </main>
      <footer>
        Page created by yournamehere
      </footer>
    </div>
  );
}

export default App;

4. We are almost ready to fire up our app. Before we do that, let’s install a few packages that we are going to use extensively for styling. Double check that your terminal is in the client folder before executing the next command.

npm install react-bootstrap bootstrap

5. Import the bootstrap CSS into the page and add content to the `` tag of our app.

// App.js
...
import 'bootstrap/dist/css/bootstrap.min.css'
import { Container, Row, Col } from 'react-bootstrap'

function App() {
  return (
    <div className="App">
      ...
      <main>
        <Container>
          <Row className="justify-content-md-center">
            <Col md="6">
              <h1>Test person form</h1>
            </Col>
            <Col md="6">
              <h1>Add person form</h1>
            </Col>
          </Row>
        </Container>
      </main>
      ...
    </div>
  );
}

export default App;

We are now ready to start our application and see what we are working with!

5. In client run npm start. The development server will start up and our app will be viewable at http://localhost:3000.

Your page should look like this:

Build Input Form

As can be seen from the headings, we are going to have a form where an image is tested for recognition and another form where we add images to galleries. This is going to be the same ReactJS component, but we are going to add conditionals that decide how the component is rendered. We are going to use React hooks to store our form data.

1.Make a directory in the src folder named components. Inside that folder create another folder ImageUploadForm. Inside ImageUploadForm create two files;

  1. ImageUploadForm.js
  2. ImageUploadForm.module.css

In ImageUploadForm.js we are going to start to build a form, using the react-bootstrap library, that has three inputs. These inputs correspond to three of the basic parameters I mentioned earlier (gallery_name, image, subject_id).

2. Add the code below that structures our form.

import React from 'react'
import { 
    Form 
    } from 'react-bootstrap'
import classes from './ImageUploadForm.module.css'

const ImageUploadForm = (props) => {
    return (
        <Form className="my-4">
            <Form.Row className="text-left">
                select gallery
            </Form.Row>
            <Form.Row className="text-left">
                enter subject name
            </Form.Row>
            <Form.Row>
                select or input image
            </Form.Row>
        </Form>
    )
}

export default ImageUploadForm

3. Import our new form into App.js and add it to our columns.

// App.js
...

import ImageUploadForm from './components/ImageUploadForm/ImageUploadForm'

...
// App.js
...
import ImageUploadForm from './components/ImageUploadForm/ImageUploadForm'

...
   ...
      <main> 
        <Container> 
          <Row className="justify-content-md-center"> 
            <Col md="6"> 
              <h1>Test person form</h1>
              <ImageUploadForm />
            </Col> 
            <Col md="6"> 
              <h1>Add person form</h1>
              <ImageUploadForm /> 
            </Col> 
          </Row>
        </Container>
      </main>
   ...
...

That shouldn’t change the look of the app, but it will when we start adding inputs.

The first input we are going to add are radio buttons that select the gallery name. Our app is going to have two galleries: “friend” and “foe”.

We will need to import the { useState } hook at the top of the page and declare our hook at the top of the components. If you are new to hooks, they are essentially a simple way to hold and change state in a component.

4. Replace “select gallery” with the code below.

// Image UploadForm.js 
import React, { useState } from 'react' 
... 

const ImageUploadForm = (props) => { 
   let [galleryName, setGalleryName]= useState('friend') // set starting value to 'friend'
      ...
         ...
            ...
                <Form.Group>
                    <Form.Label>Gallery</Form.Label>
                    <Form.Text className="text-muted">
                        Select upload gallery.
                    </Form.Text>
                    <Form.Check 
                        type="radio"
                        label="Friend"
                        checked={galleryName === "friend"}
                        onChange={() => setGalleryName("friend")}
                        />
                    <Form.Check 
                        type="radio"
                        label="Foe"
                        checked={galleryName === "foe"}
                        onChange={() => setGalleryName("foe")}
                        />
                </Form.Group>
            ...
         ...
      ...
   ...
}

Next, we are going to do something very similar to store the subject_id (name) variable, but we only want to show this input if we are adding someone to a gallery. Therefore, we will pass a ‘prop’ to each form that identifies which endpoint the form will be utilizing.

If we are testing a person we will set the endpoint prop to ‘recognize’, and if we are adding a person we will set the endpoint to ‘enroll’.

5. In App.js two variables above the App() function:

const recognize = "recognize"

const enroll = "enroll"

6. Add the prop ‘endpoint’ to each upload form. Give the ‘Add person’ form endpoint prop a value of {enroll} and the ‘Test person’ endpoint prop a value of {recognize}. It may seem weird to have variables that match their own string value, but we are going to use these values again and want to avoid hard coding those strings in four different places.

// App.js
...

const enroll = "enroll"
const recognize = "recognize"

...
   ...
      ...
         ...
            <Col md="6"> 
              <h1>Test person form</h1>
              <ImageUploadForm
                endpoint={recognize}
              />
            </Col> 
            <Col md="6"> 
              <h1>Add person form</h1>
              <ImageUploadForm
                endpoint={enroll}
              />
            </Col>
         ...
      ...
   ...
...

Back in ImageUploadForm.js we can check this prop and if it equals ‘enroll’ then we will display the ‘Name’ input.

7. Add the ‘Name’ input with a conditional to ImageUploadForm.js in replace of the row that has ‘enter subject name’. Remember to add the hook that declares the variable and set function.

...
   ...
      ...
         ...
            {props.endpoint === 'enroll' ?
            <Form.Row className="text-left">
                <Form.Group>
                    <Form.Label>Name</Form.Label>
                    <FormControl
                        type="text"
                        placeholder="Subject name"
                        onChange={(e) => setName(e.target.value)}
                        value={name}
                        required
                    />
                </Form.Group>
            </Form.Row>
            : null
            }
         ...
      ...
   ...
...

After adding the conditional input row your forms should look like this:

The last input is the image input, but it will require a little more work.

Adding the Image Input

The image input comes in a few different forms. The Kairos API “image” parameter accepts a publicly accessible URL, file upload or base64 encoded photo. Our form is going to take an image upload, translate it to base64, and submit that string, or it will accept the URL string and submit that as the image parameter.

Therefore, we need two different inputs. In order to accomplish this, we are going to create two tabs in each column, one for URLs, and one for image uploads. We will then pass another prop specifying whether the form accepts ‘file’ or ‘text’.

1.Import the Tab component from react-bootstrap and duplicate the image forms. Each column should have two Tabs, two ImageUploadForms, and one form should have a prop type='file' while the other has a prop type='text'. We are going to add a placeholder prop for our text forms. App.js should have the following code:

import React from 'react'
import './App.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import { Container, Row, Col, Tab, Tabs } from 'react-bootstrap'
import ImageUploadForm from './components/ImageUploadForm/ImageUploadForm'

const recognize = "recognize"
const enroll = "enroll"

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>Friend or Foe</h1>
      </header>
      <main>
        <Container fluid>
          <Row className="justify-content-md-around">
            <Col md="5">
              <h2>Test Person</h2>
              <Tabs defaultActiveKey="upload" id="uncontrolled-tab-example">
                <Tab eventKey="upload" title="Upload">
                  <ImageUploadForm
                    label="Select Image to Test"
                    type="file"
                    endpoint={recognize}
                  />
                </Tab>
                <Tab eventKey="url" title="URL">
                  <ImageUploadForm
                    label="Enter Image URL to Test"
                    type="text"
                    placeholder="Image address URL"
                    endpoint={recognize}
                  />
                </Tab>
              </Tabs>
            </Col>
            <Col md="5" className="bg-light">
              <h2>Add Person</h2>
              <Tabs defaultActiveKey="uploadAdd" id="uncontrolled-tab-example">
                <Tab eventKey="uploadAdd" title="Upload">
                  <ImageUploadForm
                    label="Select Image to Add"
                    type="file"
                    endpoint={enroll}
                  />
                </Tab>
                <Tab eventKey="urlAdd" title="URL">
                  <ImageUploadForm
                    label="Enter Image URL to Add"
                    type="text"
                    placeholder="Image address URL"
                    endpoint={enroll}
                  />
                </Tab>
              </Tabs>
            </Col>
          </Row>
        </Container>
      </main>
      <footer>
        Page created by yournamehere
      </footer>
    </div>
  );
}

export default App;

Now, you should be able to switch back and forth between the ‘Upload’ tab and the ‘URL’ tab. Nothing changes in the form yet because we have not added the image input.

2. In ImageUploadForm.js add the image input where it says ‘select or input image’. We will also need to import Button from react-bootstrap and create the file variable in state.

...
import {
   Form,
   Button,  //import button
   FormControl ) from 'react-bootstrap'                
   ...
	 
const ImageUploadForm = (props) => {
   ...
   let [file, setFile] = useState(null)  // add file to state
      ...
         ...
            ...
               <div className="input-group d-flex justify-content-center mb-3">
                    <div className="input-group-prepend">
                        <Button type="submit" variant="primary">Upload</Button>
                    </div>
                    <div className={props.type === 'file' ? "custom-file" : ''}>
                        <label className={props.type === 'file' ? "custom-file-label text-left" : 'text-center'}>{!file ? props.label : file.name}</label>
                        <input 
                            required
                            type={props.type}
                            className={props.type === 'file' ? "custom-file-input" : "form-control"}
                            placeholder={props.placeholder}
                            />
                    </div>
                </div>
            ...
         ...
      ...
   ...
}

You can now see that the input changes between a text image upload and a file image upload. This is because we are changing values in the code based on the props that the component is being passed.

Add Preview

It would be nice if our users could see the image that they are about to upload. Currently our image input isn’t added to state. We are going to build a file staging function that operates conditionally:

  1. If the image is uploaded, the file is converted to base64 and a local URL is created for the preview
  2. If the image input is a URL the string then we set the appropriate variables to the string

In addition, we will have an `` element that is waiting to receive a value for it’s src attribute.

1.In ImageUploadForm.js add another value to the state:

let [fileSrc, setFileSrc] = useState('')

2. Import the useCallback hook at the top of the page next to useState

import React, { useState, useCallback } from 'react'

The useCallback hook memoizes the function. This is beyond the scope of the article, and might be a little unnecessary for such a small app and function.

3. Add the fileStaging function to ImageUploadForm.js.

...
    const fileStaging = useCallback((e) => {
        if (props.type === 'file') {
            setFile(e.target.files[0])
    
            let reader = new FileReader();
    
            try {
                reader.readAsDataURL(e.target.files[0])
            } catch (e) {
                setFileSrc('')
            }
    
            reader.addEventListener("load", function() {
                setFileSrc(reader.result)
            }, false)
        } else {
            setFileSrc(e.target.value)
        }
    }, [props.type])
...

3. Add the image preview to the bottom of the form.

...
   ...
      ...
         ...
            <h2>Image Preview</h2>
            {fileSrc ? <figure><img className={classes.Image} alt="" src={fileSrc} /></figure> : <p style={{ color: "#CCC" }}>No image to preview</p>}
         </Form>
      ...
   ...
...

4. Add the onChange handler for the function to the image input. The full file should now have the code below:

import React, { useState, useCallback } from 'react' 
import { Form, FormControl, Button, } from 'react-bootstrap' 
import classes from './ImageUploadForm.module.css' 

const ImageUploadForm = (props) => { 
    let [galleryName, setGalleryName] = useState('friend') // set starting value to 'friend'
    let [name, setName] = useState('')
    let [file, setFile] = useState(null)
    let [fileSrc, setFileSrc] = useState('')

    const fileStaging = useCallback((e) => {
        if (props.type === 'file') {
            setFile(e.target.files[0])

            let reader = new FileReader();

            try {
                reader.readAsDataURL(e.target.files[0])
            } catch (e) {
                setFileSrc('')
            }

            reader.addEventListener("load", function() {
                setFileSrc(reader.result)
            }, false)
        } else {
            setFileSrc(e.target.value)
        }
    }, [props.type])

    return (
        <Form className="my-4">
            <Form.Row className="text-left">
                <Form.Group>
                    <Form.Label>Gallery</Form.Label>
                    <Form.Text className="text-muted">
                        Select upload gallery.
                    </Form.Text>
                    <Form.Check 
                        type="radio"
                        label="Friend"
                        checked={galleryName === "friend"}
                        onChange={() => setGalleryName("friend")}
                        />
                    <Form.Check 
                        type="radio"
                        label="Foe"
                        checked={galleryName === "foe"}
                        onChange={() => setGalleryName("foe")}
                        />
                </Form.Group>
            </Form.Row>
            {props.endpoint === 'enroll' ?
            <Form.Row className="text-left">
                <Form.Group>
                    <Form.Label>Name</Form.Label>
                    <FormControl
                        type="text"
                        placeholder="Subject name"
                        onChange={(e) => setName(e.target.value)}
                        value={name}
                        required
                    />
                </Form.Group>
            </Form.Row>
            : null
            }
            <Form.Row>
                <div className="input-group d-flex justify-content-center mb-3">
                    <div className="input-group-prepend">
                        <Button type="submit" variant="primary">Upload</Button>
                    </div>
                    <div className={props.type === 'file' ? "custom-file" : ''}>
                        <label className={props.type === 'file' ? "custom-file-label text-left" : 'text-center'}>{!file ? props.label : file.name}</label>
                        <input 
                            required
                            type={props.type}
                            className={props.type === 'file' ? "custom-file-input" : "form-control"}
                            placeholder={props.placeholder}
                            onChange={(e) => fileStaging(e)}
                            />
                    </div>
                </div>
            </Form.Row>
            <h2>Image Preview</h2>
            {fileSrc ? <figure><img className={classes.Image} alt="" src={fileSrc} /></figure> : <p style={{ color: "#CCC" }}>No image to preview</p>}
        </Form>
    )
}

export default ImageUploadForm

Images can now be previewed!

5. If we add a big image it will overflow the column and take over the page. Add the following CSS to ImageUploadForm.module.css.

.Image {
    max-width: 50%
}

One of the final things that needs to be implemented is a submit function that sends the data we have added to our backend. Currently, we do not have a route set up on the backend to receive the data. Yet we can still get the submit function set up in the way that we want the backend to receive the data.

Add Submit Function

1.We will use the popular library axios to send our data to the backend. In the terminal, while in face_recognition/client install axios:

npm install axios --save

Remember, this component will be used for two kinds of data submission. We will need to create a data payload object to send as a POST request and the contents of that data object will depend on which type of form is being used to submit the data.

Thankfully, a prop is being passed in the form that distinguishes between the two types: endpoint.

The function conditionally adds the subject_id value if the endpoint prop has a value of ‘enroll’.

2. Add the handleSubmit function to ImageUploadForm.js underneath the fileStaging function.

...
   const handleSubmit = (e) => {
        e.preventDefault() // will stop the page from refreshing on submit

        // set alert variable to null

        // constructs data payload
        let data = {
            gallery_name: galleryName,
            image: fileSrc
        }

        // adds subject_id if we are enrolling a new person
        if (props.endpoint === 'enroll') {
            data = {
                ...data, // spread operater - add all the properties in the previously declared data object to this object
                subject_id: name
            }
        }

        console.log(data) // log the data to make sure that it is beng sent the way we want
        axios.post(`/api/upload/${props.endpoint}`, data)
        .then(response => {
            // set alert variable
        })
        .catch(e => {
            set alert variable
        })
    }
...

3. Add the onSubmit handler to the `` tag.

<Form onSubmit={handleSubmit} className="my-4">

Now, submit the form with data. It will fail, but it your data payload will be logged to the console.

Build Route for Uploads

In the submit function we specified a URL to send the data to (/api/upload/${props.endpoint}). We used a string literal that uses back ticks—not quotes—to dynamically change the last part of the URL. There are only two values that we are giving the ‘endpoint’ prop: ‘enroll’ and ‘recognize’.

However, we need to build those routes on the backend.

1.In the terminal navigate to face_recognition/server and run the command npm run dev. We set-up this command earlier in the example.

Listening on port 3001 should be logged to the console. The nodemon server is now listening for changes in our files. This will help us develop and catch mistakes faster.

2. In server, make a directory src and inside of that folder make another directory routes.

3. In routes, make the file upload.js and add the two routes that we talked about earlier.

// upload.js

const router = require('express').Router();

router.route('/enroll').post( async (req, res) => {
    // enroll route
})

router.route('/recognize').post( async (req, res) => {
    // recognize route
})

module.exports = router;

4. The express application does not know about this file. In server.js import the router and set up the route middleware.

// server.js
const express = require('express');
const path = require('path');
require('dotenv').config()

// Import route
const uploadRouter = require('./src/routes/upload') // NEW

// Create App
const app = express();
const port = process.env.PORT || 3001;

// Adds a parsed json body object to req
app.use(express.json({ limit: '10000kb' }));

// Set route middleware
app.use('/api/upload', uploadRouter)               // NEW

// Needed for express to find static assets
//app.use(express.static(path.resolve('client', 'build')));

// Needed so express can return an actual webpage
//app.get('*', (req, res) => {
//    res.sendFile(path.resolve('client', 'build', 'index.html'));
//});

// console.log that your server is up and running
app.listen(port, () => console.log(`Listening on port ${port}`));

Add RapidAPI Credentials and API Calls to the Route

Back in the RapidAPI marketplace, pull up the Kairos Face Recognition dashboard. Locate the two endpoints that we will use (enroll and recognize).

Notice the headers are the same in both endpoints. Furthermore, the HTTP request URL is mostly the same with the exception of the last word.

1.Pull the header information into the route and create a header object. Add another variable named baseUrl and assign it the URL section that doesn’t change between the two routes.

const router = require('express').Router();

const headers = {
    "content-type":"application/json",
    "x-rapidapi-host":"kairosapi-karios-v1.p.rapidapi.com",
    "x-rapidapi-key": process.env.KAIROS_API_KEY,
    "accept":"application/json"
}

const baseUrl = "https://kairosapi-karios-v1.p.rapidapi.com/"

router.route('/enroll').post( async (req, res) => {
...

Take note that I didn’t paste in my api-key. We are going to use the dotenv library that we installed earlier to add this

2. Create a file in the server folder named .env. Add your api-key to that file.

// .env

KAIROS_API_KEY=apikey

If we were pushing this up to a Git repository, we would add ‘.env’ to the .gitignore file so our API key isn’t public or shared among other collaborators.

3. Require the dotenv file in server.js, so our variables are loaded into the app. Underneath the imports add the line:

require('dotenv').config()

Now that we have the header object and base URL, it’s easy to build axios calls.

4. Install axios on the backend and import it into upload.js.

On the frontend, we formed the data to have key values that match what the Kairos API expects as parameter values (subject_id, gallery_name, image). This will make it easy for us to take the request body and simply place it into the request to the Kairos API.

5. Add the Kairos API calls to each route.

const router = require('express').Router();
const axios = require('axios')

router.route('/enroll').post( async (req, res) => {
    let data = {
        ...req.body
    }

    const url = baseUrl + "enroll"

    try {
        const response = await axios({ 
            "method": "POST",
            url,
            headers,
            data
        })
    } catch(e) {
        console.log(e);
        res.status(500).send();
    }
})

router.route('/recognize').post( async (req, res) => {
    let data = {
        ...req.body
    }

    const url = baseUrl + "recognize"

    try {
        const response = await axios({ 
            "method": "POST",
            url,
            headers,
            data
        })

    } catch(e) {
        console.log(e);
        res.status(500).send();
    }
})

module.exports = router;

We get the data from the front end and submit it to the Kairos API, but we don’t send any useful information back. We must inspect the response objects that we get from Kairos.

Building Useful Responses

We can test the endpoints in the RapidAPI dashboard and examine the response objects.

When we enroll a subject, and it’s successful, the response looks like:

{
   "face_id": "68267b4f16394218826",
   "images": [
     {
         "attributes": {
         // demographic information
      },
         "transaction": {
            "confidence": 0.99648, // I left this here because it's the confidence level ID that was discussed at beginning of article
         }
     }
   ],
}

If the image is low resolution, or if it does not contain a face, the response fails. The API does not return an error status, it returns an error object.

{
   "Errors":[
      {
          "ErrCode":5001
          "Message":"invalid url was sent"
      }
   ]
}

We need to check the response for the ‘Errors’ parameter, and if it exists we need to send that error back to the front end. If it succeeds, we can send back whatever information we would like to.

For the enroll endpoint, we will simply send back a nice success message.

However, the recognize response has other information we want to use. If the Kairos API recognizes a face in an image it will return a ‘candidates’ object with the subject_id of the person it recognizes.

Furthermore, if it does not recognize the face, it will not have a candidates object. Knowing this, we can build string responses that send back data in an easy to display format.

1. Insert the remaining code into upload.js. The file should have the contents below:

const router = require('express').Router();
const axios = require("axios");

const headers = {
    "content-type":"application/json",
    "x-rapidapi-host":"kairosapi-karios-v1.p.rapidapi.com",
    "x-rapidapi-key": process.env.KAIROS_API_KEY,
    "accept":"application/json"
}

const baseUrl = "https://kairosapi-karios-v1.p.rapidapi.com/"

router.route('/enroll').post( async (req, res) => {
    let data = {
        ...req.body
    }

    const url = baseUrl + "enroll"

    try {
        const response = await axios({ 
            "method": "POST",
            url,
            headers,
            data
        })

        const params = Object.keys(response.data)

        // check for errors
        if (params.includes('Errors')) {
            console.log(response.data)
            return res.status(400).send({variant:"danger", text: response.data.Errors[0].Message})
        }

        // return a simple string if the add was successful
        res.send({variant:'success', text: `Success! ${data.subject_id} added.`})
    } catch(e) {
        console.log(e);
        res.status(500).send({variant: 'danger', text: 'Server error.'});
    }
})

router.route('/recognize').post( async (req, res) => {
    let data = {
        ...req.body
    }

    const url = baseUrl + "recognize"

    try {
        const response = await axios({ 
            "method": "POST",
            url,
            headers,
            data
        })

        const params = Object.keys(response.data)

        if (params.includes('Errors')) {
            return res.status(400).send({variant: "danger", text: response.data.Errors[0].Message})
        }

        let subjects = []

        // check the response for candidates
        response.data.images.map(item => {
            console.log(item)
            if (item.candidates) {
                subjects.push(item.candidates[0].subject_id)
            }
        })

        // build string responses that contain the subjects that were found, or not found in the image
        if (subjects.length < 1) {
            return res.send({variant: "info", text: "No faces were recognized. Exercise caution."})
        }

        if (data.gallery_name === 'foe') {
            const foeString = `Be careful we recognize ${subjects.join(' ')} as foe!`

            res.send({variant: 'danger', text: foeString})
        } else {
            const friendString = `Hoozah! We recognize friends! That's ${subjects.join(' and ')}`

            res.send({variant: 'success', text: friendString})
        }
    } catch(e) {
        console.log(e);
        res.status(500).send({variant: 'danger', text: 'Server error.'});
    }
})

module.exports = router;

Create the Message Component In The Frontend

There is not a way for us to display useful messages or data on the front end. Yet, you may have noticed how we are structuring the response objects in upload.js:

{
   variant: 'danger', 
   text: 'Server error.'
}

The Message component will take in a string ‘variant’ that corresponds to allowed property values for the Alert components variant prop. The Alert component is part of the bootstrap library and can have values of ‘success’, ‘danger’, ‘primary’, ‘info’, ‘light’, ‘dark’, and a few others.

1.In components create the Message directory. Inside of that folder add the file Message.js.

2. Add the below code to Message.js

import React from 'react'
import { Alert } from 'react-bootstrap'

const message = (props) => {
    const message = props.message

    return (
        <div>
            {message && <Alert variant={props.message.variant}>{props.message.text}</Alert>}
        </div>
    )
}

export default message

It is almost time to add our subjects and test our app!

Finishing Up the ImageUploadForm

The final touches to the form component are:

  • import the Message component
  • declare a variable for the message component
  • set the variable value in the handleSubmit function, and;
  • display the message component when it has a value

The below code is what the final ImageUploadForm.js should contain.

I’ll add comments next to the lines that were added.

import React, { useState, useCallback } from 'react' 
import { Form, Button, FormControl} from 'react-bootstrap' 
import classes from './ImageUploadForm.module.css' 
import axios from 'axios' 
import Message from '../Message/Message' // NEW 

const ImageUploadForm = (props) => { 
    let [alert, setAlert] = useState(null) // NEW 
    let [file, setFile] = useState(null) 
    let [fileSrc, setFileSrc] = useState('') 
    let [galleryName, setGalleryName] = useState('friend')
    let [name, setName] = useState('')

    const fileStaging = useCallback((e) => {
        if (props.type === 'file') {
            setFile(e.target.files[0])

            let reader = new FileReader();

            try {
                reader.readAsDataURL(e.target.files[0])
            } catch (e) {
                setAlert({variant:'danger', text:'Please select valid image.'})  // NEW
                setFileSrc('')
            }

            reader.addEventListener("load", function() {
                setFileSrc(reader.result)
            }, false)
        } else {
            setFileSrc(e.target.value)
        }
    }, [props.type])

    const handleSubmit = (e) => {
        e.preventDefault()

        setAlert(null)             // NEW

        let data = {
            gallery_name: galleryName,
            image: fileSrc
        }

        if (props.endpoint === 'enroll') {
            data = {
                ...data,
                subject_id: name
            }
        }

        axios.post(`/api/upload/${props.endpoint}`, data)
        .then(response => {
            setAlert(response.data)     // NEW
        })
        .catch(e => {
            setAlert(e.response.data)   // NEW
        })
    }

    return (
        <Form onSubmit={handleSubmit} className="my-4">
            {alert && <Message message={alert} />}          // NEW
            <Form.Row className="text-left">
                <Form.Group>
                    <Form.Label>Gallery</Form.Label>
                    <Form.Text className="text-muted">
                        Select upload gallery.
                    </Form.Text>
                    <Form.Check 
                        type="radio"
                        label="Friend"
                        checked={galleryName === "friend"}
                        onChange={() => setGalleryName("friend")}
                        />
                    <Form.Check 
                        type="radio"
                        label="Foe"
                        checked={galleryName === "foe"}
                        onChange={() => setGalleryName("foe")}
                        />
                </Form.Group>
            </Form.Row>
            {props.endpoint === 'enroll' ?
            <Form.Row className="text-left">
                <Form.Group>
                    <Form.Label>Name</Form.Label>
                    <FormControl
                        type="text"
                        placeholder="Subject name"
                        onChange={(e) => setName(e.target.value)}
                        value={name}
                        required
                    />
                </Form.Group>
            </Form.Row>
            : null
            }
            <Form.Row>
                <div className="input-group d-flex justify-content-center mb-3">
                    <div className="input-group-prepend">
                        <Button type="submit" variant="primary">Upload</Button>
                    </div>
                    <div className={props.type === 'file' ? "custom-file" : ''}>
                        <label className={props.type === 'file' ? "custom-file-label text-left" : 'text-center'}>{!file ? props.label : file.name}</label>
                        <input 
                            required
                            type={props.type}
                            className={props.type === 'file' ? "custom-file-input" : "form-control"}
                            placeholder={props.placeholder}
                            onChange={(e) => fileStaging(e)}
                            />
                    </div>
                </div>
            </Form.Row>
            <h2>Image Preview</h2>
            {fileSrc ? <figure><img className={classes.Image} alt="" src={fileSrc} /></figure> : <p style={{ color: "#CCC" }}>No image to preview</p>}
        </Form>
    )
}

export default ImageUploadForm

Test

Time to use our app! To recap, we will be creating two galleries with Kairos. Kairos saves the gallery’s name once it is used and creates a new gallery if the name has not been used. What is great about that is we don’t need a database to store the subject_ids, face dimensions, or images: Kairos does that for us.

I am going to be pulling image addresses off of Google to test the app and will doing a LOTR themed testing.

First, let’s add Gollum to the ‘foe’ gallery:

Second, upload two friends, Aragorn and Gandalf:

Third, check the gallery ‘foe’ with a different image the contains Gollum:

Fourth, see if our app can recognize Gandalf and Aragorn in a different image:

Conclusion

I had a lot of fun working with this API and I hope that the example app showed you how to build something even better.

Remember, there are still many ethical concerns surrounding facial recognition and they should be considered before building something that could compromise someone’s safety.

That being said, I hope you found this article helpful and leave a comment or question if you like!

Thank you for reading!

Creating a RESTful Web API with Node.js and Express.js from scratch

Creating a RESTful Web API with Node.js and Express.js from scratch

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

You can also build a Web API in Node.js by using another framework except Express.js but Express.js is one of the most popular web framework for Node.js.

You can found the final source code of this Web API in this github repository.

Let’s start to create our mentioned Web API.

Before start

If you have never used Node.js or npm package manager you should install them.

To check whether the Node.js is already installed on your computer, open your terminal and run node -v command. If you see your Node.js version it's installed. Otherwise go to below link.

Click here to download and install Node.js (You can choose LTS version)

And if you don’t have any IDE or text editor for writing javascript I advice you Visual Studio Code.

Click here to download VS Code (Optional)

About express-generator

In fact we could use <a href="https://expressjs.com/en/starter/generator.html" target="_blank">express-generator</a> tool which designed to creating an Express Web API quickly but I want to create this API from scratch because of that tool puts some extra files and folder structures that we don't need them now. But you can use this useful tool next time on creating new Web API. I won't use it now due to keep article simple.

Creating Project

Go to your workspace root folder and create a new folder there named "todo-api".

Then create "package.json" and "server.js" files into "todo-api" folder like below.

package.json

{
    "name": "todo-api",
    "version": "1.0.0",
    "scripts": {
        "start": "node server.js"
    },
    "dependencies": {
        "express": "^4.16.4"
    }
}

server.js

const http = require('http');
const express = require('express');
const app = express();
app.use(express.json());
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

After creating above files open your terminal in the "todo-api" folder and run npm installcommand.

This command will be install your project dependencies which pointed at the "package.json" file.

After finished package download process, downloaded dependency files will be installed into"node_modules" folder at the root of the "todo-api" folder.

After finished package installing then run npm start to start our Web API.

Now our Web API listening. To see result open your web browser then write localhost:3000 to address bar and press enter.

As result you’ll see our request handler response in your browser: “todo api works”.

This is a dead simple Express.js Web API. And it needs the some development. For example we need to an api endpoint to get todo items. So let’s add a new API endpoint for this.

Create a new folder named "routes" in the root of the "todo-api" folder.

Then create a "items.js" file inside of "routes" folder and put following codes inside it.

Your final folder structure should be like below;

/todo-api
/node_modules
/routes
    items.js
package.json
server.js

items.js

const express = require('express');
const router = express.Router();
const data = [
    {id: 1, title: 'Finalize project', order: 1, completed: false, createdOn: new Date()},
    {id: 2, title: 'Book ticket to London', order: 2, completed: false, createdOn: new Date()},
    {id: 3, title: 'Finish last article', order: 3, completed: false, createdOn: new Date()},
    {id: 4, title: 'Get a new t-shirt', order: 4, completed: false, createdOn: new Date()},
    {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];
router.get('/', function (req, res) {
    res.status(200).json(data);
});
router.get('/:id', function (req, res) {
    let found = data.find(function (item) {
        return item.id === parseInt(req.params.id);
    });
    if (found) {
        res.status(200).json(found);
    } else {
        res.sendStatus(404);
    }
});
module.exports = router;

Initial code of "items.js" file contains two endpoints. First one gets all todo items and second one gets one item which matches given id parameter.

Before testing items routes we should register it in the "server.js" file.

Modify "server.js" file like below to register new item routes.

server.js

const http = require('http');
const express = require('express');
const itemsRouter = require('./routes/items');
const app = express();
app.use(express.json());
app.use('/items', itemsRouter);
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

Now run npm start to start our Web API.

Then open your web browser and write localhost:3000/items to address bar and press enter.

You’ll see todo items json array in the response body.

And write localhost:3000/items/3 to address bar and press enter.

You’ll see the todo item which has id 3 in the response body.

But not finished up yet.

CRUD Operations and HTTP methods

I think we’ll need CRUD operations to Create, Read, Update and Delete todo items.

We have already two endpoints for getting items. So we need Create, Update and Delete endpoints.

Let’s add also these endpoints into the items.js file.

Our final "items.js" file and endpoints should be like below.

const express = require('express');
const router = express.Router();

const data = [
  {id: 1, title: 'Finalize project',          order: 1, completed: false, createdOn: new Date()},
  {id: 2, title: 'Book ticket to London',     order: 2, completed: false, createdOn: new Date()},
  {id: 3, title: 'Finish last article',       order: 3, completed: false, createdOn: new Date()},
  {id: 4, title: 'Get a new t-shirt',         order: 4, completed: false, createdOn: new Date()},
  {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];

router.get('/', function (req, res) {
  res.status(200).json(data);
});

router.get('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    res.status(200).json(found);
  } else {
    res.sendStatus(404);
  }
});

router.post('/', function (req, res) {
  let itemIds = data.map(item => item.id);
  let orderNums = data.map(item => item.order);

  let newId = itemIds.length > 0 ? Math.max.apply(Math, itemIds) + 1 : 1;
  let newOrderNum = orderNums.length > 0 ? Math.max.apply(Math, orderNums) + 1 : 1;

  let newItem = {
    id: newId,
    title: req.body.title,
    order: newOrderNum,
    completed: false,
    createdOn: new Date()
  };

  data.push(newItem);

  res.status(201).json(newItem);
});

router.put('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let updated = {
      id: found.id,
      title: req.body.title,
      order: req.body.order,
      completed: req.body.completed
    };

    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1, updated);

    res.sendStatus(204);
  } else {
    res.sendStatus(404);
  }
});

router.delete('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1);
  }

  res.sendStatus(204);
});

module.exports = router;

Short Explanation

I wanna explain shortly some points of our last codes.

First of all you must have noticed that our api works on a static data and keeps it on memory. All of our GET, POST, PUT and DELETE http methods just manipulate a json array. The purpose of this is to keep article simple and draw attention to the Web API structure.

Due to this situation our POST method has some extra logic such as calculating next item ids and order numbers.

So you can modify logic and data structures in these http methods to use a database or whatever you want.

Testing API with Postman

We have tested the GET methods of our Web API in our web browser and seen responses. But we can’t test directly POST, PUT and DELETE http methods in web browser.

If you want to test also other http methods you should use Postman or another http utility.

Now I’ll show you how to test the Web API with Postman

Before we start click here and install Postman.

When you first launch Postman after installing you’ll see start window. Close this start window by clicking close button on top right corner. Then you must see following screen.

An empty Postman request

Sending GET Request

Before sending a request to API we should start it by running npm startcommand as we do before.

After start the Web API and seeing “Server listening on…” message write localhost:3000/itemsto address bar as seen below and click Send button. You'll see todo items array as API response like below.

Sending a GET request with Postman

You can try similarly by giving an item id in request url like this localhost:3000/items/3

Sending POST Request

To sending a POST request and create a new todo item write localhost:3000/items to address bar and change HTTP verb to POST by clicking arrow at front of the address bar as seen below.

Sending a POST request with Postman

Before sending the POST request you should add request data to body of the request by clicking body tab and selecting raw and JSON as seen below.

Attaching a JSON body to POST request in Postman

Now click Send button to send POST request to the Web API. Then you must get “201 Created” http response code and seeing created item in the response body.

To see the last status of todo items send a get request to localhost:3000/itemsaddress. You must see newly created item at the end of the list.

Sending PUT Request

Sending PUT request is very similar to sending POST request.

The most obvious difference is request url should be pointed specific item like this localhost:3000/items/3

And you should choose PUT as http verb instead of POST and send all of the required data in the request body unlike POST.

For example you could send a JSON body in the PUT request as below.

An example JSON body for PUT request

{
    "title": "New title of todo item",
    "order": 3,
    "completed": false
}

When you click Send button you must get “204 No Content” http response code. You can check item you updated by sending a get request.

Sending DELETE Request

To send a DELETE request, change the request url to address a specific item id like this localhost:3000/items/3

And select DELETE as http verb and click Send button.

You must get “204 No Content” http response code as result of the DELETE operation.

Send a get request and see the last status of list.

About the DELETE Http Request

I want to say a few words about DELETE http request. You must have noticed something in our delete code. DELETE request returns “204 No Content” every situation.

Http DELETE requests are idempotent. So what that mean? If you delete a resource on server by sending DELETE request, it’s removed from the collection. And every next DELETE request on the same resource won’t change outcome. So you won’t get “404 Not Found” in the second request. Each request returns same response whether succeed or not. That’s mean idempotent operation.

Conclusion

Finally we’ve tested all http methods of our Web API.

As you can see, it works just fine.

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Develop this one fundamental skill if you want to become a successful developer

Throughout my career, a multitude of people have asked me&nbsp;<em>what does it take to become a successful developer?</em>

Throughout my career, a multitude of people have asked me what does it take to become a successful developer?

It’s a common question newbies and those looking to switch careers often ask — mostly because they see the potential paycheck. There is also a Hollywood level of coolness attached to working with computers nowadays. Being a programmer or developer is akin to being a doctor or lawyer. There is job security.

But a lot of people who try to enter the profession don’t make it. So what is it that separates those who make it and those who don’t? 

Read full article here