Build a JavaScript Mobile Document Scanner with Real-Time Web Upload

Document capture functionality is an essential aspect of any document management system, allowing users to capture digital images of physical documents through a variety of means, including scanners and mobile cameras. Dynamic Web TWAIN helps users easily integrate document capture functionality into their web-based document management system, enabling them to streamline their workflows and improve efficiency. Within this article, a collaboration scenario is presented where documents can be captured via a mobile camera and uploaded as images to a remote document management system built with Dynamic Web TWAIN SDK.

What you’ll build: A web-based document management system where mobile users capture documents with their phone camera and instantly upload them—via QR code pairing and Socket.IO—to a Dynamic Web TWAIN scanning dashboard running in the browser.

Key Takeaways

  • Dynamic Web TWAIN combined with a Node.js backend lets you build a full document scanning and management web app with just a few npm packages.
  • Socket.IO enables real-time push of uploaded document filenames from the server to the browser client, so scanned images appear instantly without polling.
  • A QR code rendered in the browser encodes the upload URL plus the socket ID, allowing any mobile device on the same network to pair and upload documents.
  • SQLite stores uploaded image metadata (filename, MIME type, size), keeping the architecture lightweight and self-contained for prototyping or small teams.

Common Developer Questions

  • How do I build a web-based document scanner that accepts uploads from a mobile camera in real time?
  • How do I use Socket.IO to push uploaded file notifications to a browser client?
  • How do I pair a mobile phone to a web app using a QR code for document capture?

Why Mobile Document Capture Matters

Efficient and streamlined document management systems are crucial for organizations to keep up with the fast-paced business world of today. One crucial aspect of such systems is the ability to capture and upload documents quickly and easily. Mobile camera technology offers a more convenient and flexible solution compared to traditional scanners. This scenario presents a common situation where a user captures a document and it is instantly viewable and processable in the organization’s document management system.

To achieve this, several Node.js packages can be utilized, including:

  • express - a web application framework for Node.js
  • multer - a Node.js middleware for handling multipart/form-data, which is primarily used for uploading files
  • sqlite3 - a Node.js package that provides an asynchronous API for accessing a SQLite database
  • socket.io - a Node.js package that enables real-time, bidirectional and event-based communication between the browser and the server
  • qrcode - a Node.js package that generates QR codes
  • dwt - Dynamic Web TWAIN SDK

Prerequisites

  • Node.js installed on your machine
  • A modern web browser (Chrome, Edge, or Firefox)
  • A mobile device on the same local network for camera capture testing
  • Get a 30-day free trial license for Dynamic Web TWAIN

Step 1: Create a Web Document Scanning Application

Dynamic Web TWAIN enables developers to easily integrate document scanning functionality into their web applications with a few lines of JavaScript code. Here are the basic steps involved:

  1. Create a new Node.js project and install the required packages.
     npm install express dwt
    
  2. Set up a web server using Express.
     const express = require('express');
     const app = express();
     const port = 3000;
     app.listen(port, '0.0.0.0', () => {
         console.log(`Server running at http://0.0.0.0:${port}/`);
     });
    

    The ip variable is set to 0.0.0.0, which tells Express to listen to all available IP addresses. You can also set the port variable to the desired port number.

  3. Create a public folder for storing static files, such as HTML, CSS, and JavaScript files. Copy index.html to the public folder.

    This file demonstrates how to use Dynamic Web TWAIN API to create a web document scanning application. You need to apply for a trial license (see Prerequisites above) and replace the license key in index.html.

     Dynamsoft.DWT.ProductKey = "LICENSE-KEY";
    

    Since the resource files of Dynamic Web TWAIN are located in the node_modules/dwt folder, you need to configure the node_modules folder as a static folder. Add the following code to the app.js file:

     app.use(express.static('public'));
     app.use('/node_modules', express.static(__dirname + '/node_modules'));
    
  4. A simple web document scanning application is now ready. Run the following command to start the server:
     node app.js
    

    Dynamic Web TWAIN hello world

Step 2: Add Real-Time Communication with Socket.IO

Socket.IO is a JavaScript library that enables real-time, bidirectional communication between web clients and servers. It uses a WebSocket protocol under the hood. In this scenario, Socket.IO is used to push the file name of the uploaded documents to the web client for display.

Here are the steps to integrate Socket.IO into the web client and server:

  1. Install the socket.io package:
     npm install socket.io
    
  2. Modify the app.js file to create a Socket.IO server:
     const app = express();
     const http = require('http');
     const server = http.createServer(app);
     const port = process.env.PORT || 3000;
    
     const connections = new Map();
     io.on('connection', (socket) => {
         connections.set(socket.id, socket);
         console.log(socket.id + ' connected');
    
         socket.on('disconnect', () => {
             console.log(socket.id + ' disconnected');
             connections.delete(socket.id);
         });
    
         socket.on('message', (message) => {
             console.log('Message received: ' + message);
             socket.emit('message', 'from server');
         });
     });
    
     server.listen(port, '0.0.0.0', () => {
         console.log(`Server running at http://0.0.0.0:${port}/`);
     });
    
  3. Add the following code to the index.html file to create a Socket.IO client:
     <script src="/node_modules/socket.io/client-dist/socket.io.js"></script>
     const socket = io();
     socket.on('message', (data) => {
     });
    

Step 3: Generate a QR Code to Connect Mobile Devices

QR code is a two-dimensional barcode that can be scanned using a mobile camera. It is a convenient way to share information between devices. In this scenario, a QR code is generated to display the URL of the web document scanning application. This allows users to scan the QR code using their mobile camera and open the web document scanning application on their mobile devices.

To generate a QR code in JavaScript:

  1. Include the qrcode package in the index.html file:
     <script src="/node_modules/qrcode/build/qrcode.js"></script>
    
  2. Create a canvas element used for rendering the QR code:
     <div class="qr-popup">
         <div class="qr-box">
             <span class="qr-close">&times;</span>
             <canvas id="qr-canvas"></canvas>
         </div>
     </div>
    
  3. Encode the URL of the document capture and upload page as a QR code and display it in the canvas element:
     const generateBtn = document.getElementById('generate');
     const qrPopup = document.querySelector(".qr-popup");
     const qrClose = document.querySelector(".qr-close");
    
     generateBtn.addEventListener('click', () => {
         let url = '';
         if (window.location.protocol === 'https:') {
             url = 'https://' + window.location.host + "/mobile.html?socketid=" + socket.id;
         } else {
             url = 'http://' + window.location.host + "/mobile.html?socketid=" + socket.id;
         }
         console.log(url);
         QRCode.toCanvas(document.getElementById('qr-canvas'), url, function (error) {
             if (error) console.error(error)
         })
    
         qrPopup.style.display = "block";
    
         qrClose.onclick = function () {
             qrPopup.style.display = "none";
         };
     });
    
  4. Read the QR code using a mobile camera or a QR code reader app.

    JavaScript QR code

Step 4: Upload Images from Mobile and Save to SQLite Database

Install multer and sqlite3 packages:

npm install multer sqlite3

On the client side

An input element with type file can be used to capture images from a mobile camera.

<form id='myForm' action="javascript:void(0)" method="POST" enctype="multipart/form-data">
    <input type="file" name="image" accept="image/*">
    <button type="submit">Upload</button>
</form>

Using fetch API can upload files to a web server.

const form = document.getElementById('myForm');

form.addEventListener('submit', (event) => {
    event.preventDefault();

    const formData = new FormData(form);
    let id = decodeURIComponent(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURIComponent("socketid").replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1"));
    formData.append('id', id);

    fetch('/upload', {
        method: 'POST',
        body: formData
    })
        .then(function (response) {
            console.log(response);
            showMessage("Your file has been uploaded.");
        })
        .catch(function (error) {
            console.error(error);
            showMessage(error, 3000);
        });
});

On the server side

The multer package is used to save the uploaded images on the server-side.

const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

app.use('/uploads', express.static(__dirname + '/uploads'));

app.post('/upload', upload.single('image'), (req, res) => {
    const id = req.body.id;
    const { filename, mimetype, size } = req.file;
});

By default, Multer assigns a random name to the uploaded file to avoid naming collisions and security vulnerabilities.

After storing an image file on the disk, you would typically add its filename and MIME type to a database, which would allow you to retrieve the image later on.

const sqlite3 = require('sqlite3').verbose();

app.post('/upload', upload.single('image'), (req, res) => {
    const id = req.body.id;
    const { filename, mimetype, size } = req.file;
    db.run('INSERT INTO images (filename, mimetype, size) VALUES (?, ?, ?)',
        [filename, mimetype, size],
        (err) => {
            if (err) {
                console.error(err);
                return res.sendStatus(500);
            }
            connections.get(id).emit('message', JSON.stringify({ 'filename': filename}));
            res.sendStatus(200);
        });
});

Upon receiving a request for an image, the web server will query the associated database to retrieve the file name and MIME type. With this information, the server will utilize the fs package to read the image file and send it to the client.

const fs = require('fs');
app.get('/image/:id', (req, res) => {
    const id = req.params.id;
    const imagePath = path.join(__dirname, 'uploads', id);

    db.all('SELECT mimetype FROM images WHERE filename = ?', [id], (err, rows) => {
        if (err) {
            console.error(err);
            return res.sendStatus(500);
        }

        if (rows.length > 0) {
            let mimetype = rows[0].mimetype;

            fs.readFile(imagePath, (err, data) => {
                if (err) {
                    res.status(404).send('Image not found');
                } else {
                    res.setHeader('Content-Type', mimetype);
                    res.send(data);
                }
            });
        }
        else {
            res.sendStatus(404);
        }
    });

});

document mobile capture

Common Issues and Edge Cases

  • Mobile device cannot reach the server. The phone and the desktop must be on the same local network. If you are behind a corporate firewall or using separate Wi-Fi networks, the QR code URL will not resolve. Use ipconfig (Windows) or ifconfig (macOS/Linux) to confirm the server’s LAN IP, and ensure port 3000 is not blocked.
  • Uploaded images appear corrupted or have no extension. Multer stores files with a random filename and no extension by default. When serving them back, always read the MIME type from the database and set the Content-Type header accordingly, as shown in the /image/:id route above.
  • Socket.IO connection drops after the QR code is scanned. If the mobile page loads but the upload notification never arrives on the desktop, verify that the socketid query parameter was correctly encoded in the QR code URL. A mismatch means the server cannot route the message event to the right desktop client.

Source Code

https://github.com/yushulx/web-twain-document-scan-management/tree/main/examples/mobile_camera