How to Build REST Endpoints with Node.js and Dynamsoft Vision SDKs

Two months ago, we published a repository demonstrating how to build a RESTful service with .NET and Dynamsoft Vision SDKs. In this tutorial, the server-side programming language is changed to Node.js. We will go through the steps of creating REST endpoints for scanning documents, decoding barcodes, detecting MRZ, and rectifying documents. The client-side code is reused from the previous open-source project.

Prerequisites

  • Dynamsoft Service: This is a background service used for communicating with document scanners. It can be installed on both Windows and Linux. You can download it from the following links:
  • License Key: Dynamic Web TWAIN, Dynamsoft Barcode Reader, Dynamsoft Label Recognizer, and Dynamsoft Document Normalizer require their own license key. Free trial licenses are available here.

Required NPM Packages

{
  "dependencies": {
    "barcode4nodejs": "^9.6.29",
    "body-parser": "^1.20.2",
    "cors": "^2.8.5",
    "docrectifier4nodejs": "1.0.0",
    "docscan4nodejs": "^1.0.1",
    "express": "^4.18.2",
    "mrz4nodejs": "1.0.2",
    "multer": "^1.4.5-lts.1",
    "sharp": "^0.32.6"
  }
}
  • barcode4nodejs: A Node.js C++ addon for reading 1D and 2D barcodes with the Dynamsoft Barcode Reader.
  • body-parser: A Node.js body parsing middleware.
  • cors: A Node.js package for providing a middleware that can be used to enable CORS with various options.
  • docrectifier4nodejs: A Node.js C++ addon for rectifying documents with the Dynamsoft Document Normalizer.
  • docscan4nodejs: A JavaScript library for acquiring documents from scanners with Dynamic Web TWAIN.
  • express: A Node.js web application framework.
  • mrz4nodejs: A Node.js C++ addon for detecting MRZ with the Dynamsoft Label Recognizer.
  • multer: A Node.js middleware for handling multipart/form-data, which is primarily used for uploading files.
  • sharp: A Node.js image processing library.

REST Endpoints Defined in .NET C#

In our previous .NET C# project, the REST endpoints were defined as follows:

Method Endpoint Description
GET /dynamsoft/product Retrieve the names of all Dynamsoft Vision SDKs.
GET /dynamsoft/dwt/getdevices Retrieve the device names of connected scanners, including TWAIN, SANE, eSCL, and WIA.
POST /dynamsoft/dwt/ScanDocument Acquire images from a scanner and return the image data.
POST /dynamsoft/dbr/DecodeBarcode Upload an image for barcode decoding and return the decoding results.
POST /dynamsoft/dlr/DetectMrz Upload an image for MRZ detection and return the detection results.
POST /dynamsoft/ddn/rectifyDocument Upload an image for document rectification and return the rectified image.

REST Endpoints Implementation in Node.js

Implementing REST endpoints in Node.js is much easier than in .NET C#. Let’s begin with the web server configuration.

const express = require('express');
const cors = require('cors');
const app = express();
const server = http.createServer(app);

app.use(cors());
app.use(express.static('public'));
app.use('/uploads', express.static('uploads'));

const port = process.env.PORT || 3000;

server.listen(port, '0.0.0.0', () => {
    host = server.address().address;
    console.log(`Server running at http://0.0.0.0:${port}/`);
});
  • cors: Enable CORS with the default options.
  • express.static: Serve static files from the public folder, which contains the client-side code. Use the uploads folder for storing uploaded images.

Then initialize the Dynamsoft Vision SDKs with valid license keys.

const barcode4nodejs = require("barcode4nodejs")
const docscan4nodejs = require("docscan4nodejs");
const docrectifier4nodejs = require("docrectifier4nodejs");
const mrz4nodejs = require('mrz4nodejs');

barcode4nodejs.initLicense(
    "LICENSE-KEY",
);

mrz4nodejs.initLicense(
    "LICENSE-KEY",
);
let mrzScanner = new mrz4nodejs();
let ret = mrzScanner.loadModel(path.dirname(require.resolve('mrz4nodejs')));

docrectifier4nodejs.initLicense(
    "LICENSE-KEY",
);
let docRectifier = new docrectifier4nodejs();
docRectifier.setParameters(docrectifier4nodejs.Template.color);
  • loadModel: Load the MRZ model that is located in the node_modules folder.
  • setParameters: Set the document rectification template to color option.

Configure multer to save uploaded images in a designated folder.

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

Based on the endpoint table, the corresponding Node.js code can be written as follows:

  • /dynamsoft/product.

      app.get('/dynamsoft/product', (req, res) => {
          res.send(JSON.stringify(["Dynamic Web TWAIN",
              "Dynamsoft Barcode Reader",
              "Dynamsoft Label Recognizer",
              "Dynamsoft Document Normalizer",]));
      });
    
  • /dynamsoft/dwt/getdevices.

      app.get('/dynamsoft/dwt/getdevices', (req, res) => {
    
          docscan4nodejs.getDevices(dynamsoftService).then((scanners) => {
              res.send(scanners);
          });
      });
    

    The scanners variable is an array of objects, with each object containing device, name, and type properties. For example:

      [
          {
              device: '{\n' +
                '\t"deviceInfo" : \n' +
                '\t{\n' +
                '\t\t"UUID" : "f8262726-dc64-5cf1-89ab-5e257f2d0f3a",\n' +
                '\t\t"adminurl" : "http://HP6C02E0BCF77F.local.",\n' +
                '\t\t"cs" : "binary,color,grayscale",\n' +
                '\t\t"duplex" : "F",\n' +
                '\t\t"is" : "platen,adf",\n' +
                '\t\t"mopria-certified-scan" : "1.2",\n' +
                '\t\t"name" : "HP LaserJet Pro M329 [BCF77F]",\n' +
                '\t\t"pdl" : "application/octet-stream,application/pdf,image/jpeg",\n' +
                '\t\t"representation" : "images/printer.png",\n' +
                '\t\t"rs" : "eSCL",\n' +
                '\t\t"service_type" : 3,\n' +
                '\t\t"txtvers" : "1",\n' +
                '\t\t"ty" : "HP LaserJet Pro M329",\n' +
                '\t\t"vers" : "2.63"\n' +
                '\t},\n' +
                '\t"deviceType" : 512,\n' +
                '\t"name" : "SFAgTGFzZXJKZXQgUHJvIE0zMjk="\n' +
                '}\n',
              name: 'HP LaserJet Pro M329',
              type: 512
        }
      ]
    
  • /dynamsoft/dwt/ScanDocument.

      app.post('/dynamsoft/dwt/ScanDocument', async (req, res) => {
          const json = req.body;
        
          let parameters = {
              license: "LICENSE-KEY",
              device: json['scan'],
          };
        
          parameters.config = {
              IfShowUI: false,
              PixelType: 2,
              Resolution: 200,
              IfFeederEnabled: false,
              IfDuplexEnabled: false,
          };
        
          try {
              let jobId = await docscan4nodejs.scanDocument(dynamsoftService, parameters);
              let filename = await docscan4nodejs.getImageFile(dynamsoftService, jobId, './uploads');
              console.log('Scanned file: ' + filename);
              res.send(JSON.stringify({
                  'image': 'uploads/' + filename
              }));
          }
          catch (err) {
              console.error(err);
              return res.status(500).send('An error occurred while processing the image.');
          }
      });
    

    To scan documents, a valid license key is required. The scanDocument function generates a jobId, which is used to retrieve the scanned image file. The getImageFile function then saves this image file to the local disk and returns its file name.

  • /dynamsoft/dbr/DecodeBarcode.

      app.post('/dynamsoft/dbr/DecodeBarcode', upload.single('image'), async (req, res) => {
          const file = req.file;
        
          if (!file) {
              return res.status(400).send('No file uploaded.');
          }
        
          try {
              let result = await barcode4nodejs.decodeFileAsync(file.path, barcode4nodejs.barcodeTypes);
              console.log(result);
              res.status(200).send(result);
          }
          catch (err) {
              console.error(err);
              return res.status(500).send('An error occurred while processing the image.');
          }
      });
    

    The decodeFileAsync function returns an array of objects, where each object includes properties for format, value, coordinates, and elapsed time. For example:

      [
          {
              format: 'QR_CODE',
              value: 'JOHN DOE\nTR456  43E\nfrom Sydney to Vilnius\n23:40',
              x1: 991,
              y1: 309,
              x2: 1125,
              y2: 309,
              x3: 1124,
              y3: 440,
              x4: 991,
              y4: 440,
              page: 0,
              time: 31
          }
      ]
    
  • /dynamsoft/dlr/DetectMrz.

      app.post('/dynamsoft/dlr/DetectMrz', upload.single('image'), async (req, res) => {
          const file = req.file;
        
          if (!file) {
              return res.status(400).send('No file uploaded.');
          }
        
          try {
              let result = await mrzScanner.decodeFileAsync(file.path);
              console.log(result);
              let output = "";
              if (result.length == 2) {
                  output = mrzScanner.parseTwoLines(result[0].text, result[1].text);
              }
              else if (result.length == 3) {
                  output = mrzScanner.parseThreeLines(result[0].text, result[1].text, result[2].text);
              }
              console.log(output);
              res.status(200).send(output);
          }
          catch (err) {
              console.error(err);
              return res.status(500).send('An error occurred while processing the image.');
          }
      });
    

    The decodeFileAsync function returns OCR results, typically comprising two or three lines of text. By parsing these strings, we can extract MRZ (Machine Readable Zone) information, such as:

      {
        type: 'PASSPORT (TD-3)',
        nationality: 'CAN',
        surname: 'AMAN',
        givenname: 'RITA TANIA',
        passportnumber: 'ERE82721 ',
        issuecountry: 'CAN',
        birth: '1984-12-07',
        gender: 'M',
        expiry: '2024-05-25'
      }
    
  • /dynamsoft/ddn/rectifyDocument.

      app.post('/dynamsoft/ddn/rectifyDocument', upload.single('image'), async (req, res) => {
          const file = req.file;
        
          if (!file) {
              return res.status(400).send('No file uploaded.');
          }
        
          try {
              let results = await docRectifier.detectFileAsync(file.path);
              let result = results[0];
              result = await docRectifier.normalizeFileAsync(file.path, result['x1'], result['y1'], result['x2'], result['y2'], result['x3'], result['y3'], result['x4'], result['y4']);
              let data = result['data']
              let width = result['width']
              let height = result['height']
              for (let i = 0; i < data.length; i += 4) {
                  const red = data[i];
                  const blue = data[i + 2];
                  data[i] = blue;
                  data[i + 2] = red;
              }
        
              const timestamp = Date.now();
        
              sharp(data, {
                  raw: {
                      width: width,
                      height: height,
                      channels: 4
                  }
              }).toFile('uploads/' + timestamp + '.jpeg', (err, info) => {
                  if (err) {
                      console.error('Error:', err);
                  } else {
                      res.send(JSON.stringify({
                          'image': 'uploads/' + timestamp + '.jpeg'
                      }));
                  }
              });
          }
          catch (err) {
              console.error(err);
              return res.status(500).send('An error occurred while processing the image.');
          }
      });
    

    The detectFileAsync function yields an array of objects, where each object includes the properties x1, y1, x2, y2, x3, y3, x4, and y4. These properties are coordinates used by the normalizeFileAsync function for rectifying the document. The normalizeFileAsync function then returns an image object containing data, width, and height properties. Here, data is an array representing the image pixels, while width and height specify the image’s dimensions. The sharp library encodes and saves the image to the local disk.

Test the REST Endpoints in Web Browser

Open the index.html file in a web browser. The web page looks like:

Source Code

https://github.com/yushulx/dynamsoft-sdk-webapi-restful-service/tree/main/node