Build an end-to-end application with LoopBack, Cloudant, and React.js

In this tutorial, learn how to build an end-to-end React.js application that serves as a dashboard to display sales data that is stored in a Cloudant database. See how to use LoopBack 4 to expose REST APIs to connect to the Cloudant database so that the frontend dashboard can call the APIs.

Overview

The are two main parts of this tutorial:

tutorial overview

  • Part 1: Create the APIs connected to Cloudant using LoopBack: See how to create a LoopBack application lb4-sales-analytics that connects to a Cloudant database.
  • Part 2: Create the React application that calls the REST APIs created in Part 1: Learn how to build a dashboard application, lb4-sales-react-app, using React.js.

Prerequisites

To complete the steps in this tutorial, you need to:

Part 1: Create APIs connecting to Cloudant via LoopBack

In this section we build a LoopBack application that creates APIs that talk to the Cloudant database.

application overview

Step 1. Scaffold a LoopBack 4 application

Generate a LoopBack 4 application called lb4-sales-analytics by running the application generator command: lb4 app.

$ lb4 app lb4-sales-analytics
? Project description: lb4-sales-analytics
? Project root directory: lb4-sales-analytics
? Application class name: Lb4SalesAnalyticsApplication
? Select features to enable in the project (Press <space> to select, <a> to toggle all, <i> to invert selectio
n)Enable tslint, Enable prettier, Enable mocha, Enable loopbackBuild, Enable vscode, Enable docker, Enable rep
ositories, Enable services
...
Application lb4-sales-analytics was created in lb4-sales-analytics.

Next steps:

$ cd lb4-sales-analytics
$ npm start

Tip: If you’re going to use all the default values, you can speed up the scaffolding process by using --yes option; so, in this case: lb4 app your-app-name --yes.

Step 2. Create the sales model

The Sales model represents each sales order, which have the following properties:

  • id: id of the sales order
  • description: description of the sales order
  • date: date when the sales order is being made
  • country: country where the sales order is being made
  • total: amount of the sales order

Run the command lb4 model to generate the model.

$ lb4 model
? Model class name: Sales
? Please select the model base class Entity (A persisted model with an ID)
? Allow additional (free-form) properties? No
Let's add a property to Sales
Enter an empty property name when done

? Enter the property name: id
? Property type: number
? Is id the ID property? Yes
? Is it required?: No
? Default value [leave blank for none]:

Let's add another property to Sales
Enter an empty property name when done

? Enter the property name: description
? Property type: string
? Is it required?: No
? Default value [leave blank for none]:

Let's add another property to Sales
Enter an empty property name when done

? Enter the property name: date
? Property type: date
? Is it required?: Yes
? Default value [leave blank for none]:

Let's add another property to Sales
Enter an empty property name when done

? Enter the property name: country
? Property type: string
? Is it required?: Yes
? Default value [leave blank for none]:

Let's add another property to Sales
Enter an empty property name when done

? Enter the property name: total
? Property type: number
? Is it required?: Yes
? Default value [leave blank for none]:

Let's add another property to Sales
Enter an empty property name when done

? Enter the property name:
   create src/models/sales.model.ts
   update src/models/index.ts

Model Sales was created in src/models/

Step 3. Add a datasource

Now let’s create a datasource that connects to a Cloudant database. This example uses the Couchdb Docker image, so that you can run the database locally. The LoopBack Cloudant connector repo contains utility scripts to download and run the Docker image. See the instructions. Alternatively, you can use the Cloudant service on IBM Cloud.

Run the DataSource generator command lb4 datasource to create the DataSource.

$ lb4 datasource
? Datasource name: cloudant
? Select the connector for cloudant: IBM Cloudant DB (supported by StrongLoop)
? Connection String url to override other settings (eg: https://username:password@host): http://admin:pass@localhost:8080/lb4-sales
? database:
? username:
? password: [hidden]
? Specify the model name to document mapping, defaults to `loopback__model__name`:
   create src/datasources/cloudant.datasource.json
   create src/datasources/cloudant.datasource.ts
...
Datasource cloudant was created in src/datasources/

This datasource configures the LoopBack Cloudant connector. If you have the connection URL, you can ignore the rest of the settings like database, username, and password.

By default, the REST APIs can only return a 25 instances at most. To modify that, you can add a globalLimit property to the generated cloudant.datasource.json as below:

{
  "name": "cloudant",
  "connector": "cloudant",
  "url": "http://admin:pass@localhost:8080",
  "database": "lb4-sales",
  "username": "",
  "password": "",
  "modelIndex": "",
  "globalLimit": 1000
}

If you plan to deploy the LoopBack application to IBM Cloud and use the Cloudant service, go to the Deploying to IBM Cloud docs page for binding the application to the service so that the Cloudant service credentials won’t be exposed in the LoopBack application.

Step 4. Create a repository

The Repository generator command lb4 repository creates a Repository class that binds the datasource and the model:

$ lb4 repository
? Please select the datasource CloudantDatasource
? Select the model(s) you want to generate a repository Sales
? Please select the repository base class DefaultCrudRepository (Legacy juggler bridge)
   create src/repositories/sales.repository.ts
   update src/repositories/index.ts

Repository SalesRepository was created in src/repositories/

Step 5. Create a controller

Lastly, create the Controller that handles the request-response lifecycle for your API.

$ lb4 controller
? Controller class name: Sales
? What kind of controller would you like to generate? REST Controller with CRUD functions
? What is the name of the model to use with this CRUD repository? Sales
? What is the name of your CRUD repository? SalesRepository
? What is the name of ID property? id
? What is the type of your ID? number
? What is the base HTTP path name of the CRUD operations? /sales
   create src/controllers/sales.controller.ts
   update src/controllers/index.ts

Controller Sales was created in src/controllers/

Besides the generated CRUD operations, you need to add two more endpoints:

  • GET /sales/analytics/{country}/{year}/{month}: Get the number of sales by country in a date YYYY/MM range
  • GET /sales/analytics/{country}/{year}: Number of sales by country for a year

In src/controllers/sales.controller.ts, add the following two methods that correspond to the new endpoints:

@get('/sales/analytics/{country}/{year}/{month}', {
    responses: {
      '200': {
        description: 'Number of sales by country in a date YYYY/MM range.',
        content: {'application/json': {schema: CountSchema}},
      },
    },
  })
  async analyticsMonthAndYear(
    @param.path.string('country') country: string,
    @param.path.number('year') year: number,
    @param.path.number('month') month: number,
  ): Promise<number> {
    const filter = {
      where: {
        country,
        date: {
          between: [
            new Date(year, month - 1).toISOString(),
            new Date(year, month).toISOString(),
          ] as [string, string],
        },
      },
    };

    const res = await this.salesRepository.find(filter);

    return res.length;
  }

  @get('/sales/analytics/{country}/{year}', {
    responses: {
      '200': {
        description: 'Number of sales by country for a year.',
        content: {'application/json': {schema: CountSchema}},
      },
    },
  })
  async analyticsYear(
    @param.path.string('country') country: string,
    @param.path.number('year') year: number,
  ): Promise<number> {
    const filter = {
      where: {
        country,
        date: {
          between: [
            new Date(year, 0).toISOString(),
            new Date(year + 1, 0).toISOString(),
          ] as [string, string],
        },
      },
    };

    const res = await this.salesRepository.find(filter);

    return res.length;
  }

Step 6. Change port to 3001

The default port used in the LoopBack application is 3000. Since the React application you’re going to create will use the same port, you need to modify the LoopBack application to use port 3001 instead.

Go to index.js. Change the port from 3000 to 3001 in config, like this:

const config = {
  rest: {
    port: +(process.env.PORT || 3001),
    host: process.env.HOST,
    openApiSpec: {
      // useful when used with OpenAPI-to-GraphQL to locate your application
      setServersFromRequest: true,
    },
  },
};

Test your API

Now, your application is ready to run. Use npm start to get it going.

$ npm start

Server is running at http://[::1]:3001
Try http://[::1]:3001/ping

Open a browser and go to URL: http://localhost:3001

LoopBack app landing page

The OpenAPI spec for the REST APIs can be found in http://localhost:3001/openapi.json. Test your APIs by going to the API Explorer: http://localhost:3001/explorer.

You can test it out by going to GET /sales/count or any endpoint and click the Try it out button.

Test endpoint

Optional: Seed the database

To seed a database, let’s insert some random data during the start of the application. We can make use of the life cycle support of LoopBack.

Run the life cycle observer generator command: lb4 observer.

$ lb4 observer
? Observer name: AddData
? Observer group: AddDataGroup
   create src/observers/add-data.observer.ts
   update src/observers/index.ts
Observer AddData was created in src/observers/

In the AddDataObserver, there are two generated methods: start() and stop(). To add some data during the start of the application, call Repository.create().

First, we’ll get the SalesRepository that we created earlier.

Add the constructor as follows:

constructor(
  @repository('SalesRepository') private salesRepo: SalesRepository,
) {}

Make sure you add the following imports:

//import the repository decorator
import {repository} from '@loopback/repository';

//import the Sales and SalesRepository in my LB app
import {SalesRepository} from '../repositories';
import {Sales} from '../models';

In the start() method, create an instance of Sales and add to the database through the Repository.create() method.

async start(): Promise<void> {
  let count: number = (await this.salesRepo.count()).count;
  if (count !== 0) return;

  //create an instance of Sales to be inserted into the database
  let salesData = new Sales({
    description: 'this is a sample data',
    date: '2019-01-01',
    country: 'Canada',
    total: 100,
  });
  this.salesRepo.create(salesData);
}

Read this blog post for more details about generating random Sales instances.

Note: Code to generate multiple instances can be found under src/observers/add-data.observer.ts.

Now that you’ve seen how to create APIs that pull data from Cloudant via LoopBack, let’s see how to create the dashboard where you can view this information.

Part 2. Create the dashboard using React.js

In this section, we show you the steps to create a dashboard using React which calls the REST APIs created by the LoopBack application you created in Part 1 for data. If you are new to React, you might want to take this tutorial first which covers how to use the technology.

After everything is built, the app will look like:

Dashboard

In this dashboard, there are three components:

  1. Toolbar at the top of the page
  2. Overview section that contains grids of some data points
  3. Sales graph section that shows the sales number for the past few months for various countries

Step 1: Create a React.js app

Run this command:

$ npx create-react-app lb4-sales-react-app

Step 2: Create the dashboard

Under the src folder, create a file called Dashboard.js. See code in src/Dashboard.js in our pre-built application.

As you can see in the render() method, there are three main components:

  • Toolbar with “LoopBack Dashboard” text
  • Overview section with the CenteredGrid component which we’ll be creating
  • Sales section with the SimpleLineChart component which we’ll be creating

Step 3: Create CenteredGrid for the overview section

Create a file called CenteredGrid.js, and see code in src/CenteredGrid.js in our pre-built application. We are going to create two grids for the total number of sales and total revenue from sales. They maps to the two endpoints /sales/count and /sales you created using LoopBack 4 previously.

Since there are some values that are commonly used in the three components we are creating, create a file called config.js with the following content:

const baseUrl = 'http://localhost:3001';
const availableCountries = ['US', 'Canada', 'Germany', 'France', 'Mexico'];

export {baseUrl, availableCountries};

In CenteredGrid.js, initialize the component’s state as follows in the constructor:

this.state = {
  totalNumberOfSales: 0,
  totalRevenueOfSales: 0,
};

Add the componentDidMount() lifecycle method, so that the REST APIs will be called after the component is rendered and then update the state with the data:

Check the React lifecycle methods.

async componentDidMount() {
  const [ totalNumberOfSales, totalRevenueOfSales ] = await Promise.all([
    fetch(`${baseUrl}/sales/count`).then(res => res.json()).catch(err => err),
    fetch(`${baseUrl}/sales`).then(res => res.json()).catch(err => err)
  ]);

  this.setState({
    totalNumberOfSales: totalNumberOfSales.count || 0,
    totalRevenueOfSales: Array.isArray(totalRevenueOfSales) ?
      totalRevenueOfSales.reduce((sum, curr) => sum + curr.total, 0) : 0,
  });
}

Finally, we will modify the render() function as below:

render() {
  const { classes } = this.props;
  return (
    <div className={classes.root}>
    <Grid container spacing={24}>
      <Grid item xs={6}>
          <Paper className={classes.paper}>
          <h3>Total Number Of Sales</h3>
          { this.state.totalNumberOfSales }
          </Paper>
      </Grid>
      <Grid item xs={6}>
          <Paper className={classes.paper}>
          <h3>Total Revenue From Sales</h3>
          ${ this.state.totalRevenueOfSales }
          </Paper>
      </Grid>
    </Grid>
    </div>
  );
}

Step 4: Create SimpleLineChart for the Sales Section

In this section, we need two values to construct the graph:

  1. x-axis: the dates (year, month, and the label on the graph)
  2. y-axis: the sales graphData

Let’s create a file called SimpleLineChart.js, and we are going to initialize the component’s state with the two variables in the constructor:

Note that the graph period is determined by the variable CALC_PERIOD_IN_MONTHS

constructor() {
  super();

  this.state = {
    graphData: [],
    dates: []
  };

  const MONTHS_IN_TEXT = ['JAN', 'FEB', 'MAR','APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
  const currentYear = new Date().getUTCFullYear();
  const currentMonth = new Date().getUTCMonth();

  for (let i = 0; i < CALC_PERIOD_IN_MONTHS; i++) {
    const date = new Date(currentYear, currentMonth - 12 + i, 1);

    this.state.dates[i] = {
      label: MONTHS_IN_TEXT[date.getUTCMonth()],
      year: date.getUTCFullYear(),
      month: date.getUTCMonth(),
    };
  }
}

Take a look at the componentDidMount() function to see how to get the sales data per country.

Summary

This tutorial shows the end-to-end scenario for having a frontend application that talks to a database through a LoopBack application. In Part 1, we created a LoopBack application that connects to a Cloudant database for getting sales data. The REST APIs exposed in this LoopBack application are used by the React application we created in Part 2 to show the data as dashboard.

Aly Hegazy
Diana Lau