SDK Documentation Help

Creating a JavaScript Barcode Scanning App From Scratch

In this tutorial, we will build an app that uses barcode scanning from scratch. The aim is not to build a production-ready, real-world app, but instead illustrate how common scanning workflows can be implemented using the SDK's capabilities and plain JavaScript/HTML/CSS.

The app is deployed under https://pixelverse-llc.github.io/inventory-tracking-app/ and full source is available on GitHub.

Inventory Tracking

The app that we'll build will do inventory tracking. The user signs into to the app and can borrow items (e.g. a laptop) from a storage locker, and return them later. Tracking of physical items is a task that is often done using barcode scanning.

Tech Stack

In frontend development, there are always a lot of different ways to achieve the same thing for any given task. For this tutorial, we have chosen a deliberately minimal approach that should be easily adaptable to your framework of choice.

Vanilla JS/HTML/CSS (No Framework)

We are going to implement the app without using any frontend frameworks or libraries, relying only on the baseline capabilities available in modern web browsers – JavaScript, HTML and CSS.

This might seem like an odd choice, but STRICH is framework-agnostic and aims to work well in any web environment. We encourage you to try building an app with just the core Web primitives – it's a refreshing experience.

No Styling

We will not use any styling beyond basic layout and making the buttons touch-friendly. We will make it functional and have acceptable UX. The app will not look beautiful.

Feel free to add some style to the app if you want!

No Backend - localStorage Only

To keep things simple, the app will not use a backend and instead use localStorage to store the currently logged-in user and borrowed inventory.

No Build Steps, No NPM

No build steps like bundling and minification involved. The HTML, JS and CSS that we create can be opened as-is.

How the App uses Barcode Scanning

The app will use barcode scanning for two purposes: user identification and item scanning.

Scanning Badge QR Code

The app will require an user to scan a QR Code printed on a hypothetical badge before items can be borrowed. We'll assume the QR Code contains a string of digits, like the one below.

Employee badge with QR Code

Scanning Inventory Items

Items are borrowed and returned by scanning the 1D barcode printed on them. We assume that items are labelled using Code 128, Code 39 or EAN/UPC product barcodes.

Here's a screen recording of a user scanning three PlayStation games in quick succession:

Project Setup

Git Repository

We will store the app in a Git repository and use GitHub Pages to deploy it under a publicly reachable, HTTPS-enabled URL. We'll assume that you are already familiar with Git, the de-facto standard for version control. If you haven't used Git before, we recommend GitHub's Getting Started documentation.

Create a new Git repository on GitHub. Then, in your repository settings, make sure GitHub Pages is enabled, and choose main as the branch and / (root) as the repository path from where to deploy your web app. Your settings should look similar to the screenshot below.

Screenshot of Github Pages configuration

Clone your new Git repository to your local machine using the command-line, or a Git client of your choice, using the URL shown in GitHub. If you want to work directly with the final repository, run the following command:

git clone git@github.com:pixelverse-llc/inventory-tracking-app.git

Directory Structure

Our app consists of the following HTML, CSS and JavaScript files.

Path

Contents

/index.html

Login and inventory screen

/scan.html

Item scanning screen

/css/styles.css

Global stylesheet (shared)

/lib/authentication.js

User authentication module (shared)

/lib/inventory.js

Inventory management module (shared)

/img/badge_qr.webp

Badge image with QR Code

Hello, World on GitHub Pages

Get started by adding an index.html file to the folder where you checked out your repository. Edit it and add a minimal Hello, world! document:

<!DOCTYPE html> <html lang="en"> <body> <main> <h1>Hello, world!</h1> </main> </body> </html>

Add the index.html file to Git, commit it and push it to the main branch.

git add index.html git commit -m "initial commit" index.html git push

If everything is set up properly, you should be able to access your new GitHub Pages site via the URL https://USERNAME.github.io/REPOSITORY/ (URL will also be shown in the settings) after a short wait, and be greeted with your shiny new app:

Screenshot of dummy Hello, World website

Not terribly impressive, but you have a working GitHub Pages setup now! Feel free to play around with it until you're ready for the next step.

Optional: Run a Web Server Locally

For development purposes, it is useful to run a web server that serves the contents of the repository on your local machine. If you have Python installed (it comes pre-installed on macOS), you can launch a simple HTTP server with the following command:

python3 -m http.server

You can then open your web app by opening the URL http://localhost:8000 in your web browser.

Obtaining a STRICH SDK License Key

Next, you will need to get a license key for the STRICH SDK. Create an account in the Customer Portal and start the free trial. A credit card or PayPal will be required, but it will not be charged during the trial. Then create a license key, and when asked for a URL, use your GitHub Pages URL.

Screenshot of Customer Portal license key

Building the Login Screen

Now that we have an SDK license key, we can start scanning some codes!

The first thing our users will see is a login screen. It will consist of a header, an image of a badge with a QR Code printed on it, and a button to scan the QR Code:

Screenshot of login screen

Adding a button to start scanning instead of starting it directly when the page has loaded is recommended, because it allows you to inform the user that access to the camera will be required, and for what purpose. Initially the user will have to grant permission to access the camera to the app, and if the reason is not clear and the prompt appears seemingly out of the blue, it will likely be denied.

Login Screen HTML

In the HTML for the login screen, we set a mobile viewport, include a CSS stylesheet (we'll create it later) and display the screen contents in a centered Flex container which will contain the header, image and button.

Update your index.html to match the snippet below.

<html lang="en"> <head> <meta charset="UTF-8"> <title>Inventory Tracking App</title> <!-- mobile viewport --> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- global styles --> <link rel="stylesheet" href="css/styles.css"> </head> <body> <main> <!-- "Logged out" view --> <div id="logged-out" hidden> <h1>Scan Badge</h1> <img class="img-responsive" src="img/badge_qr.webp" width="378" height="209" alt="Badge with QR Code"> <!-- scan button, enabled when STRICH SDK is initialized --> <button id="scan-qr-button" disabled>Scan QR Code</button> </div> <!-- "Logged in" view (coming later) --> </main> <script type="module"> // screen logic, see next sections </script> </body> </html>

Login Screen Stylesheet (CSS)

To style our app, we will use a CSS stylesheet and store it under css/styles.css. The CSS does a subset of what most CSS Resets do: use the more intuitive border-box box-sizing model, remove margins, and make images responsive.

We also make buttons more touch-friendly by making them full-width and increasing their height to 48px, the recommended size for mobile-friendly buttons.

The content of our screens will live inside a main element, which we will center in the screen using Flex layout.

/* see: https://css-tricks.com/box-sizing/#aa-universal-box-sizing */ *, *:before, *:after { box-sizing: border-box; } /* remove body margin */ body { margin: 0; } /* responsive images */ .img-responsive { max-width: 100%; height: auto; } /* make buttons wide and touch-friendly */ button { min-height: 48px; width: 100%; } /* centered content container */ main { max-width: 640px; padding: 20px; display: flex; flex-direction: column; height: 100dvh; justify-content: center; margin: auto; }

For the full stylesheet, please check out the styles.css in the GitHub repository.

Login Screen Logic (JavaScript)

To fill the screen with some life, we need to add some JavaScript that:

  • Imports the latest version of the STRICH SDK as an ES6 module from the jsDeliver CDN.

  • Checks if the device has a camera using the hasCameraDevice method, and if not, displays an error message to the user.

  • Initializes the STRICH SDK using the license key we obtained previously.

  • Enables the SCAN QR CODE button, and makes it open a PopupScanner instance configured to read QR Codes. We don't do anything with scanned code yet.

Populate the empty script tag in index.html with the following JavaScript, and substitute the placeholder with your license key.

import { StrichSDK, PopupScanner } from "https://cdn.jsdelivr.net/npm/@pixelverse/strichjs-sdk@latest"; /** * Attempt to scan a QR Code using the PopupScanner. * * See: https://docs.strich.io/the-popup-scanner.html */ async function scanQRCode() { const qrCodes = await PopupScanner.scan({ symbologies: ['qr'], labels: { title: 'Scan Badge' } }); // if a QR Code was scanned, use its encoded value as the user, and update the UI if (qrCodes) { // log in... } } async function updateUI() { // set up the QR Code login screen, but check if the browser has access to a camera const hasCamera = await StrichSDK.hasCameraDevice(); if (hasCamera) { const scanQrButton = document.getElementById('scan-qr-button'); try { const licenseKey = '<your license key here>'; await StrichSDK.initialize(licenseKey); scanQrButton.disabled = false; scanQrButton.onclick = scanQRCode; } catch (err) { // handle SDK error: https://docs.strich.io/reference/classes/SdkError.html alert(`Failed to initialize STRICH SDK, your license key may have expired: ${err.message}`); } } else { alert(`Sorry, you need a camera to scan barcodes`); } } // initial UI update updateUI();

Committing and pushing the updated index.html file will cause a new version of your GitHub Pages site to be deployed.

Screenshot of successful login page deployment on GitHub Pages

You should now be able to see your new login page and trigger a QR Code scan. Try it out!

The Authentication Module

Our app uses the data in the badge QR Code to log the user into the app.

Since we do not have a real backend against which we could authenticate the user, we will simply treat the data encoded in the QR Code as the username, and store it in localStorage. We will encapsulate the login functionality in an ES6 module that can be shared between all pages that require access to the login information.

lib/authentication.js: Utility functions for storing the currently logged-in user

The lib/authentication.js module provides access to the name of the logged-in user, and functions for setting the logged-in user, and logging out.

// the key to use to store a logged-in user in localStorage const usernameKey = 'username'; // the currently logged-in username, or null, if the user is logged out export function getUsername() { return localStorage.getItem(usernameKey); } export function logOut() { localStorage.removeItem(usernameKey); } export function logIn(username) { localStorage.setItem(usernameKey, username); }

To load the module in the login screen, import it along-side the SDK.

import { StrichSDK, PopupScanner } from "https://cdn.jsdelivr.net/npm/@pixelverse/strichjs-sdk@latest"; import { logIn } from './lib/authentication.js'; async function scanQRCode() { // ... PopupScanner ... // if a QR Code was scanned, use its value as the user, and update the UI if (qrCodes) { logIn(qrCodes[0].data); await updateUI(); } }

Building the Inventory Screen

After the user has logged in, we want to display the main screen: the list of borrowed inventory, and buttons for the primary actions.

The screen consists of a title, a list of items (barcode and count) and buttons to borrow or lend items. At the bottom, the username of the logged-in user is displayed, along with a button for logging out.

Screenshot of inventory screen

Inventory Screen Layout (HTML)

Add the following snippet as a child of the <main> element. The view is initially hidden, and will be shown only if the user is logged in.

<!-- "Logged in" view --> <div id="logged-in" hidden> <h1>My Items</h1> <!-- placeholder to display if no items were scanned yet --> <p id="no-items-placeholder">There are no items in your inventory yet.</p> <!-- the list of items, populated dynamically from the template below --> <ul id="item-list" hidden></ul> <!-- the item template --> <template id="item-template"> <li> <span class="item-barcode"></span> – (<span class="item-count"></span>) </li> </template> <button id="borrow-items">Borrow Items</button> <button id="return-items">Return Items</button> <button id="log-out">Log Out</button> <p>Logged in as: <span id="username"></span></p> </div>

The list items will be generated dynamically. We use an HTML template element for the list item template.

Inventory Screen Logic (JavaScript)

To switch between the logged-out and logged-in views, we have to extend the previous updateUI() function. Depending on the authentication state, it will either make the logged-in view or the logged-out view visible.

If the user is logged in, the borrowed inventory is loaded from a module that exposes a loadItems() function. The returned items are used to populate the list using the dynamically instantiated item template. If the borrowed inventory is empty, a placeholder is made visible instead.

We hook up the Borrow Items and Return Items buttons and make them navigate to a scan screen, passing a query parameter containing the mode (borrow or return). We disable the Return Items button if the inventory is empty.

At the bottom, we display the logged-in username and a button that logs the user out, clearing the inventory.

import { loadItems, clearItems } from './lib/inventory.js'; /** * Update the UI depending on the current application state (authenticated: yes/no, inventory items) */ async function updateUI() { const isLoggedIn = getUsername() !== null; // show logged-in or logged-out view depending on authentication state document.getElementById('logged-out').style.display = isLoggedIn ? 'none' : 'block'; document.getElementById('logged-in').style.display = isLoggedIn ? 'block' : 'none'; if (isLoggedIn) { // display stored items in a list const items = loadItems(); // show placeholder if list is empty document.getElementById('no-items-placeholder').hidden = items.length > 0; // populate list from item template const listElement = document.getElementById('item-list'); document.getElementById('item-list').hidden = items.length === 0; for (const item of items) { const itemElement = document.getElementById('item-template').content.cloneNode(true); itemElement.querySelector('.item-barcode').innerHTML = item.barcode; itemElement.querySelector('.item-count').innerHTML = `${item.count}x`; listElement.appendChild(itemElement); } // if we don't have any items, disable the return items button if (items.length === 0) { document.getElementById('return-items').disabled = true; } // borrow items/return items buttons navigate to scan page document.getElementById('borrow-items').onclick = () => document.location.href = 'scan.html?mode=borrow'; document.getElementById('return-items').onclick = () => document.location.href = 'scan.html?mode=return'; // show logged-in user and logout button, clears inventory document.getElementById('username').innerHTML = getUsername(); document.getElementById('log-out').onclick = () => { logOut(); clearItems(); updateUI(); }; } else { // logged out logic (see previous sections) } }

Check out the index.html file in the GitHub repository for the full code.

The Inventory Module

Where is loadItems() coming from? Similar to the authentication module, we create a module that encapsulates all interactions with the inventory:

  • Getting the list of borrowed items

  • Adding/removing an item

  • Clearing the list items

Since we are not using a backend, we will again use localStorage to store the borrowed items locally. For every item, a barcode and count are stored.

Adding an item that already exists increments the existing item's count. Removing an item decrements the count. If the count reaches 0, the item is removed from the list.

We use JSON to convert the array of items to a string and vice versa, suitable for storing it in localStorage.

// Expose a list of items from localStorage const itemsKey = 'items'; export function clearItems() { localStorage.removeItem(itemsKey); } export function loadItems() { const itemsStr = localStorage.getItem(itemsKey); return itemsStr === null ? [] : JSON.parse(itemsStr); } export function storeItems(items) { localStorage.setItem(itemsKey, JSON.stringify(items)); } export function addItem(item) { const myItems = loadItems(); const existingItemIdx = myItems.findIndex(it => it.barcode === item.barcode); if (existingItemIdx === -1) { myItems.push(item); } else { myItems[existingItemIdx].count += item.count; } storeItems(myItems); } export function removeItem(item) { const myItems = loadItems(); const existingItemIdx = myItems.findIndex(it => it.barcode === item.barcode); if (existingItemIdx === -1) { myItems.splice(existingItemIdx, 1); } else { myItems[existingItemIdx].count -= item.count; if (myItems[existingItemIdx].count <= 0) { myItems.splice(existingItemIdx, 1); } } storeItems(myItems); }

Save the module's code in a file called lib/inventory.js.

Building the Scanning Screen

We've built the main screen, but our inventory is empty. The scanning screen is where we finally scan items and add them to the inventory.

The scanning screen will have two modes: borrowing items and returning items. Depending on the mode, the scanning screen will display a different title, and interact with the inventory module differently.

The barcode reader will be displayed on top, a label showing the number of items scanned and a Finish Scanning button below.

Screenshot of the scanning screen

The screen will allow scanning multiple items in one go, making the Popup Scanner less suitable. Instead, we will use BarcodeReader, a more customizable integration. Check out the Getting Started Guide for more information about the different ways to integrate STRICH into your app.

Scanning Screen Layout (HTML)

Copy the index.html file and rename it to scan.html. Replace the <main> element with the snippet below.

<main> <!-- Return items header --> <div id="return-items" hidden> <h1>Return Items</h1> <p>Return items to the storage by scanning their barcodes.</p> </div> <!-- Borrow items header --> <div id="borrow-items" hidden> <h1>Borrow Items</h1> <p>Take items from the storage by scanning their barcodes.</p> </div> <!-- STRICH BarcodeReader host element --> <div id="barcode-reader-host" style="position: relative; height: 240px"></div> <!-- number of items scanned --> <p id="items-scanned">No items scanned yet</p> <!-- Finish Scanning button --> <button id="finish-scanning"> Finish Scanning </button> </main> <script type="module"> // scanning screen logic, see next section </script>

Scanning Screen Logic (JavaScript)

The mode is passed to the screen via a query parameter, e.g. scan.html?mode=borrow.

Scanned items are accumulated in the onItemScanned() function and stored in the scannedItems variable.

Compared to scanning the badge QR Code, setting up the barcode scanning code here is a bit more complex. A BarcodeReader is created by invoking its constructor with a Configuration object. The configuration contains the list of barcode types (symbologies) to detect, as well as a parameter that suppresses duplicate scans, and most importantly, the host element. The host element is the HTML element that will contain the visible elements of the BarcodeReader. It needs to have a fixed size and use relative positioning.

When a barcode is detected, the BarcodeReader is instructed will invoke the onItemScanned() callback, which accumulates the item in the scannedItems variable.

After we've configured the BarcodeReader, we initialize and start barcode detection. When scanning is finished, we destroy the BarcodeReader and navigate back to the main screen.

Add the following JavaScript snippet to the <script> tag in the scan.html file.

import { StrichSDK, BarcodeReader } from 'https://cdn.jsdelivr.net/npm/@pixelverse/strichjs-sdk@latest'; import { addItem, removeItem } from './lib/inventory.js'; // list of items to check in or out, objects have a 'barcode' and a 'count' field let scannedItems = []; /** * Add the scanned barcode to a temporary list of scanned items. */ function onItemScanned(barcode) { let existing = scannedItems.find(item => item.barcode === barcode); if (existing) { existing.count++; } else { items.push({ barcode, count: 1 }); } // update displayed item count let totalCount = 0; for (const item of scannedItems) { totalCount += item.count; } document.getElementById('items-scanned').innerHTML = `${totalCount} items scanned`; } // read mode from query parameter and show respective header const queryParams = new URLSearchParams(window.location.search); const mode = queryParams.get('mode') ?? 'borrow'; if (mode === 'borrow') { document.getElementById('borrow-items').hidden = false; } else { // out document.getElementById('return-items').hidden = false; } // initialize SDK and enable button when ready let barcodeReader = null; try { const licenseKey = '<your license key here>'; await StrichSDK.initialize(licenseKey); // BarcodeReader configuration, see: https://docs.strich.io/reference/interfaces/Configuration.html const hostElement = document.getElementById('barcode-reader-host'); const config = { selector: hostElement, engine: { symbologies: ['ean13', 'ean8', 'upca', 'upce', 'code128', 'code39'], duplicateInterval: 2500 } }; barcodeReader = new BarcodeReader(config); // set detection callback barcodeReader.detected = (detections) => { for (let detection of detections) { onItemScanned(detection.data); } }; // acquire camera await barcodeReader.initialize(); // start decoding await barcodeReader.start(); } catch (err) { // see: https://docs.strich.io/reference/classes/SdkError.html alert(`Failed to initialize Barcode Scanning: ${err.message}`); } // finish scanning button commits items and navigates back to inventory document.getElementById('finish-scanning').onclick = async () => { // stop and destroy BarcodeReader (releases camera) await barcodeReader.destroy(); // add scanned items to inventory for (const item of scannedItems) { if (mode === 'return') { // return: remove item from local inventory removeItem(item); } else { // borrow: add item to local inventory addItem(item); } } // navigate back to main screen document.location.href = 'index.html'; }

Putting it All Together

Commit your changes and push them to your GitHub repository. Wait a moment, and your changes should be ready and published on your GitHub Pages site.

Here's how the finished app should look like:

Conclusion

We've built a small but functional barcode scanning app using plain HTML, JavaScript and CSS. We used QR Code scanning to identify the user, and 1D barcode scanning to track physical items. We used localStorage to simulate user authentication and an inventory database. Finally, we stored the app in a Git repository and deployed it to GitHub pages to make it easily accessible.

We hoped you enjoyed this tutorial. If you questions or comments, please drop us a line.

Last modified: 30 April 2025