Node.js: resize images as thumbnails on the fly in ExpressJS

Node.js: resize images as thumbnails on the fly in ExpressJS

We can create image thumbnails on the fly with ExpressJS and the gm module.

We can create image thumbnails on the fly with ExpressJS and the gm module.

To get started, you need first to install the GraphicsMagick and ImageMagick libraries. If you're working on a server, you can use the package manager of your distro (the packages are imagemagick and graphicsmagick) to install such libraries. On macOS, you can use Homebrew or MacPorts (package names are the same).

Done this, we need to install the gm module:


npm install gm --save

We're going to create image thumbnails with the following criteria:

  1. JPEG and PNG images only: other types of images are either not suitable for the web (e.g. TIFF or BMP) or already served in small dimensions (e.g. GIF).
  2. Image files must be saved in a specific directory under the public assets directory. This is for security reasons.
  3. We'll use streams to improve performance.

Now we can write the following code:


'use strict';

const path = require('path');
const ABSPATH = path.dirname(process.mainModule.filename);
const gm = require('gm');
const fs = require('fs');

const exists = (path) => {
    try {
        return fs.statSync(path).isFile();
    } catch (e) {
        return false;
    }
};

const getFileExtension = (filename) => {
    return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2);
};

class Media {
    constructor(path) {
        this.src = path;
    }

    isValidMedia(src) {
        return /\.(jpe?g|png)$/.test(src);
    }

    isValidBaseDir(src) {
        return /^\/public\/images/.test(src);
    }

    thumb(request, response) {
        let image = ABSPATH +  this.src;

        if(this.isValidBaseDir(this.src) && this.isValidMedia(this.src) && exists(image)) {

            let width = (request.query.w && /^\d+$/.test(request.query.w)) ? request.query.w : '150';
            let height = (request.query.h && /^\d+$/.test(request.query.h)) ? request.query.h : '150';
            let extension = getFileExtension(this.src);
            let mime = (extension === 'jpeg' || extension === 'jpg') ? 'jpeg' : 'png';

            response.type(mime);

            gm(image).resize(width, height).stream().pipe(response);
        } else {
            response.sendStatus(404);
        }    
    }
}

module.exports = Media;

In ExpressJS we only need to create a specific route:


'use strict';

const app = require('express')();
const Media = require('./classes/Media');

app.get('/thumb', (req, res) => {
    if(req.query.src) {
       let image = new Media(req.query.src);
       image.thumb(req, res);
    } else {
        res.sendStatus(403);
    }
});

app.listen(8080);

Now you can try it out using a link like http://localhost:8080/thumb/?src=/public/images/image.jpg&w=640&h=480.