Developing a Web Application for Reading Multiple Barcodes with Go and HTML5
Published: 2024-03-26 03:05:53

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
- [Go](https://go.dev/dl/)
- [Dynamsoft Barcode Reader v9.x trial license](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr).

## 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.

```html
<!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](https://www.dynamsoft.com/codepool/img/2024/03/go-web-server-barcode-reader.png)

- 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.

```javascript
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.
    
    ```javascript
    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.
    
    ```javascript
    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.

    ```javascript
    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`.

```javascript
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.

```javascript
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.

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

```

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

```go
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.

```go
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](https://github.com/yushulx/goBarcodeQrSDK/tree/main/example/web)