Build a simple web app
Note: Where possible, use qlik-embed rather than this framework. Review the tutorial embedding Qlik Analytics using qlik-embed web components.
Third-party cookies: This tutorial leverages cookies for auth, which are blocked by some browser vendors. Please use an OAuth solution where possible, which doesn’t leverage cookies.
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:
- How to use web integrations (CORS/domain allowlisting).
- How to do REST calls (headers, query parameters, and more).
- How to set up WebSockets towards the Qlik Associative Engine.
If you’re just interested in the code produced in this tutorial, skip to the Summary section.
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: This uses the
request
function created earlier. Thefalse
parameter ensures access to the basic response object instead of the JSON body, allowing you 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 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);
}
})();