12k
All articles

Multer NPM: Upload de Arquivos em Node.js

O middleware Multer permite gerenciar uploads de arquivos em Node.js com Express, configurando disk storage, memory storage, filtragem e integração com AWS S3.

OpenReplay Team
OpenReplay Team
Multer NPM: Upload de Arquivos em Node.js

O upload de arquivos é um requisito comum em aplicações web, mas lidar com eles adequadamente em Node.js pode ser desafiador. Multer é um middleware para Express.js que torna o upload de arquivos simples e eficiente. Este artigo explora como usar o Multer para gerenciar uploads de arquivos em suas aplicações Node.js.

Principais Pontos

  • Multer simplifica uploads de arquivos em Node.js ao lidar com multipart/form-data
  • Configure opções de armazenamento com base em suas necessidades (disco, memória ou nuvem)
  • Implemente validação e filtragem adequadas para garantir segurança
  • Defina limites apropriados para evitar abusos
  • Trate erros de forma elegante para proporcionar uma boa experiência ao usuário
  • Considere armazenamento em nuvem para aplicações em produção

O que é o Multer?

Multer é um middleware Node.js especificamente projetado para lidar com multipart/form-data, que é o formato usado ao fazer upload de arquivos através de formulários HTML. Construído sobre o busboy, o Multer processa eficientemente uploads de arquivos e os torna acessíveis em suas rotas Express.

Principais recursos do Multer incluem:

  • Gerenciamento de uploads de arquivos únicos e múltiplos
  • Opções de armazenamento configuráveis
  • Capacidades de filtragem de arquivos
  • Limites de tamanho e validação
  • Integração perfeita com Express.js

Entendendo Multipart Form Data

Antes de mergulhar no Multer, é importante entender por que o processamento regular de formulários não funciona para uploads de arquivos.

Quando um formulário contém arquivos, o navegador o codifica como multipart/form-data em vez do padrão application/x-www-form-urlencoded. Esta codificação permite que dados binários (arquivos) sejam transmitidos junto com campos de texto.

Os middlewares integrados do Express (express.json() e express.urlencoded()) não conseguem processar dados multipart, por isso precisamos do Multer.

<!-- Este formulário enviará multipart/form-data -->
<form action=""/upload"" method=""POST"" enctype=""multipart/form-data"">
  <input type=""file"" name=""document"">
  <button type=""submit"">Upload</button>
</form>

Começando com o Multer

Instalação

Primeiro, instale o Multer em seu projeto Node.js:

npm install multer

Configuração Básica

Aqui está um exemplo simples de como configurar o Multer em uma aplicação Express:

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

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

app.post('/upload', upload.single('document'), (req, res) => {
  // req.file contém informações sobre o arquivo carregado
  console.log(req.file);
  
  // req.body contém quaisquer campos de texto
  console.log(req.body);
  
  res.send('Arquivo carregado com sucesso');
});

app.listen(3000, () => {
  console.log('Servidor rodando na porta 3000');
});

Neste exemplo:

  1. Criamos uma instância do Multer com uma pasta de destino para uploads
  2. Usamos o middleware upload.single('document') para lidar com um único upload de arquivo
  3. As informações do arquivo carregado estão disponíveis em req.file

Lidando com Diferentes Cenários de Upload

O Multer fornece vários métodos para lidar com diferentes cenários de upload de arquivos:

Upload de Arquivo Único

// Lidar com um único arquivo com o nome de campo 'profile'
app.post('/profile', upload.single('profile'), (req, res) => {
  // req.file contém o arquivo carregado
  res.json({ 
    message: 'Arquivo carregado com sucesso',
    file: req.file 
  });
});

Múltiplos Arquivos (Mesmo Campo)

// Lidar com múltiplos arquivos (máximo 5) com o nome de campo 'photos'
app.post('/photos', upload.array('photos', 5), (req, res) => {
  // req.files contém um array de arquivos
  res.json({ 
    message: `${req.files.length} arquivos carregados com sucesso`,
    files: req.files 
  });
});

Múltiplos Arquivos (Campos Diferentes)

const uploadFields = upload.fields([
  { name: 'avatar', maxCount: 1 },
  { name: 'gallery', maxCount: 3 }
]);

app.post('/profile', uploadFields, (req, res) => {
  // req.files é um objeto com nomes de campos como chaves
  // req.files.avatar contém o arquivo de avatar
  // req.files.gallery contém os arquivos da galeria
  res.json({ 
    message: 'Arquivos carregados com sucesso',
    avatar: req.files.avatar,
    gallery: req.files.gallery
  });
});

Formulários Multipart Apenas com Texto

app.post('/form-data', upload.none(), (req, res) => {
  // Processa apenas campos de texto, rejeita quaisquer arquivos
  res.json(req.body);
});

Opções de Armazenamento

O Multer fornece diferentes mecanismos de armazenamento para controlar onde e como os arquivos são armazenados.

Armazenamento em Disco

Para mais controle sobre o armazenamento de arquivos, use diskStorage:

const storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, 'uploads/');
  },
  filename: function(req, file, cb) {
    // Cria um nome de arquivo único
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, file.fieldname + '-' + uniqueSuffix + getExtension(file.originalname));
  }
});

function getExtension(filename) {
  return filename.substring(filename.lastIndexOf('.'));
}

const upload = multer({ storage: storage });

Esta configuração dá a você controle sobre:

  • Onde os arquivos são armazenados (destination)
  • Como são nomeados (filename)

Armazenamento em Memória

Se você precisa processar arquivos na memória sem salvar em disco:

const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

app.post('/upload', upload.single('file'), (req, res) => {
  // req.file.buffer contém os dados do arquivo
  // Processe o arquivo na memória (por exemplo, upload para armazenamento em nuvem)
  
  // Exemplo: Obter buffer e tipo do arquivo
  const fileBuffer = req.file.buffer;
  const fileType = req.file.mimetype;
  
  res.send('Arquivo processado');
});

O armazenamento em memória é útil quando:

  • Você está passando arquivos para outro serviço (como S3 ou Cloudinary)
  • Você precisa processar o arquivo antes de salvar
  • Você está trabalhando em ambientes serverless

Filtragem de Arquivos

Você pode controlar quais arquivos são aceitos usando a opção fileFilter:

const fileFilter = (req, file, cb) => {
  // Aceita apenas arquivos de imagem
  if (file.mimetype.startsWith('image/')) {
    cb(null, true);
  } else {
    cb(new Error('Apenas arquivos de imagem são permitidos!'), false);
  }
};

const upload = multer({ 
  storage: multer.diskStorage({...}),
  fileFilter: fileFilter
});

Limites de Tamanho e Segurança

Definir limites é crucial para segurança e desempenho:

const upload = multer({
  storage: multer.diskStorage({...}),
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB em bytes
    files: 5 // Máximo de 5 arquivos por requisição
  }
});

Outras opções de limite incluem:

  • fieldNameSize: Tamanho máximo do nome do campo (padrão 100 bytes)
  • fieldSize: Tamanho máximo do valor do campo (padrão 1MB)
  • fields: Número máximo de campos não-arquivo (padrão Infinity)
  • parts: Número máximo de partes (campos + arquivos) (padrão Infinity)

Tratamento de Erros

Os erros do Multer devem ser tratados adequadamente para fornecer feedback significativo:

app.post('/upload', (req, res) => {
  upload.single('file')(req, res, (err) => {
    if (err instanceof multer.MulterError) {
      // Ocorreu um erro do Multer
      if (err.code === 'LIMIT_FILE_SIZE') {
        return res.status(400).json({ error: 'Arquivo muito grande' });
      }
      return res.status(400).json({ error: err.message });
    } else if (err) {
      // Ocorreu um erro desconhecido
      return res.status(500).json({ error: 'Erro no servidor' });
    }
    
    // Tudo correu bem
    res.json({ message: 'Arquivo carregado com sucesso', file: req.file });
  });
});

Exemplo Completo: Construindo um Sistema de Upload de Arquivos

Vamos construir um sistema completo de upload de arquivos com frontend e backend:

Backend (server.js)

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

const app = express();
const port = 3000;

// Servir arquivos estáticos
app.use(express.static('public'));

// Configurar armazenamento
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    const uploadDir = 'uploads';
    // Criar diretório se não existir
    if (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir);
    }
    cb(null, uploadDir);
  },
  filename: (req, file, cb) => {
    const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
    cb(null, uniqueName);
  }
});

// Configurar filtro de arquivos
const fileFilter = (req, file, cb) => {
  // Aceitar imagens e PDFs
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
  
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('Tipo de arquivo inválido. Apenas JPEG, PNG, GIF e PDF são permitidos.'), false);
  }
};

// Configurar multer
const upload = multer({
  storage: storage,
  fileFilter: fileFilter,
  limits: {
    fileSize: 10 * 1024 * 1024, // 10MB
    files: 5
  }
});

// Rota de upload de arquivo único
app.post('/upload/single', (req, res) => {
  upload.single('file')(req, res, (err) => {
    if (err instanceof multer.MulterError) {
      return res.status(400).json({ success: false, message: err.message });
    } else if (err) {
      return res.status(400).json({ success: false, message: err.message });
    }
    
    if (!req.file) {
      return res.status(400).json({ success: false, message: 'Nenhum arquivo fornecido' });
    }
    
    res.json({
      success: true,
      message: 'Arquivo carregado com sucesso',
      file: {
        filename: req.file.filename,
        originalname: req.file.originalname,
        mimetype: req.file.mimetype,
        size: req.file.size,
        path: req.file.path
      }
    });
  });
});

// Rota de upload de múltiplos arquivos
app.post('/upload/multiple', (req, res) => {
  upload.array('files', 5)(req, res, (err) => {
    if (err instanceof multer.MulterError) {
      return res.status(400).json({ success: false, message: err.message });
    } else if (err) {
      return res.status(400).json({ success: false, message: err.message });
    }
    
    if (!req.files || req.files.length === 0) {
      return res.status(400).json({ success: false, message: 'Nenhum arquivo fornecido' });
    }
    
    const fileDetails = req.files.map(file => ({
      filename: file.filename,
      originalname: file.originalname,
      mimetype: file.mimetype,
      size: file.size,
      path: file.path
    }));
    
    res.json({
      success: true,
      message: `${req.files.length} arquivos carregados com sucesso`,
      files: fileDetails
    });
  });
});

// Obter lista de arquivos carregados
app.get('/files', (req, res) => {
  const uploadDir = 'uploads';
  
  fs.readdir(uploadDir, (err, files) => {
    if (err) {
      return res.status(500).json({ success: false, message: 'Erro ao ler diretório de arquivos' });
    }
    
    res.json({
      success: true,
      files: files
    });
  });
});

app.listen(port, () => {
  console.log(`Servidor rodando em http://localhost:${port}`);
});

Frontend (public/index.html)

<!DOCTYPE html>
<html lang=""en"">
<head>
  <meta charset=""UTF-8"">
  <meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
  <title>Upload de Arquivos com Multer</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }
    .upload-section {
      margin-bottom: 30px;
      padding: 20px;
      border: 1px solid #ddd;
      border-radius: 5px;
    }
    .file-list {
      margin-top: 20px;
    }
    .file-item {
      padding: 10px;
      margin: 5px 0;
      background-color: #f5f5f5;
      border-radius: 3px;
    }
    .error {
      color: red;
      margin-top: 10px;
    }
    .success {
      color: green;
      margin-top: 10px;
    }
    progress {
      width: 100%;
      margin-top: 10px;
    }
  </style>
</head>
<body>
  <h1>Upload de Arquivos com Multer</h1>
  
  <div class=""upload-section"">
    <h2>Upload de Arquivo Único</h2>
    <form id=""singleUploadForm"">
      <input type=""file"" id=""singleFile"" name=""file"" required>
      <button type=""submit"">Enviar Arquivo</button>
    </form>
    <div id=""singleUploadResult""></div>
    <progress id=""singleUploadProgress"" value=""0"" max=""100"" style=""display:none;""></progress>
  </div>
  
  <div class=""upload-section"">
    <h2>Upload de Múltiplos Arquivos</h2>
    <form id=""multipleUploadForm"">
      <input type=""file"" id=""multipleFiles"" name=""files"" multiple required>
      <button type=""submit"">Enviar Arquivos</button>
    </form>
    <div id=""multipleUploadResult""></div>
    <progress id=""multipleUploadProgress"" value=""0"" max=""100"" style=""display:none;""></progress>
  </div>
  
  <div class=""upload-section"">
    <h2>Arquivos Carregados</h2>
    <button id=""refreshFiles"">Atualizar Lista</button>
    <div id=""fileList"" class=""file-list""></div>
  </div>

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      // Upload de arquivo único
      const singleUploadForm = document.getElementById('singleUploadForm');
      const singleUploadResult = document.getElementById('singleUploadResult');
      const singleUploadProgress = document.getElementById('singleUploadProgress');
      
      singleUploadForm.addEventListener('submit', function(e) {
        e.preventDefault();
        const fileInput = document.getElementById('singleFile');
        
        if (!fileInput.files.length) {
          singleUploadResult.innerHTML = '<div class=""error"">Por favor, selecione um arquivo</div>';
          return;
        }
        
        const formData = new FormData();
        formData.append('file', fileInput.files[0]);
        
        singleUploadProgress.style.display = 'block';
        singleUploadProgress.value = 0;
        singleUploadResult.innerHTML = '';
        
        const xhr = new XMLHttpRequest();
        
        xhr.upload.addEventListener('progress', function(e) {
          if (e.lengthComputable) {
            const percentComplete = (e.loaded / e.total) * 100;
            singleUploadProgress.value = percentComplete;
          }
        });
        
        xhr.onload = function() {
          if (xhr.status === 200) {
            const response = JSON.parse(xhr.responseText);
            singleUploadResult.innerHTML = `<div class=""success"">${response.message}</div>`;
            singleUploadForm.reset();
            loadFiles();
          } else {
            let errorMessage = 'Falha no upload';
            try {
              const response = JSON.parse(xhr.responseText);
              errorMessage = response.message || errorMessage;
            } catch (e) {}
            singleUploadResult.innerHTML = `<div class=""error"">${errorMessage}</div>`;
          }
          singleUploadProgress.style.display = 'none';
        };
        
        xhr.onerror = function() {
          singleUploadResult.innerHTML = '<div class=""error"">Ocorreu um erro de rede</div>';
          singleUploadProgress.style.display = 'none';
        };
        
        xhr.open('POST', '/upload/single', true);
        xhr.send(formData);
      });
      
      // Upload de múltiplos arquivos
      const multipleUploadForm = document.getElementById('multipleUploadForm');
      const multipleUploadResult = document.getElementById('multipleUploadResult');
      const multipleUploadProgress = document.getElementById('multipleUploadProgress');
      
      multipleUploadForm.addEventListener('submit', function(e) {
        e.preventDefault();
        const fileInput = document.getElementById('multipleFiles');
        
        if (!fileInput.files.length) {
          multipleUploadResult.innerHTML = '<div class=""error"">Por favor, selecione pelo menos um arquivo</div>';
          return;
        }
        
        const formData = new FormData();
        for (let i = 0; i < fileInput.files.length; i++) {
          formData.append('files', fileInput.files[i]);
        }
        
        multipleUploadProgress.style.display = 'block';
        multipleUploadProgress.value = 0;
        multipleUploadResult.innerHTML = '';
        
        const xhr = new XMLHttpRequest();
        
        xhr.upload.addEventListener('progress', function(e) {
          if (e.lengthComputable) {
            const percentComplete = (e.loaded / e.total) * 100;
            multipleUploadProgress.value = percentComplete;
          }
        });
        
        xhr.onload = function() {
          if (xhr.status === 200) {
            const response = JSON.parse(xhr.responseText);
            multipleUploadResult.innerHTML = `<div class=""success"">${response.message}</div>`;
            multipleUploadForm.reset();
            loadFiles();
          } else {
            let errorMessage = 'Falha no upload';
            try {
              const response = JSON.parse(xhr.responseText);
              errorMessage = response.message || errorMessage;
            } catch (e) {}
            multipleUploadResult.innerHTML = `<div class=""error"">${errorMessage}</div>`;
          }
          multipleUploadProgress.style.display = 'none';
        };
        
        xhr.onerror = function() {
          multipleUploadResult.innerHTML = '<div class=""error"">Ocorreu um erro de rede</div>';
          multipleUploadProgress.style.display = 'none';
        };
        
        xhr.open('POST', '/upload/multiple', true);
        xhr.send(formData);
      });
      
      // Carregar arquivos
      const fileList = document.getElementById('fileList');
      const refreshFilesButton = document.getElementById('refreshFiles');
      
      function loadFiles() {
        fetch('/files')
          .then(response => response.json())
          .then(data => {
            if (data.success) {
              if (data.files.length === 0) {
                fileList.innerHTML = '<p>Nenhum arquivo carregado ainda</p>';
              } else {
                let html = '';
                data.files.forEach(file => {
                  html += `<div class=""file-item"">${file}</div>`;
                });
                fileList.innerHTML = html;
              }
            } else {
              fileList.innerHTML = `<div class=""error"">${data.message}</div>`;
            }
          })
          .catch(error => {
            fileList.innerHTML = '<div class=""error"">Erro ao carregar arquivos</div>';
          });
      }
      
      refreshFilesButton.addEventListener('click', loadFiles);
      
      // Carregamento inicial
      loadFiles();
    });
  </script>
</body>
</html>

Integração com Armazenamento em Nuvem

Para aplicações em produção, geralmente você deseja armazenar arquivos em armazenamento em nuvem em vez de no seu servidor. Veja como integrar o Multer com o AWS S3:

const AWS = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');

// Configurar AWS
AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: process.env.AWS_REGION
});

const s3 = new AWS.S3();

// Configurar multer para usar S3
const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: process.env.S3_BUCKET_NAME,
    metadata: function (req, file, cb) {
      cb(null, { fieldName: file.fieldname });
    },
    key: function (req, file, cb) {
      const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}`;
      cb(null, uniqueName);
    }
  }),
  limits: {
    fileSize: 5 * 1024 * 1024 // 5MB
  }
});

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({
    success: true,
    message: 'Arquivo carregado para o S3 com sucesso',
    fileLocation: req.file.location // URL do S3
  });
});

Nota: Você precisará instalar o pacote multer-s3: npm install multer-s3 aws-sdk.

Melhores Práticas de Segurança

Ao implementar uploads de arquivos, siga estas melhores práticas de segurança:

  1. Validar tipos de arquivo: Sempre verifique tipos MIME e extensões
  2. Limitar tamanhos de arquivo: Evite ataques DoS de uploads de arquivos grandes
  3. Verificar malware: Considere integrar verificação de vírus para arquivos carregados
  4. Usar armazenamento seguro: Não armazene arquivos em diretórios públicos
  5. Gerar nomes de arquivo aleatórios: Não use nomes de arquivo fornecidos pelo usuário diretamente
  6. Implementar autenticação: Garanta que apenas usuários autorizados possam fazer upload de arquivos
  7. Definir limites de upload: Restrinja o número de arquivos por requisição e por usuário

Conclusão

O Multer fornece uma solução robusta para lidar com uploads de arquivos em aplicações Node.js. Seguindo os padrões e práticas descritos neste artigo, você pode implementar funcionalidades de upload de arquivos seguras, eficientes e amigáveis ao usuário. Seja construindo um simples uploader de imagens ou um sistema complexo de gerenciamento de documentos, a API flexível do Multer e suas capacidades de integração o tornam uma excelente escolha para lidar com multipart/form-data em suas aplicações Express.

Perguntas Frequentes

Como lidar com uploads de arquivos grandes com o Multer?

Para arquivos grandes, considere: aumentar o limite de tamanho na configuração do Multer, implementar uploads em partes para arquivos muito grandes, usar streaming para processar arquivos sem carregá-los inteiramente na memória e implementar rastreamento de progresso no frontend.

Posso usar o Multer com TypeScript?

Sim, o Multer funciona bem com TypeScript. Instale as definições de tipo com: npm install @types/multer

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.