Build a simple web app

This tutorial goes through all the steps necessary for you to create a simple web app from scratch that’ll integrate with your Qlik Cloud tenant. You’ll learn about concepts such as:

If you’re just interested in the code produced in this tutorial, skip to the Summary section.

Note: You need access to a Qlik Cloud Analytics tenant, and either tenant administrator rights yourself, or the means to get a web integration set up for you.

Requirements

This tutorial requires you to have the following things accessible:

  • Node.js (version 10 or newer)
  • A terminal (for example Git Bash on Windows, or Terminal.app on Mac).
  • OpenSSL (installed through Git Bash, and by default on Mac).
  • A modern web browser, for example, Google Chrome.
  • A text editor or IDE of your choice, for example, Visual Studio Code.
  • An existing web integration, or possibility to get one created in your tenant.

Bootstrapping the project

To avoid making this tutorial too advanced, you’ll only work with plain HTML, CSS, JavaScript, and the enigma.js library, which simplifies communication with the Qlik Associative Engine through websockets.

But before you can start writing any code, you need to prepare the project. There are three steps:

  • Create the necessary project (folders and files) structure.
  • Start a local web server.
  • Create or retrieve a web integration configuration from your tenant.

Project structure

First of all, start your preferred terminal and create the project folder.

# move to a folder where you want to store the project:
cd ~

# new folder to contain the project files:
mkdir simple-web-app

But what good is a folder without any content? Continue by creating an index page and an app JavaScript file that serves as the basis for the logic needed to communicate with your tenant.

Create an index.html file using your favorite editor and save the file in the project folder.

<!doctype html>
<html lang="en">
<meta charset="utf-8" />
<title>Simple web app</title>
<div id="intro">Loading...</div>
<div id="title">Loading...</div>
<script src="https://unpkg.com/enigma.js@2.7.0/enigma.min.js"></script>
<script src="app.js"></script>

</html>

Next, create the app.js file. Just put an IIFE expression in that file for now. You’ll fill it out with more content further into this tutorial.

(async () => {

Next up, learn how to start the HTTPS web server needed for communication with your tenant.

Starting the web server

To serve your web app locally, you’ll need an HTTPS web server. The web integration that you’ll set up later requires HTTPS, which means that you can’t just open up your web app files using the file:// protocol.

In this tutorial, you’ll use http-server, a package available through the npm package manager that comes with your Node.js installation. This is a simple command-line web server that also support HTTPS.

Open up your terminal and enter the following commands. Ignore typing the lines starting with # as they’re just comments for each command. The npx command comes as part of your npm installation and allows you to execute command-line tools available on npm without having to install them globally.

# move to the folder created during the `Project structure` section:
cd ~/simple-web-app

# create a certificate needed for the HTTPS server. Note that this command
# may prompt you for additional information. Enter the information needed
# and continue:
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
# if this command fails, you may have to install openssl manually first.

# start the HTTPS server using the certificates generated before.
# -S means to use HTTPS and the certificates generated by openssl, -c -1 disabled caching:
npx http-server -S -c -1

Note: If you’re using Google Chrome and get an error about invalid certificate, you may need to navigate to chrome://flags/#allow-insecure-localhost in a browser tab and enable that flag.

If these commands run successfully, you should now have an HTTPS web server running. Note the URL shown in the command-line window as you’ll need it in the next step when configuring the web integration.

Create or retrieve web integration

To allow third-party domains to communicate with your tenant, you need to set up a web integration. This process requires tenant administrator privileges.

Once you’ve created or retrieved the web integration ID, you should have everything you need to start communicating with your tenant in your web app.

In the next section, you’ll learn more about the business logic needed in your web app to communicate with your tenant.

Building the web app

In the previous section, you learned how to set up your developer environment, including retrieving the necessary web integration ID. In this section, you’ll learn:

  • How to configure your HTTP requests properly.
  • How to detect if your web app visitor is signed in and trigger the single-sign on, if needed.
  • How to open up websocket connections to the Qlik Associative Engine using enigma.js.

Configure HTTP requests

When communicating with a tenant, you’re required to supply the web integration you’ve configured for your web app with each request. This reduces the risk of abuse, and at the same time avoids having to maintain a global whitelist of domains for each tenant.

Next, you’ll add some code to your app.js file that helps you to properly configure requests towards your tenant.

Note: fetch is a web standards API for making REST calls, see full documentation on this API.

(async () => {
  const tenantUri = 'https://your-tenant.us.qlikcloud.com';
  const webIntegrationId = 'yourWebIntegrationId';
  const titleElement = document.getElementById('title');

  async function request(path, returnJson = true) {
    const res = await fetch(`${tenantUri}${path}`, {
      mode: 'cors',
      credentials: 'include',
      redirect: 'follow',
      headers: {
        // web integration is sent as a header:
        'qlik-web-integration-id': webIntegrationId,
      },
    });
    if (res.status < 200 || res.status >= 400) throw res;
    return returnJson ? res.json() : res;
  }

This function is used throughout the rest of this tutorial to simplify doing REST API calls. fetch has to be configured for CORS or your web app might be blocked by the browser and/or web server. Note that your web integration ID is sent as a header.

Ensure your user is signed in to your tenant

In this step, you’ll fetch the user metadata, and trigger the single-sign on flow if your user isn’t signed in to your tenant.

Preceding })(); in your app.js file, add the following code:

  try {
    // call your-tenant.us.qlikcloud.com/api/v1/users/me to
    // retrieve the user metadata, as a way to detect if they
    // are signed in. An error will be thrown if the response
    // is a non-2XX HTTP status:
    const user = await request('/api/v1/users/me');
    document.getElementById('intro').innerHTML = `Hello, ${user.name}`;
  } catch (err) {
    const returnTo = encodeURIComponent(window.location.href);
    // redirect your user to the tenant log in screen, and once they're
    // signed in, return to your web app:
    window.location.href = `${tenantUri}/login?returnto=${returnTo}&qlik-web-integration-id=${webIntegrationId}`;
  }

Save the app.js file, and refresh your browser. You should now see a Hello, <your name> message once you’ve signed in.

Note: If you get sent to the hub after logging in, it’s likely that your web integration is misconfigured. Confirm, for example, that you’re using the correct web integration ID, and that the origin your solution runs on is allowlisted.

Next up, you’ll fetch the CSRF token, which is required as a header in all non-GET requests; for websockets it’s defined as a query parameter instead (since you can’t set headers on websockets in browser environments).

Preceding })(); in your app.js file, add the following code.

  try {
    // fetch the CSRF token:
    const res = await request('/api/v1/csrf-token', false);
    const csrfToken = res.headers.get('qlik-csrf-token');
  }
})();

Note: It’s using the request function created earlier, and the false parameter ensures that you’ll get access to the basic response object instead of the JSON body, which is needed to access the headers in this case.

Open an app using enigma.js

In the previous step, you stored the csrfToken value, which is needed when opening websockets. The next thing now is to open a session towards an app using enigma.js. The use case is simplified here so feel free to play around with the code to fit your needs once you got it to present the app title in your web app.

After the const csrfToken = ... row in in your app.js file, add the following code.

    // fetch the list of available apps:
    const apps = await request('/api/v1/items?resourceType=app');

    if (!apps.data.length) {
      titleElement.innerHTML = 'No apps available';
      return;
    }

Note: If your web app is displaying the No apps available text, it means that the user you’re signed in as doesn’t have any apps available to them yet. Head over to the hub and create or import an app then continue this tutorial.

Once you have the app list, you can continue by creating the enigma.js session.

After the if-clause you added from the previous step, add the following code in your app.js file.

    // grab the first app ID in the list:
    const appId = apps.data[0].resourceId;

    // build a websocket URL:
    const url = `${tenantUri.replace(
      'https',
      'wss'
    )}/app/${appId}?qlik-web-integration-id=${webIntegrationId}&qlik-csrf-token=${csrfToken}`;

    // fetch the engine API schema:
    const schema = await (await fetch('https://unpkg.com/enigma.js@2.7.0/schemas/12.612.0.json')).json();

    // create the enigma.js session:
    const session = window.enigma.create({ url, schema });
    const global = await session.open();

In the URL, note that you’re referring to the appId, webIntegrationId, and csrfToken values. These are all required parameters for opening a websocket properly. Next up is to actually open the websocket, and the app, and finally display the app title in your web app.

After the const session = ... row, add the following code to your app.js file.

    // open the app, and fetch the layout:
    const app = await global.openDoc(appId);
    const appLayout = await app.getAppLayout();

    // finally, present the app title in your web app:
    titleElement.innerHTML = appLayout.qTitle;
  } catch (err) {
    window.console.log('Error while setting up:', err);

This concludes this section. You should now have your signed-in user name together with an app title presented in your web app.

Summary

This tutorial hopefully taught you how to create a basic web app from scratch that communicates with your tenant, and all the steps needed to configure web integrations, HTTP requests, and websockets.

The following files have been created as part of this tutorial.

index.html

<!doctype html>
<html lang="en">
<meta charset="utf-8" />
<title>Simple web app</title>
<div id="intro">Loading...</div>
<div id="title">Loading...</div>
<script src="https://unpkg.com/enigma.js@2.7.0/enigma.min.js"></script>
<script src="app.js"></script>

</html>

app.js

(async () => {
  const tenantUri = 'https://your-tenant.us.qlikcloud.com';
  const webIntegrationId = 'yourWebIntegrationId';
  const titleElement = document.getElementById('title');

  async function request(path, returnJson = true) {
    const res = await fetch(`${tenantUri}${path}`, {
      mode: 'cors',
      credentials: 'include',
      redirect: 'follow',
      headers: {
        // web integration is sent as a header:
        'qlik-web-integration-id': webIntegrationId,
      },
    });
    if (res.status < 200 || res.status >= 400) throw res;
    return returnJson ? res.json() : res;
  }

  try {
    // call your-tenant.us.qlikcloud.com/api/v1/users/me to
    // retrieve the user metadata, as a way to detect if they
    // are signed in. An error will be thrown if the response
    // is a non-2XX HTTP status:
    const user = await request('/api/v1/users/me');
    document.getElementById('intro').innerHTML = `Hello, ${user.name}`;
  } catch (err) {
    const returnTo = encodeURIComponent(window.location.href);
    // redirect your user to the tenant log in screen, and once they're
    // signed in, return to your web app:
    window.location.href = `${tenantUri}/login?returnto=${returnTo}&qlik-web-integration-id=${webIntegrationId}`;
  }

  try {
    // fetch the CSRF token:
    const res = await request('/api/v1/csrf-token', false);
    const csrfToken = res.headers.get('qlik-csrf-token');

    // fetch the list of available apps:
    const apps = await request('/api/v1/items?resourceType=app');

    if (!apps.data.length) {
      titleElement.innerHTML = 'No apps available';
      return;
    }

    // grab the first app ID in the list:
    const appId = apps.data[0].resourceId;

    // build a websocket URL:
    const url = `${tenantUri.replace(
      'https',
      'wss'
    )}/app/${appId}?qlik-web-integration-id=${webIntegrationId}&qlik-csrf-token=${csrfToken}`;

    // fetch the engine API schema:
    const schema = await (await fetch('https://unpkg.com/enigma.js@2.7.0/schemas/12.612.0.json')).json();

    // create the enigma.js session:
    const session = window.enigma.create({ url, schema });
    const global = await session.open();

    // open the app, and fetch the layout:
    const app = await global.openDoc(appId);
    const appLayout = await app.getAppLayout();

    // finally, present the app title in your web app:
    titleElement.innerHTML = appLayout.qTitle;
  } catch (err) {
    window.console.log('Error while setting up:', err);
  }
})();

Was this page helpful?