Upload de arquivos e imagens utilizando o Multer - express - Node.js

O framework Express com certeza é um dos mais conhecidos para quem trabalha com web e Node.js. Seja para construir uma aplicação Web MVC ou uma API Rest ele se encaixa como uma luva. Sendo assim, antes de falar sobre o upload de arquivos, vamos começar a construção de uma API bem simples com um único endpoint:POST /cadastro

Este endpoint espera receber no body as seguintes informações:

  • nome - Nome do usuário que está efetuando o cadastro
  • site - Site preferido do usuário

Traduzindo este cenário em código:

const express = require('express');
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post('/cadastro', (req, res) => {
    const { nome, site } = req.body;
    res.json({ nome, site });
});

app.listen(3000);

No código acima há a implementação no endpoint POST /cadastro, que recebe duas informações no body e devolve as mesmas informações para que possamos estudar.

Agora vamos voltar a atenção para um formulário HTML que irá postar os dados que o endpoint espera:

<form action="/cadastro" method="post">
    <input name="nome" type="text" />
    <input name="site" type="url" />
    <input type="submit" value="Enviar" />
</form>

Ao efetuarmos o post utilizando o formulário acima, será gerada a seguinte requisição HTTP:

POST /cadastro HTTP/1.1
Host: localhost:3000
Content-Type: application/x-www-form-urlencoded

nome=Marcelo&site=https://consolelog.com.br

A requisição acima será atendida pelo endpoint POST /cadastro e a resposta terá exatamente as mesmas informações postadas no body:

{
    "nome": "Marcelo",
    "site": "https://consolelog.com.br"
}

Problema do upload de arquivo

Vamos adicionar ao nosso endpoint uma nova informação além do nome e site:

  • foto

A ideia é que um usuário possa postar uma foto neste formulário e nossa aplicação salve o arquivo postado no diretório /uploads. Com isto em mente, nosso formulário sofrerá algumas modificações:

<form action="/cadastro" method="post" enctype="multipart/form-data">
    <input name="nome" type="text" />
    <input name="site" type="url" />
    <input name="foto" type="file" />
    <input type="submit" value="Enviar" />
</form>

Adicionamos um novo <input type="file" ... >" e também adicionamos um enctype="multipart/form-data". O multipart/form-data especificado no RCF7578 é uma estrutura de dados que possibilita o upload de arquivos.

Se não alterarmos a API, preenchermos o formulário acima e clicarmos no botão Enviar, o resultado da requisição será {}, um objeto vazio! Porque isto ocorre?

Lembre-se de que agora nossos dados seguem uma outra estrutura que dá suporte a upload de arquivos, multipart/form-data, e o Express 4.x não está preparado nativamente para dar suporte a este tipo de estrutura de dados. Por esta razão ele não consegue efetuar o parse do body da requisição e retorna um objeto vazio já que não encontrou as informações nomee site conforme o código da API apresentado logo no início deste post.

Então precisamos de alguém que faça este trabalho de efetuar o parse do formulário na estrutura multipart/form-data e este "alguém" é o multer.

Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It is written on top of busboy for maximum efficiency.

NOTE: Multer will not process any form which is not multipart (multipart/form-data).

A título de curiosidade, podemos postar as mesmas informações utilizando diferentes estruturas de dados, veja abaixo os exemplos de requisições HTTP e repare bem no header Content-Type:

application/x-www-form-urlencoded

POST /cadastro HTTP/1.1
Host: localhost:3000
Content-Type: application/x-www-form-urlencoded

nome=Marcelo&site=https://consolelog.com.br

application/json

POST /cadastro HTTP/1.1
Host: localhost:3000
Content-Type: application/json

{
    "nome": "Marcelo",
    "site": "https://consolelog.com.br"
}

multipart/form-data

POST /cadastro HTTP/1.1
Host: localhost:3000
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="nome"

Marcelo
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="site"

https://consolelog.com.br
----WebKitFormBoundary7MA4YWxkTrZu0gW


Utilizando o multer para upload de arquivo(s) / imagem(s)

Para executar a instalação do multer basta utilizar o comando npm install multer. Uma vez instalado, sua configuração é bem simples, veja abaixo no código:

const express = require('express');
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

// Configuramos o upload como um middleware que
// espera um arquivo cujo a chave é "foto"
app.post('/cadastro', upload.single('foto'), (req, res) => {
    const { nome, site } = req.body;
    res.json({ nome, site });
});

app.listen(3000);

Subindo a aplicação (node index.js) podemos efetuar um POST para testá-la. Nos meus testes locais estou utilizando o Postman, que é um programa que ajuda bastante na hora de testar APIs.


Caso esteja consumindo a API através de uma aplicação rodando em seu navegador, você poderá ter problemas com CORS. Para resolver isto localmente:

  • Chrome: execute-o utilizando a seguinte opção:
    chrome.exe" --disable-web-security --disable-gpu --user-data-dir=~/chromeTemp.
  • Safari: vá até a aba Developer e selecione a opção Disable Cross-Origin Restrictions

Veja abaixo as etapas para configurar a requisição no Postman e o respectivo resultado (também na imagem):

Como configurar o POST utilizando o Postman
  1. Configuração da URL do endpoint
  2. Configuração da estrutura de dados: multipart/form-data
  3. Informando o parâmetro nome e seu respectivo valor
  4. Informando o parâmetro site e seu respectivo valor
  5. Informando o parâmetro foto
  6. Depois que informar o parâmetro foto, passe o mouse próximo onde marquei o número 6 que irá aparecer a opção File.
  7. Quando a opção File for selecionada, será possível selecionar um arquivo para upload
  8. Clique no botão Send para enviar a requisição
  9. Resultado da requisição (veja o resultado na imagem)

Até aqui perfeito, mas cade a foto? Bom, a foto ficou salva no diretório uploads, conforme configuração, mas sem a extensão - este é o comportamento padrão do multer. Apenas para conferir se tudo funcionou bem, manualmente eu adicionei a extensão para visualizar a foto:

O multer salvou o upload no diretório /uploads com um nome diferente e sem a extensão
Adicionando a extensão manualmente ao arquivo é possível visualizar que o upload funcionou corretamente

Por padrão o multer irá atribuir um novo nome ao arquivo sem a extensão, justamente para evitar um override, imagine se dois usuários fizerem um upload com o mesmo nome de arquivo.

Como renomear o arquivo de upload - multer

O multer tem várias opções e uma delas nos permite ler o nome original (originalname) do arquivo e informar um novo. Veja no código abaixo:

const express = require('express');
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

const multer = require('multer');

// Configuração de armazenamento
const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, 'uploads/')
    },
    filename: function (req, file, cb) {
        // Extração da extensão do arquivo original:
        const extensaoArquivo = file.originalname.split('.')[1];

        // Cria um código randômico que será o nome do arquivo
        const novoNomeArquivo = require('crypto')
            .randomBytes(64)
            .toString('hex');

        // Indica o novo nome do arquivo:
        cb(null, `${novoNomeArquivo}.${extensaoArquivo}`)
    }
});

const upload = multer({ storage });

app.post('/cadastro', upload.single('foto'), (req, res) => {
    const { nome, site } = req.body;
    res.json({ nome, site });
});

app.listen(3000);

Repetindo o post anterior veja que agora a imagem é salva com a extensão:

O upload foi realizado com sucesso e o nome do arquivo agora tem a extensão

Upload de vários arquivos

Vamos complicar um pouco mais e imaginar que o usuário possa postar 3 arquivos ao mesmo tempo. No formulário HTML precisamos adicionar o atributo multiple no <input type="file"> para que seja possível selecionar vários arquivos ao mesmo tempo:

<form action="/cadastro" method="post" enctype="multipart/form-data">
    <input name="nome" type="text" />
    <input name="site" type="url" />
    <input name="foto" type="file" multiple />
    <input type="submit" value="Enviar" />
</form>

No código da API a alteração é bem simples, apenas substituímos o upload.single por upload.array e indicamos a quantidade máxima de arquivos:

// ... (código ocultado) ...

const upload = multer({ storage })
const maxFotos = 3;

app.post('/cadastro', upload.array('foto', maxFotos), (req, res) => {
    const { nome, site } = req.body;
    res.json({ nome, site });
});

// ... (código ocultado) ...

Para testarmos via Postman basta selecionar mais de um arquivo no campo foto conforme a imagem abaixo mostra:

Selecionando 3 arquivos para upload no postman

Resultado:

Upload concluído com sucesso dos 3 arquivos

Considerações

O multer é um grande aliado para tratarmos essa questão de upload. Vale dar uma lida em sua documentação porque há diversos recursos interessantes que com certeza serão úteis durante o seu desenvolvimento.

Como falamos de upload, vale comentar que os webservers (NGINX, IIS, etc.) normalmente tem um limite de tamanho para o tamanho do header e/ou body, por exemplo, no NGINX o limite padrão para tamanho de body é de 1mb, ou seja, não conseguimos efetuar um post com mais de 1mb por padrão. Para aumentar este valor dê uma olhada na documentação sobre o parâmetro client_max_body_size.

Links interessantes: