Node JS Image Delivery Microservice Challenge [Mongo DB & Stats page]

Continuation from http://justcodesnippets.durlut.ro/index.php/2019/12/07/node-js-image-delivery-microservice-challenge-folder-structure/

We create a cluster on https://cloud.mongodb.com/ for development purposes.

We install mongodb driver npm install mongodb –save

We create DbClient class that will hold DB operations

import mongodb from "mongodb";

export class DbClient {

    public incrementValue(imageFullName: string, incrementTimesServed: boolean, incrementTimesResized: boolean, incrementTimesDirectlyServed: boolean) {
        const client = this.getClient();
        client.connect((err) => {

            const imagesCollection = this.getImagesCollection(client);
            // https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/
            imagesCollection.findOneAndUpdate(
                { imageName: imageFullName }
                , {
                    $inc: {
                        timesServed: incrementTimesServed ? 1 : 0
                        , timesResized: incrementTimesResized ? 1 : 0
                        , timesDirectlyServed: incrementTimesDirectlyServed ? 1 : 0
                    }
                },
                { upsert: true }
            );

            client.close();
        });
    }

    public getImagesCollectionHtml(): Promise<string> {
        // https://medium.com/thecodinghype/https-medium-com-thecodinghype-reading-from-mongodb-database-using-express-js-and-node-js-250ef8b9282a
        // https://stackoverflow.com/questions/35246713/node-js-mongo-find-and-return-data
        const client: mongodb.MongoClient = this.getClient();
        return client.connect().then((connectedClient) => {
            return this.getImagesCollection(connectedClient);
        }).then((imagesCollection) => {
            return imagesCollection.find().toArray();
        }).then(async (images) => {
            // write HTML output
            let output: string = "<html><header><title>Images</title></header><body>";
            output += "<h1>List retrieved from DB</h1>";
            output += '<table border="1">';
            output += `<tr> <td><b>Name</b></td><td><b>Nr of served images</b></td><td><b>Nr of times resized</b></td><td><b>Nr of times directly served</b></td></tr>`;

            // process list
            images.forEach((image) => {
                output += `<tr><td>${image.imageName}</td><td>${image.timesServed}</td><td>${image.timesResized}</td><td>${image.timesDirectlyServed}</td></tr>`;
            });

            // write HTML output (ending)
            output += "</table></body></html>";
            console.log(output);
            await client.close();
            return output;
        });
    }

    private getImageStatsDb(client: mongodb.MongoClient): mongodb.Db {
        return client.db("imageStats");
    }

    private getImagesCollection(client: mongodb.MongoClient): mongodb.Collection<any> {
         return this.getImageStatsDb(client).collection("images");
    }

    private getClient(): mongodb.MongoClient {
        const uri = "mongodb+srv://admin:admin@cluster0-pyydy.mongodb.net/test?retryWrites=true&w=majority";
        const client = new mongodb.MongoClient(uri, { useNewUrlParser: true });
        return client;
    }

}

 

As the images are being served we increment in the database the images values:

In the end, the index.ts file holds 1 more route and the images route also makes calls to the database.

import express from "express";
import { DbClient } from "./db/db-client";
import { RequestImageValidatorService } from "./services/request-image-validator-service";
import { ServedImageService } from "./services/served-image-service";
import { FolderStructureUtils } from "./utils/folder-structure-utils";
const app = express();
const port = 8080; // default port to listen

app.use(express.static("images")); // https://expressjs.com/en/starter/static-files.html

// define a route handler for the default home page
app.get("/", (req, res) => {
    res.send("Hello world!");
});

// define a route handler for the image processing
app.get("/image/:imageName/:imageResolution", async (req, res, next) => {
    // res.send(`image named ${req.params.imageName} and resolution ${req.params.imageResolution}`);

    const imageName = req.params.imageName;
    const imageResolution = req.params.imageResolution;
    console.log(`requested image named ${imageName} and resolution ${imageResolution}`);
    const imageValidatorService: RequestImageValidatorService = new RequestImageValidatorService(imageName, imageResolution);
    if (!imageValidatorService.validateImage()) {
        res.status(404).send({ errors: imageValidatorService.errors });
    }

    const dbClient: DbClient = new DbClient();
    const incrementTimesServed: boolean = true;
    let incrementTimesResized: boolean = false;
    let incrementTimesDirectlyServed: boolean = false;

    const imageExistsPhysically: boolean = await FolderStructureUtils.imageWithResolutionExists(imageName, imageResolution);
    if (imageExistsPhysically) {

        incrementTimesDirectlyServed = true;
        dbClient.incrementValue(imageName, incrementTimesServed, incrementTimesResized, incrementTimesDirectlyServed);

        const imagePath = FolderStructureUtils.getImageWithResolutionPath(imageName, imageResolution);
        res.status(200).sendFile(imagePath);
        return;
    }

    const serverImageService: ServedImageService = new ServedImageService();
    const originalServedImage = await serverImageService.getOriginalServedImage(imageName); // https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
    if (originalServedImage.existsOnFileSystem) {// if image exists we can proceed to try and serve the resized image

        incrementTimesResized = true;
        dbClient.incrementValue(imageName, incrementTimesServed, incrementTimesResized, incrementTimesDirectlyServed);

        const resizedImage = await serverImageService.getResizedServedImage(originalServedImage, imageResolution);
        res.status(200).sendFile(resizedImage.absolutePath);
    } else {
        res.status(404).send({ error: `File ${imageName} does not exist on file system at ${originalServedImage.absolutePath}` });
    }

});

// define a route handler for the default home page
app.get("/stats", async (req, res) => {
    const dbClient: DbClient = new DbClient();
    const html = await dbClient.getImagesCollectionHtml();
    res.status(200).send(html);
});

// start the Express server
app.listen(port, () => {
    // tslint:disable-next-line:no-console
    console.log(`server started at http://localhost:${port}`);
});

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *