How to upload files with Multer in two different storages?

Good night, I need to upload a file to a local folder (where it will be treated to compress the file) and to S3 storage. the configuration of S3 and local storage with multer is scheduled and both work separately, when I put the two configurations in the same route returns undefined in the second multer middleware.

Form submitted:

var formulario = new FormData();

formulario.append('file', arquivo);

api.post('/postarArte', formulario).then((resposta) => {
    console.log(resposta);
});

Route with middleware:

const multer = require('multer');
const multerConfig = require('./config/multerS3');
const multerLocal = require('./config/multerLocal');

// Rota para postar arte individual
router.post('/postarArte',   

    // Upload para a pasta local
    multerLocal.single('file'),

    // Upload para o storage S3
    multer(multerConfig).single('file'),

    // inserção no banco de dados
    postarArte
    
);

I need the return of the image URL upada on S3 and the locally stored image path to write both to the database, so I thought I'd use the same route. is there any specific configuration to use two storages in multer (maybe in the same configuration file) or some other way that solves this problem?

MulterLocal configuration:

const multer = require('multer');
const path = require('path');

// Vamos exportar nosso módulo multer, executando com as nossas configurações em um objeto.
module.exports = (multer({

    // Como vai ser feito o armazenamento de aruqivos:
    storage: multer.diskStorage({

        // Destino do arquivo:
        destination: (req, file, cb) => {

            // setando o destino como segundo paramêtro do callback
            cb(null, path.resolve(__dirname, '..', '..', 'images'));
        },

        // Como os arquivos vão ser chamados:
        filename: (req, file, cb) => {

            // Setando o nome do arquivo que vai ser salvado no segundo paramêtro
            // Apenas concatenei a data atual como o nome original do arquivo, que a biblioteca nos disponibiliza.
            cb(null, Date.now().toString() + '-' + file.originalname);

        },

        // Formatos aceitos:
        fileFilter: (req, file, cb) => {

            const isAccepted = ['image/png', 'image/jpg', 'image/jpeg'].find(formatoAceito => {
                formatoAceito == file.mimetype
            });

            // Formato aceito:
            if (isAccepted) {

                return cb(null, true);
            }

            // Formato inválido:
            return cb(null, false);
        }

    }),

  
}));

MulterS3 configuration


const crypto = require('crypto');

const aws = require('aws-sdk');

const multerS3 = require('multer-s3');

const storageTypes = {

    s3: multerS3({

        s3: new aws.S3(),
        bucket: '*****',
        contentType: multerS3.AUTO_CONTENT_TYPE,
        /** permissão, para que todo mundo consiga ler os arquivos */
        acl: 'public-read',
        /** nome da imagem que vai ser gravada no S3 */
        key: (req, file, callBack) => {

            crypto.randomBytes(16/** Tamanho do numero de bytes */, (err, hash) => {
                if (err) callBack(err);

                const fileName = `${hash.toString('hex')}-${file.originalname}`;
                callBack(null, fileName);
            });
        },
    }),
}

module.exports = {

    storage: storageTypes[process.env.STORAGE_TYPE],

    fileFilter: (req, file, callBack) => {
        const allowedMimes = [
            'image/jpeg',
            'image/pjpeg',
            'image/png',
            'image/gif'
        ];

        if (allowedMimes.includes(file.mimetype)) {
            callBack(null, true);
        } else {
            callBack(new Error('Arquivo inválido'));
        };
    },
    
};

PostarArte function setting:

const conexao = require('../models/conexao');

function postarArte(req, resultado) {
    console.log(req.body);
    console.log(req.file);
    
    const { titulo, desc: descricao, tipo } = req.body;

    const { 
        originalname: nomeOriginal, 
        location: url, 
        key: chave, 
        size: tamanhoArquivo 
    } = req.file;

    valores = [
        [titulo, nomeOriginal, chave, descricao, tipo, url, tamanhoArquivo]
    ]

    conexao.query(`INSERT INTO postagem(titulo, nomeOriginal, chave, descricao, tipo, url, tamanhoArquivo) VALUES (?)`, valores, (req, res) => {
        return resultado.json()
    });    

};

module.exports = postarArte

Thank you all!

Author: Braga Us, 2020-08-16

1 answers

Instead of letting multer run "alone" you can create your own middleware and intercept multer's responses. I assume the problem is that multer overwrites req.files, I have never tested the Chained multer this way but I assume the problem is it overwrites req.files.

You can do it like this, it's kind of callback hell, and kind of "dirty" because it inserts keys into the object req (but I don't see it being a problem in the namespace), but it does what searches:

const multerLocal = multerLocal.single('file');
const multerS3 = multer(multerConfig).single('file');

router.post('/postarArte', (req, res, next) => {
  // chama o primeiro multer
  multerLocal(req, res, () => {
        // guarda o endereço do ficheiro
        req.multerLocalFiles = req.files;
 
        // chama o segundo multer (sequencialmente)
        multerS3(req, res, () => {

          // guarda o endereço do ficheiro
          req.multerS3Files = req.files;
          // deixa o express ir para o próximo middleware
          next();
        })
      }
    },

    // inserção no banco de dados
    postarArte
);
 2
Author: Sergio, 2020-08-16 21:21:11