Creating Multer storage engine

Viewed 2441

I'm setting image uploads with Express and Multer. My router handler will receive a formData with multiple files: I want to process them to multiple resolutions and save them in folders with the name of its resolution:

media
├── w350
│   ├── image1.jpg
│   └── image2.jpg
├── w600
│   ├── image1.jpg
│   └── image2.jpg
└── w1200
    ├── image1.jpg
    └── image2.jpg

To process the images before in the middleware I have to use a custom storage engine, so I started with this engine to write my own one: https://github.com/gladchinda/advanced-multer-node-sourcecode/blob/master/helpers/AvatarStorage.js

My implementation is this.

var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var Jimp = require('jimp');
var mkdirp = require('mkdirp');
var concat = require('concat-stream');
var streamifier = require('streamifier');

// create a multer storage engine
var CustomStorage = function(options) {
    function CustomStorage() {
        return; // Should be empty
    }

    CustomStorage.prototype._createOutputStream = function(filepath, cb, options) {
        var output = fs.createWriteStream(filepath);
        output.on('error', cb);
        output.on('finish', function() {
            cb(null, {
                destination: options.uploadPath,
                baseUrl: options.uploadPath,
                filename: path.basename(filepath),
                storage: options.storage,
            });
        });
        return output;
    };

    // this processes the Jimp image buffer
    CustomStorage.prototype._processImage = function(image, cb, options) {
        var self = this;
        var batch = [];
        var mime = Jimp.MIME_PNG;
        var clone = image.clone();
        // Set image resolutions
        options.sizes.map((item) => {
            var outputStream;
            var image = null;
            var filepath = options.fileName.split('.');

            options.uploadPath = path.join(options.folder, 'w' + item.width);
            // Create path if not exist
            if (options.storage == 'local') {
                !fs.existsSync(options.uploadPath) && mkdirp.sync(options.uploadPath);
            }
            filepath = path.join(options.uploadPath, options.fileName);
            outputStream = self._createOutputStream(filepath, cb, options);
            image = clone.clone().resize(item.width, item.height);
            var image = {
                stream: outputStream,
                image: image,
            };
            batch.push(image);
        });

        batch.forEach((item) => {
            item.image.getBuffer(mime, function(err, buffer) {
                if (options.storage == 'local') {
                    streamifier.createReadStream(buffer).pipe(item.stream);
                }
            });
        });
    };

    CustomStorage.prototype._handleFile = function(req, file, cb) {
        let that = this;
        let options = {
            sizes: [
                {
                    width: 300,
                    height: 150,
                },
                {
                    width: 600,
                    height: 300,
                },
                {
                    width: 1200,
                    height: 600,
                },
            ],
            storage: 'local',
            output: 'jpg',
            fileName: file.originalname,
            fieldName: file.fieldname,
            folder: 'media/images'
        };


        var fileManipulate = concat(function(imageData) {
            Jimp.read(imageData)
                .then(function(image) {
                    that._processImage(image, cb, options);
                })
                .catch(cb);
        });
        file.stream.pipe(fileManipulate);
    };

    return new CustomStorage(options);
};

module.exports = CustomStorage;

It doesn't require any options, so in your router you can call it with:

import CustomStorage from './CustomStorage';

var storage = CustomStorage();
var upload = multer({ storage: storage });

router.put('/:id', upload.any(), function(req, res, next) {
    console.log('req.files: ', JSON.stringify(req.files, undefined, 2));
});

When uploading two images this should return something like:

req.files:  [
  {
    "originalname": "fantastic-photo.jpg",
    "encoding": "7bit",
    "mimetype": "image/jpeg",
    "destination": "media/images/Projects/w1200",
    "baseUrl": "media/images/Projects/w1200",
    "filename": "374827017056551892885227324803010320001537184436993.jpg",
    "storage": "local",
    "fieldname": "projects_img"
  },
  {
    "originalname": "fantastic-photo.jpg",
    "encoding": "7bit",
    "mimetype": "image/jpeg",
    "destination": "media/images/Projects/w1200",
    "baseUrl": "media/images/Projects/w1200",
    "filename": "374827017056551892885227324803010320001537184436993.jpg",
    "storage": "local",
    "fieldname": "projects_img"  
    }
]

But instead of that it returns only the first image:

req.files:  [
  {
    "originalname": "fantastic-photo.jpg",
    "encoding": "7bit",
    "mimetype": "image/jpeg",
    "destination": "media/images/Projects/w1200",
    "baseUrl": "media/images/Projects/w1200",
    "filename": "374827017056551892885227324803010320001537184436993.jpg",
    "storage": "local",
    "fieldname": "projects_img"
  },
  {
    "fieldname": "projects_img2"
  }
]

I cant understand why it doesn't return the data for the second image. Any help will be welcome!

0 Answers
Related