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 cadastrosite
- 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 nome
e 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 handlingmultipart/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):
- Configuração da URL do endpoint
- Configuração da estrutura de dados:
multipart/form-data
- Informando o parâmetro
nome
e seu respectivo valor - Informando o parâmetro
site
e seu respectivo valor - Informando o parâmetro
foto
- Depois que informar o parâmetro
foto
, passe o mouse próximo onde marquei o número 6 que irá aparecer a opçãoFile
. - Quando a opção
File
for selecionada, será possível selecionar um arquivo para upload - Clique no botão Send para enviar a requisição
- 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:
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:
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:
Resultado:
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: