Developing a Web Application for Reading Multiple Barcodes with Go and HTML5

Go’s native support for concurrency is one of its standout features, making it an excellent choice for building web servers capable of handling multiple requests simultaneously and efficiently. In this article, we’ll guide you through the creation of a web application designed to decode multiple barcode images uploaded by users. The barcode detection logic will be implemented on the web server side using Go, while the client-side application will handle image picking, file uploading, and result display.

Prerequisites

HTML & JavaScript for Image Picking, Uploading, and Displaying Results

The user interface (UI) for the web application includes an input element, a button element, an image element, a canvas element, and a textarea element.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>Dynamsoft Vision SDKs</title>
</head>

<body>
    <h1>1D/2D Barcode Reader</h1>
    <div id="loading-indicator" class="loading-indicator">
        <div class="spinner"></div>
    </div>

    <div class="container" id="file_container">
        <div>
            <input type="file" id="pick_file" accept="image/*" />
            <button onclick="detect()">Detect</button>
        </div>

        <div class="row">
            <div class="imageview">
                <img id="image_file" src="default.png" />
                <canvas id="overlay_canvas" class="overlay"></canvas>
            </div>
        </div>

        <div class="row">
            <div>
                <textarea id="detection_result"></textarea>
            </div>
        </div>

    </div>

    <script src="main.js"></script>
</body>

</html>

Go web server barcode reader

  • The input element allows users to select an image file.
  • The image element displays the selected image file.
  • The button element initiates the upload of the image to the server for barcode detection.
  • The canvas element is utilized to draw the contours of the detected barcode.
  • The textarea element presents the decoded barcode data.

Picking an Image File from the File System and Clipboard

We utilize an Image element to load the selected image file and initialize a canvas element to match the size of the image.

let imageFile = document.getElementById('image_file');
let overlayCanvas = document.getElementById('overlay_canvas');
let img = new Image();

function loadImage2Canvas(base64Image) {
    imageFile.src = base64Image;
    img.src = base64Image;
    img.onload = function () {
        let width = img.width;
        let height = img.height;

        overlayCanvas.width = width;
        overlayCanvas.height = height;

        detect();
    };
}

There are three methods provided for users to add an image to the image element:

  • Clicking the input element to select an image file.

      document.getElementById("pick_file").addEventListener("change", function () {
          let currentFile = this.files[0];
          if (currentFile == null) {
              return;
          }
          var fr = new FileReader();
          fr.onload = function () {
              loadImage2Canvas(fr.result);
          }
          fr.readAsDataURL(currentFile);
      });
    
  • Listening for the drag-and-drop event to load an image file.

      let overlayCanvas = document.getElementById('overlay_canvas');
    
      overlayCanvas.addEventListener('dragover', function (event) {
          event.preventDefault();
          event.dataTransfer.dropEffect = 'copy';
      }, false);
        
      overlayCanvas.addEventListener('drop', function (event) {
          event.preventDefault();
          if (event.dataTransfer.files.length > 0) {
              let file = event.dataTransfer.files[0];
              if (file.type.match('image.*')) {
                  let reader = new FileReader();
                  reader.onload = function (e) {
                      loadImage2Canvas(e.target.result);
                  };
                  reader.readAsDataURL(file);
              } else {
                  alert("Please drop an image file.");
              }
          }
      }, false);
    
  • Copying and pasting an image from the clipboard.

      document.addEventListener('paste', (event) => {
          const items = (event.clipboardData || event.originalEvent.clipboardData).items;
        
          for (index in items) {
              const item = items[index];
              if (item.kind === 'file') {
                  const blob = item.getAsFile();
                  const reader = new FileReader();
                  reader.onload = (event) => {
                      loadImage2Canvas(event.target.result);
                  };
                  reader.readAsDataURL(blob);
              }
          }
      });
    

Uploading an Image File as a Base64 String

When the user clicks the “Detect” button, the image file is uploaded to the server as a base64 string using the Fetch API.

const base64Image = img.src;

const requestBody = {
    image: base64Image
};

const response = await fetch('/upload', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify(requestBody),
});

if (!response.ok) {
    throw new Error(`HTTP error! Status: ${response.status}`);
}

Displaying the Decoded Barcode Data

The server returns the decoded barcode data as a JSON object. The client-side application first parses the JSON object, then draws contours of the detected barcode on the canvas element, and displays the barcode data in the textarea.

const barcodes = await response.json();
detection_result.value = `Found ${barcodes.length} barcode(s)\n`;
barcodes.forEach(barcode => {
    detection_result.value += `\nText: ${barcode.Text}, Format: ${barcode.Format}`;
    detection_result.value += `\nCoordinates: (${barcode.X1}, ${barcode.Y1}), (${barcode.X2}, ${barcode.Y2}), (${barcode.X3}, ${barcode.Y3}), (${barcode.X4}, ${barcode.Y4})`;
    detection_result.value += "\n--------------";
    // Draw overlay
    context.beginPath();
    context.strokeStyle = '#ff0000';
    context.lineWidth = 2;
    context.moveTo(barcode.X1, barcode.Y1);
    context.lineTo(barcode.X2, barcode.Y2);
    context.lineTo(barcode.X3, barcode.Y3);
    context.lineTo(barcode.X4, barcode.Y4);
    context.lineTo(barcode.X1, barcode.Y1);
    context.stroke();

    context.font = '18px Verdana';
    context.fillStyle = '#ff0000';
    let x = [barcode.X1, barcode.X2, barcode.X3, barcode.X4];
    let y = [barcode.Y1, barcode.Y2, barcode.Y3, barcode.Y4];
    x.sort(function (a, b) {
        return a - b;
    });
    y.sort(function (a, b) {
        return b - a;
    });
    let left = x[0];
    let top = y[0];

    context.fillText(barcode.Text, left, top + 50);
});

Building the Web Server in Go

We utilize Go to host the web server, managing the upload of image files, detecting barcodes, and generating responses.

Serving Static Content

Organize your project’s directory structure to include index.html within the static folder, along with your CSS, JavaScript, and other assets.

/project
    /static
        styles.css
        main.js
        index.html

Adjust your server setup to serve all static content using http.FileServer.

func main() {
    fs := http.FileServer(http.Dir("./static"))
    http.Handle("/", fs)
    ...
}

Handling Upload Requests for Barcode Detection

Create an endpoint /upload to manage the upload of image files. The server extracts the base64-encoded image string from the request body, decodes it, and utilizes the Dynamsoft Barcode Reader SDK to detect barcodes.

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/yushulx/goBarcodeQrSDK"
)

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
		return
	}

	var data struct {
		Image string `json:"image"` // Field where the base64 image data will be stored
	}

	if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
		http.Error(w, "Error decoding JSON body", http.StatusBadRequest)
		return
	}

	imgData, err := base64.StdEncoding.DecodeString(data.Image[strings.IndexByte(data.Image, ',')+1:])
	if err != nil {
		http.Error(w, "Error decoding base64 image", http.StatusBadRequest)
		return
	}

	obj := goBarcodeQrSDK.CreateBarcodeReader()

	startTime := time.Now()
	ret, barcodes := obj.DecodeStream(imgData)
	elapsed := time.Since(startTime)
	fmt.Println("DecodeStream() time cost: ", elapsed)

	if ret != 0 {
		fmt.Printf(`DecodeStream() = %d`, ret)
	}

	defer goBarcodeQrSDK.DestroyBarcodeReader(obj)

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(barcodes); err != nil {
		// Handle error
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func main() {
	license := "LICENSE-KEY"

	ret, errMsg := goBarcodeQrSDK.InitLicense(license)
	if ret != 0 {
		fmt.Println(`initLicense(): `, ret)
		fmt.Println(errMsg)
		return
	}

	fs := http.FileServer(http.Dir("./static"))
	http.Handle("/", fs)

	http.HandleFunc("/upload", uploadHandler)

	log.Println("Listening on :2024...")
	http.ListenAndServe(":2024", nil)
}

Ensure to substitute LICENSE-KEY with your actual Dynamsoft Barcode Reader SDK license key.

Source Code

https://github.com/yushulx/goBarcodeQrSDK/tree/main/example/web