Dynamsoft's Web TWAIN SDK has long been a market leader in web-based document scanning, empowering numerous organizations to develop their own document management systems. The SDK consisted of two main components: a Dynamsoft service for scanner management and a JavaScript library for front-end development. Previously, this service could only be accessed via the JavaScript library. With the upcoming release, the Dynamsoft service will expose the core document scanning feature via REST API. This new feature enables developers to use various programming languages for document scanning tasks. Now, in addition to web-based applications, the SDK can also be used to create desktop and mobile document scanning apps, as well as server-side scanning services. In this article, I will guide you through the process of using the new REST API for document scanning in Node.js.
Prerequisites
-
Install Dynamsoft Service REST Preview for Windows.
At present, the REST API is exclusively available on Windows, but support for Linux (both x64 and ARM64) and macOS is coming soon.
Request a free trial license for Dynamsoft Service.
REST API Reference
By default, the REST API's host address is set to http://127.0.0.1:18622
. To modify this to a LAN IP address, navigate to http://127.0.0.1:18625/
in your web browser.
Method | Endpoint | Description | Parameters | Response |
---|---|---|---|---|
GET | /DWTAPI/Scanners |
Get a list of scanners | None |
200 OK with scanner list |
POST | /DWTAPI/ScanJobs |
Creates a scan job |
license , device , config
|
201 Created with job ID |
GET | /DWTAPI/ScanJobs/:id/NextDocument |
Retrieves a document image |
id : Job ID |
200 OK with image stream |
DELETE | /DWTAPI/ScanJobs/:id |
Deletes a scan job |
id : Job ID |
200 OK |
The parameters for the /DWTAPI/ScanJobs
endpoint are outlined in the Dynamic Web TWAIN documentation, and they control the scanner's behavior.
For instance, you can set the resolution to 200 DPI and the pixel type to color:
let parameters = {
license: "LICENSE-KEY",
device: device,
};
parameters.config = {
IfShowUI: false,
PixelType: 2, // color
Resolution: 200,
IfFeederEnabled: false,
IfDuplexEnabled: false,
};
Developing Node.js Functions to Call the REST API
Install Axios for sending HTTP requests directly from Node.js to RESTful APIs and retrieve their responses.
npm install axios
According to the REST API reference, we implement five functions: getDevices()
, scanDocument()
, deleteJob
, getImageFiles
and getImageStreams()
.
const axios = require('axios');
const fs = require('fs');
const path = require('path');
module.exports = {
getDevices: async function (host) {
return [];
},
scanDocument: async function (host, parameters) {
return '';
},
deleteJob: async function (host, jobId) {
},
getImageFiles: async function (host, jobId, directory) {
let images = [];
return images;
},
getImageStreams: async function (host, jobId) {
let streams = [];
return streams;
},
};
-
getDevices()
retrieves a list of scanners.
getDevices: async function (host) { devices = []; let url = host + '/DWTAPI/Scanners' try { let response = await axios.get(url) .catch(error => { console.log(error); }); if (response.status == 200 && response.data.length > 0) { console.log('\nAvailable scanners: ' + response.data.length); return response.data; } } catch (error) { console.log(error); } return []; },
-
scanDocument()
creates a scan job and returns its ID.
scanDocument: async function (host, parameters) { let url = host + '/DWTAPI/ScanJobs'; try { let response = await axios.post(url, parameters) .catch(error => { console.log('Error: ' + error); }); let jobId = response.data; if (response.status == 201) { return jobId; } else { console.log(response); } } catch (error) { console.log(error); } return ''; },
-
deleteJob()
deletes a scan job.
deleteJob: async function (host, jobId) { if (!jobId) return; let url = host + '/DWTAPI/ScanJobs/' + jobId; console.log('Delete job: ' + url); axios({ method: 'DELETE', url: url }) .then(response => { console.log('Deleted:', response.data); }) .catch(error => { }); },
-
getImageFiles()
retrieves the image files of a scan job.
getImageFiles: async function (host, jobId, directory) { let images = []; let url = host + '/DWTAPI/ScanJobs/' + jobId + '/NextDocument'; console.log('Start downloading images......'); while (true) { try { const response = await axios({ method: 'GET', url: url, responseType: 'stream', }); if (response.status == 200) { await new Promise((resolve, reject) => { const timestamp = Date.now(); const imagePath = path.join(directory, `image_${timestamp}.jpg`); const writer = fs.createWriteStream(imagePath); response.data.pipe(writer); writer.on('finish', () => { images.push(imagePath); console.log('Saved image to ' + imagePath + '\n'); resolve(); }); writer.on('error', (err) => { console.log(err); reject(err); }); }); } else { console.log(response); } } catch (error) { console.error('No more images.'); break; } } return images; },
-
getImageStreams()
retrieves the image streams of a scan job.
getImageStreams: async function (host, jobId) { let streams = []; let url = host + '/DWTAPI/ScanJobs/' + jobId + '/NextDocument'; console.log('Start downloading images......'); while (true) { try { const response = await axios({ method: 'GET', url: url, responseType: 'stream', }); if (response.status == 200) { streams.push(response.data); } else { console.log(response); } } catch (error) { console.error('No more images.'); break; } } return streams; },
Scanning Documents from the Command Line in Terminal
Let's create a app.js
file for scanning documents from the command line.
-
Import
docscan4nodejs
andreadline
. Thedocscan4nodejs
module is what we just implemented, andreadline
is used to read user input from the command line.
const docscan4nodejs = require("docscan4nodejs") const readline = require('readline');
-
Create a
readline.Interface
instance to read user input from the command line.
const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question(questions, function (answer) {});
-
Get all available scanners that compatible with TWAIN, SANE, ICA, WIA, and eSCL.
let devices = await docscan4nodejs.getDevices(host);
-
Select a scanner from the list for scanning documents. A valid license key is required.
let parameters = { license: "LICENSE-KEY", device: devices[index].device, }; parameters.config = { IfShowUI: false, PixelType: 2, //XferCount: 1, //PageSize: 1, Resolution: 200, IfFeederEnabled: false, IfDuplexEnabled: false, }; docscan4nodejs.scanDocument(host, parameters).then((jobId) => { if (jobId !== '') { console.log('job id: ' + jobId); (async () => { let images = await docscan4nodejs.getImageFiles(host, jobId, './'); for (let i = 0; i < images.length; i++) { console.log('Image ' + i + ': ' + images[i]); } await docscan4nodejs.deleteJob(jobId); })(); } });
-
Run the script in the terminal.
node app.js
Get all available scanners
Acquire a Document
Implementing Server-side Document Scanning for Web-Based Applications
A more advanced use case involves implementing server-side document scanning for web-based applications. The key benefit of this strategy is that it enables document scanning directly from a web browser, eliminating the need for additional software installations. For instance, if your office has a single document scanner and you wish to share it among multiple colleagues, a web-based application would allow them to initiate scans from their individual computers.
We use express
to create a web server and socket.io
to transmit data between the server and the client.
npm install express socket.io
Node.js Web Server
-
Initialize
express
andsocket.io
.
const express = require('express'); const path = require('path'); const fs = require('fs'); const app = express(); const http = require('http'); const server = http.createServer(app); const io = require('socket.io')(server); const docscan4nodejs = require("docscan4nodejs") const { PassThrough } = require('stream'); app.use(express.static('public')); app.use('/node_modules', express.static(__dirname + '/node_modules')); 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', async (message) => { }); }); // Start the server const port = process.env.PORT || 3000; server.listen(port, '0.0.0.0', () => { console.log(`Server running at http://0.0.0.0:${port}/`); });
-
As the
socket.io
connection is established, get the available scanners and send them to the client.
io.on('connection', (socket) => { ... docscan4nodejs.getDevices(host).then((scanners) => { socket.emit('message', JSON.stringify({ 'devices': scanners })); }); });
-
When receiving the scan event, initiate a document scan and transmit the resulting image stream to the web client.:
socket.on('message', async (message) => { let json = JSON.parse(message); if (json) { if (json['scan']) { console.log('device: ' + json['scan']); let parameters = { license: "LICENSE-KEY", device: json['scan'], }; parameters.config = { IfShowUI: false, PixelType: 2, //XferCount: 1, //PageSize: 1, Resolution: 200, IfFeederEnabled: false, IfDuplexEnabled: false, }; let jobId = await docscan4nodejs.scanDocument(host, parameters); if (jobId !== '') { console.log('job id: ' + jobId); let streams = await docscan4nodejs.getImageStreams(host, jobId); for (let i = 0; i < streams.length; i++) { await new Promise((resolve, reject) => { try { const passThrough = new PassThrough(); const chunks = []; streams[i].pipe(passThrough); passThrough.on('data', (chunk) => { chunks.push(chunk); }); passThrough.on('end', () => { const buffer = Buffer.concat(chunks); socket.emit('image', buffer); resolve(); }); } catch (error) { reject(error); } }); } } } } });
Web Client
-
Establish a
socket.io
connection with the server.
const socket = io(); var data = []; var devices = []; var selectSources = document.getElementById("sources"); socket.on('message', (message) => { }); socket.on('image', (buffer) => { });
-
Update the
<select>
element with the available scanners.
socket.on('message', (message) => { try { let json = JSON.parse(message); if (json) { if (json['devices']) { selectSources.options.length = 0; devices = json['devices']; for (let i = 0; i < devices.length; i++) { console.log('\nIndex: ' + i + ', Name: ' + devices[i]['name']); let option = document.createElement("option"); option.text = devices[i]['name']; option.value = i.toString(); selectSources.add(option); } } } } catch (error) { console.log(error) } });
-
Send a scan event to the server when the user clicks the
Scan
button.
<button onclick="acquireImage()">Scan Documents</button> function acquireImage() { if (devices.length > 0 && selectSources.selectedIndex >= 0) { socket.emit('message', JSON.stringify({ 'scan': devices[selectSources.selectedIndex]['device'] })); } }
-
Display the image stream in the
<img>
element.
socket.on('image', (buffer) => { // Convert the Buffer into a base64 string const base64Image = btoa( new Uint8Array(buffer) .reduce((data, byte) => data + String.fromCharCode(byte), '') ); // Set the image src to display the image let img = document.getElementById('document-image'); let url = `data:image/jpeg;base64,${base64Image}`; img.src = url; data.push(url); let option = document.createElement("option"); option.selected = true; option.text = url; option.value = url; let thumbnails = document.getElementById("thumb-box"); let newImage = document.createElement('img'); newImage.setAttribute('src', url); if (thumbnails != null) { thumbnails.appendChild(newImage); newImage.addEventListener('click', e => { if (e != null && e.target != null) { let target = e.target; img.src = target.src; } }); } });
Start the web server on your Windows machine, and then navigate to http://LAN-IP:18625/
using Safari on macOS.