Continuation from here: http://justcodesnippets.durlut.ro/index.php/2019/12/07/node-js-image-delivery-microservice-challenge-refactoring-1/
First of all let’s address the issue of the synchronous calls to check for file existence.
const fileExists: boolean = fs.existsSync(fileAbsolutePath);
We need to make the call async. We can get some info from here: https://stackoverflow.com/questions/40593875/using-filesystem-in-node-js-with-async-await and here https://stackoverflow.com/questions/17699599/node-js-check-if-file-exists
We create a FileUtils class for reusability:
import fs from "fs"; import util from "util"; export class FileUtils { public static async createDirectory(directoryPath: string): Promise<boolean> { try { await this.fsMkDir(directoryPath); return true; } catch (err) { console.log(err); return false; } } public static async fileExists(fileAbsolutePath: string): Promise<boolean> { try { const result = await this.fsStat(fileAbsolutePath); return result.isFile(); } catch (err) { console.log(err); return false; } } public static async directoryExists(fileAbsolutePath: string): Promise<boolean> { try { const result = await this.fsStat(fileAbsolutePath); return result.isDirectory(); } catch (err) { console.log(err); return false; } } // https://stackoverflow.com/questions/40593875/using-filesystem-in-node-js-with-async-await // https://stackoverflow.com/questions/17699599/node-js-check-if-file-exists private static fsStat = util.promisify(fs.stat); private static fsMkDir = util.promisify(fs.mkdir); }
We create a FolderStructureUtils class to controll the new folder structure that will unfold. This will help us bring results faster. Each resolution has its own folder.
import fs from "fs"; import path from "path"; import { FileUtils } from "../utils/file-utils"; export class FolderStructureUtils { public static getOriginalImagesDirectoryPath(): string { return `${this.imagesFolder}original/`; } public static getOriginalImageAbsolutePath(imageName: string): string { const originalImagesDirectoryPath: string = this.getOriginalImagesDirectoryPath(); return `${originalImagesDirectoryPath}${imageName}`; } public static getResolutionImageDirectoryPath(imageResolution: string): string { return `${this.imagesFolder}${imageResolution}/`; } public static async resolutionImageDirectoryExists(imageResolution: string): Promise<boolean> { const directoryPath = this.getResolutionImageDirectoryPath(imageResolution); return await FileUtils.directoryExists(directoryPath); } public static async imageWithResolutionExists(imageName: string, imageResolution: string): Promise<boolean> { const filePath = this.getImageWithResolutionPath(imageName, imageResolution); return await FileUtils.fileExists(filePath); } public static getImageWithResolutionPath(imageName: string, imageResolution: string): string { const directoryPath = this.getResolutionImageDirectoryPath(imageResolution); const filePath = `${directoryPath}${imageName}`; return filePath; } public static async createImageResolutionDirectory(imageResolution: string): Promise<string> { const directoryPath = this.getResolutionImageDirectoryPath(imageResolution); const isDirCreated: boolean = await FileUtils.createDirectory(directoryPath); if (isDirCreated) { return directoryPath; } else { throw Error(`Directory ${directoryPath} could not be created`); } } private static imagesFolder: string = path.join(__dirname, `/../images/`); }
Right now, index.ts just looks if the file exists at the specified location. If not, the original file is located and a folder and file at certain resolution is created.
import express from "express"; 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 imageExistsPhysically: boolean = await FolderStructureUtils.imageWithResolutionExists(imageName, imageResolution); if (imageExistsPhysically) { 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 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}` }); } }); // start the Express server app.listen(port, () => { // tslint:disable-next-line:no-console console.log(`server started at http://localhost:${port}`); });