How to Build REST Endpoints with Node.js and Dynamsoft Vision SDKs
Tags: node, javascript, programming, restapi
Published: 2023-12-05 01:05:18

Two months ago, we published a [repository](https://github.com/yushulx/dynamsoft-sdk-dotnet-webapi-restful-service) 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.


<video src="https://github.com/yushulx/dynamsoft-sdk-nodejs-REST-API/assets/2202306/d85401ca-d17c-4af4-b8d0-57d645dbbd28" controls="controls" muted="muted" style="max-height:640px; min-height: 200px; max-width: 100%;"></video>

## 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:
    - [Windows](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.msi)
    - [Linux](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.deb)
- **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](https://www.dynamsoft.com/customer/license/trialLicense).

## Required NPM Packages

```json
{
  "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.

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

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

```js
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`.
    
    ```js
    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`.
    
    ```js
    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:
    
    ```json
    [
        {
            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`.
    
    ```js
    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`.
    
    ```js
    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:
    
    ```json
    [
        {
            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`.
    
    ```js
    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:

    ```json
    {
      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`.
    
    ```js
    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:

<img src="https://www.dynamsoft.com/codepool/img/2023/12/nodejs-rest-endpoint-dynamsoft-sdk.png" width="600">


## Source Code
[https://github.com/yushulx/dynamsoft-sdk-nodejs-REST-API](https://github.com/yushulx/dynamsoft-sdk-nodejs-REST-API)