O FormData no Node.js permite montar corpos multipart/form-data para enviar campos de texto, arquivos, imagens e outros blobs por HTTP. A API segue o padrão da Web e integra diretamente com o fetch nativo, tornando uploads mais consistentes entre navegador e backend.
Apesar da interface simples, uploads exigem limites, validação de tipo, nomes seguros, timeouts e proteção contra SSRF. Também é importante não definir manualmente o boundary do Content-Type, porque ele precisa corresponder ao corpo serializado.
Neste guia, você aprenderá a criar formulários, adicionar campos, anexar Blob e File, enviar com fetch, receber respostas, trabalhar com arquivos do filesystem, controlar memória, testar e evitar vulnerabilidades.
O que é FormData?
FormData representa um conjunto de pares entre nomes e valores, serializado como multipart. A documentação oficial de FormData no Node.js mostra a API global. A documentação de FormData na MDN explica os métodos padronizados.
Para requisições, consulte Fetch Nativo no Node.js. Para dados binários, veja Buffer no Node.js. O artigo Web Streams API no Node.js ajuda a entender corpos em fluxo.
Criando um formulário
const form = new FormData();
form.set('name', 'Ana');
form.set('active', 'true');Valores simples são convertidos para texto. Números e booleanos devem ser serializados de forma explícita para evitar ambiguidades.
append() e set()
form.append('tag', 'node');
form.append('tag', 'javascript');
form.set('category', 'backend');append() adiciona outro valor à mesma chave. set() substitui os valores anteriores.
Lendo valores
console.log(form.get('name'));
console.log(form.getAll('tag'));
console.log(form.has('category'));Use getAll() quando a chave pode aparecer várias vezes.
Removendo um campo
form.delete('temporary');Esse método remove todos os valores associados à chave.
Iterando
for (const [name, value] of form.entries()) {
console.log(name, value);
}Não registre blobs, tokens ou dados pessoais sem sanitização.
Enviando com fetch
const response = await fetch('https://api.example.com/profile', {
method: 'POST',
body: form
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}Quando o body é FormData, o fetch define o Content-Type com boundary.
Não defina o boundary manualmente
const response = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'multipart/form-data'
},
body: form
});Esse exemplo está incorreto porque o header não inclui o boundary gerado. Remova o Content-Type e deixe a implementação criá-lo.
Adicionando Blob
const content = new Blob(
['conteúdo do arquivo'],
{ type: 'text/plain' }
);
form.set('document', content, 'document.txt');O terceiro argumento define o nome enviado ao servidor.
Adicionando File
const file = new File(
[bytes],
'photo.jpg',
{ type: 'image/jpeg' }
);
form.set('photo', file);A disponibilidade de File depende da versão do Node.js. Consulte a documentação do runtime usado.
Arquivo do filesystem
const fs = require('node:fs/promises');
const bytes = await fs.readFile('./uploads/report.pdf');
const blob = new Blob([bytes], {
type: 'application/pdf'
});
form.set('report', blob, 'report.pdf');readFile() carrega todo o arquivo em memória. Para arquivos grandes, use uma estratégia compatível com streaming ou biblioteca que suporte upload incremental.
Veja File System no Node.js para caminhos e permissões.
Tipos MIME
O tipo informado pelo cliente é apenas uma indicação. O servidor deve validar conteúdo, extensão, tamanho e assinatura de arquivo.
Nomes de arquivo
Nunca use diretamente o nome recebido para criar um caminho local:
const unsafePath = path.join(uploadRoot, uploadedName);Gere um identificador próprio e mantenha o nome original apenas como metadado sanitizado.
Campos JSON
form.set('metadata', JSON.stringify({
title: 'Relatório',
category: 'finance'
}));O servidor precisa analisar e validar esse JSON separadamente.
Arrays
for (const tag of tags) {
form.append('tags', tag);
}Documente se o servidor espera chaves repetidas, colchetes ou JSON.
Booleanos e números
form.set('active', String(active));
form.set('page', String(page));Na recepção, faça conversão explícita e valide faixas.
Timeout
const response = await fetch(url, {
method: 'POST',
body: form,
signal: AbortSignal.timeout(15000)
});Uploads grandes podem exigir prazo maior, mas um timeout ilimitado permite conexões penduradas.
Cancelamento do usuário
const controller = new AbortController();
const promise = fetch(url, {
method: 'POST',
body: form,
signal: controller.signal
});
controller.abort();Cancelar localmente não garante que o servidor não recebeu parte ou todo o arquivo.
Autenticação
const response = await fetch(url, {
method: 'POST',
headers: {
authorization: `Bearer ${token}`
},
body: form
});Não inclua o token em campos do formulário ou logs.
Resposta JSON
if (!response.ok) {
const message = await response.text();
throw new Error(`Upload falhou: ${response.status}`);
}
const result = await response.json();Limite o corpo de erro e não exponha a resposta completa ao usuário.
Memória
Construir um Blob a partir de um Buffer grande pode duplicar referências ou cópias, dependendo da operação. Meça RSS e heap durante uploads reais.
Uploads concorrentes
Limite quantos arquivos são preparados e enviados simultaneamente. Dez uploads de 500 MB podem esgotar memória e largura de banda.
const limit = createConcurrencyLimit(3);
await Promise.all(files.map(file =>
limit(() => uploadFile(file))
));Retry
Não repita uploads automaticamente sem entender se o servidor criou o recurso. Use chaves de idempotência ou protocolo de upload resumível.
Consulte Retry com Backoff no Node.js.
Upload resumível
Para arquivos grandes e redes instáveis, prefira protocolos com partes, offset e confirmação. FormData comum envia o corpo inteiro novamente após falha.
Segurança no destino
Se a URL é fornecida pelo usuário, há risco de SSRF. Use allowlist de hosts, bloqueie redes privadas, controle redirects e limite portas.
Recebendo multipart
FormData é uma API para construir e manipular o formulário. Para receber multipart em um servidor Node.js, use um parser confiável com limites de:
- tamanho total;
- tamanho por arquivo;
- quantidade de campos;
- quantidade de arquivos;
- comprimento de nomes;
- tempo de upload.
Arquivos temporários
Parsers podem gravar em disco temporário. Configure diretório, permissões, quotas e remoção após erro.
Validação de imagem
Não confie apenas no Content-Type. Analise dimensões, assinatura e formato real. Reencode imagens quando necessário para remover conteúdo inesperado.
Antivírus
Ambientes com documentos enviados por terceiros podem exigir varredura antes de disponibilizar o arquivo.
Observabilidade
Registre:
- destino sanitizado;
- quantidade de campos;
- tamanho total;
- duração;
- status;
- cancelamento;
- tentativa;
- identificador da operação.
Não registre conteúdo, nomes sensíveis ou tokens.
Testes
Cubra:
- campo simples;
- chaves repetidas;
- arquivo vazio;
- arquivo grande;
- Unicode no nome;
- tipo MIME inválido;
- timeout;
- cancelamento;
- resposta de erro;
- limite de concorrência.
Use o Node Test Runner com um servidor local.
Erros comuns
- Definir Content-Type manualmente: boundary fica incorreto.
- Ler arquivo gigante inteiro: memória cresce.
- Confiar no MIME: conteúdo malicioso passa.
- Usar nome original como caminho: ocorre path traversal.
- Retry sem idempotência: uploads duplicam.
- Sem timeout: conexão fica aberta.
- Concorrência ilimitada: recursos são esgotados.
Boas práticas
- Deixe o fetch gerar o boundary.
- Valide campos e arquivos.
- Limite tamanho e quantidade.
- Gere nomes internos.
- Use timeout e cancelamento.
- Controle concorrência.
- Evite carregar arquivos grandes em memória.
- Proteja URLs contra SSRF.
- Use idempotência.
- Remova temporários.
Conclusão
O FormData no Node.js simplifica o envio de campos e arquivos com fetch usando uma API compatível com a Web.
Uploads seguros dependem de mais do que serialização. Limites, validação, nomes controlados, timeouts, idempotência e observabilidade evitam que uma operação conveniente se torne fonte de consumo excessivo, duplicidade ou vulnerabilidades. Com essas práticas, FormData funciona bem em integrações e serviços modernos.




