CORS em APIs Node.js é um tema importante para quem desenvolve serviços modernos com JavaScript no servidor. Não basta fazer uma demonstração funcionar no computador local: uma solução profissional precisa ter contratos claros, tratamento de falhas, segurança, testes, observabilidade e um processo de implantação previsível.
Neste guia, você aprenderá a aplicar CORS em APIs Node.js de forma prática, entendendo a arquitetura, os principais componentes, os riscos mais comuns e as decisões que tornam o projeto mais fácil de manter. O objetivo é sair de um exemplo isolado e chegar a uma base que possa evoluir sem depender de improvisos.
O que é CORS em APIs Node.js?
CORS, ou Cross-Origin Resource Sharing, é o mecanismo de headers HTTP usado pelo navegador para decidir se um site pode acessar recursos de outra origem. Ele não é um sistema de autenticação e não protege chamadas feitas por servidores, scripts fora do navegador ou ferramentas de linha de comando.
Em aplicações Node.js, essa abordagem se encaixa bem em sistemas orientados a eventos e operações de entrada e saída. Para revisar os fundamentos da plataforma, leia o que é Node.js. Se você ainda está montando seu primeiro backend, o tutorial sobre como criar uma API com Node.js oferece uma visão complementar.
Quando vale a pena usar?
Configure CORS quando um frontend hospedado em uma origem precisa consumir uma API em outra. A política deve ser tão restrita quanto o produto permite. Aplicações servidas pela mesma origem podem evitar CORS; APIs públicas podem aceitar várias origens, mas ainda precisam de autenticação, rate limiting e validação.
A decisão deve considerar o problema real, a experiência da equipe, a infraestrutura disponível e o custo de operação. Uma tecnologia pode ser excelente e ainda assim ser inadequada quando aumenta a complexidade sem gerar benefício mensurável. Comece pequeno, defina critérios de sucesso e valide o comportamento sob carga e falhas.
Preparando o projeto
Crie uma pasta dedicada, inicialize o package.json e fixe uma versão suportada do Node.js no arquivo de configuração do projeto. Use variáveis de ambiente para endereços, credenciais e opções de execução, mas valide tudo no início do processo para evitar falhas tardias.
npm init -y
npm install express cors
npm install --save-dev supertestSepare dependências de produção das ferramentas de desenvolvimento. Ative lint, formatação e testes no pipeline de integração contínua. O artigo sobre deploy com GitHub Actions mostra como automatizar verificações antes que uma alteração chegue ao servidor.
Exemplo inicial
O exemplo permite uma lista explícita de origens e rejeita as demais. A função também aceita requisições sem Origin, comuns em clientes servidor a servidor.
import express from 'express';
import cors from 'cors';
const allowedOrigins = new Set([
'https://app.example.com',
'https://admin.example.com'
]);
const app = express();
app.use(cors({
origin(origin, callback) {
if (!origin || allowedOrigins.has(origin)) {
return callback(null, true);
}
callback(new Error('Origin not allowed'));
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 600
}));Quando credentials é true, a resposta não pode usar Access-Control-Allow-Origin com asterisco. A origem precisa ser refletida apenas depois de passar pela lista permitida. O maxAge reduz preflights repetidos, mas mudanças de política podem levar tempo para alcançar clientes.
Estrutura recomendada
Centralize a política de CORS em um módulo de configuração, porém permita exceções explícitas por rota. Separe origens de desenvolvimento, homologação e produção. Não derive permissões diretamente de qualquer header ou parâmetro enviado pelo cliente.
- config: leitura e validação de ambiente;
- domain: regras de negócio independentes do transporte;
- application: casos de uso e orquestração;
- infrastructure: banco, filas, rede e integrações;
- interfaces: HTTP, comandos, eventos ou tarefas agendadas;
- tests: unidades, integração e cenários de contrato.
Essa separação reduz acoplamento e permite substituir bibliotecas sem reescrever regras de negócio. Evite criar camadas vazias apenas para seguir um padrão: cada módulo deve proteger uma responsabilidade concreta.
Contratos e validação
Dados externos são sempre não confiáveis. Valide corpo, parâmetros, headers, eventos e respostas de serviços terceiros. Defina tamanho máximo, formatos permitidos e mensagens de erro consistentes. Schemas executáveis aproximam documentação e comportamento real; veja também o guia de validação com Zod no TypeScript.
Normalize origens como URLs completas com esquema e porta. Compare valores exatos, não use includes ou endsWith ingênuos. Uma regra como domínio terminando em example.com pode aceitar attackerexample.com. Para subdomínios dinâmicos, faça parse da URL e valide o hostname por limites de rótulo.
Tratamento de erros
Classifique falhas em categorias: entrada inválida, autenticação, autorização, recurso ausente, conflito, indisponibilidade temporária e erro interno. A resposta pública deve ser estável e não pode expor stack trace, consulta SQL, segredo ou detalhes da infraestrutura.
Responda preflight rejeitado sem expor detalhes desnecessários. Em muitos casos, o navegador exibirá um erro genérico ao frontend. Registre a origem, rota e motivo internamente. Não transforme toda falha de autenticação em problema de CORS, porque isso dificulta diagnóstico.
function isAllowedOrigin(origin) {
try {
const url = new URL(origin);
return url.protocol === 'https:' &&
(
url.hostname === 'app.example.com' ||
url.hostname.endsWith('.trusted.example')
);
} catch {
return false;
}
}Segurança
A segurança deve fazer parte da arquitetura desde o início. Aplique privilégio mínimo, valide todas as fronteiras e trate credenciais como dados sensíveis. Para CORS em APIs Node.js, priorize:
- mantenha uma lista explícita de origens confiáveis;
- não use asterisco quando envia cookies ou credenciais;
- valide scheme, hostname e porta;
- reduza métodos e headers permitidos ao necessário;
- proteja a API com autenticação independente de CORS;
- revise regras para ambientes de preview e subdomínios temporários.
Registre eventos de segurança sem armazenar tokens completos, senhas ou dados pessoais desnecessários. Revise dependências e mantenha um processo claro para corrigir vulnerabilidades.
Desempenho e capacidade
Otimização começa com medição. Defina indicadores como latência, taxa de erro, uso de memória, CPU, tamanho de filas e tempo de dependências. O guia sobre como otimizar APIs RESTful em Node.js detalha princípios que também se aplicam aqui.
- configure maxAge com valor moderado para preflight;
- evite políticas diferentes sem necessidade por usuário;
- não consulte banco de dados em cada verificação de origem;
- faça cache seguro de configurações de tenants;
- meça quantidade e latência de requisições OPTIONS;
- mantenha respostas preflight pequenas.
Teste com carga representativa e inclua cenários de falha. Uma solução que funciona apenas com dependências saudáveis não está pronta para produção.
Observabilidade
Registre contagem de origens aceitas e bloqueadas, preflights por rota e falhas de configuração. Evite colocar a origem como label ilimitado de métrica; use logs para valores completos e métricas agregadas por resultado.
Use logs estruturados em JSON e inclua um identificador de correlação. Métricas devem mostrar volume, sucesso, falhas e duração. Traces distribuídos ajudam a encontrar gargalos quando uma operação atravessa vários serviços, mas precisam de amostragem para controlar custo.
Testes automatizados
Teste requisições simples e preflight. Confirme origem permitida, origem bloqueada, credenciais, métodos, headers e ausência do header Origin. Também valide que uma origem parecida, porém maliciosa, não passa pela regra.
import test from 'node:test';
import assert from 'node:assert/strict';
import request from 'supertest';
import { app } from './app.js';
test('aceita preflight confiável', async () => {
const response = await request(app)
.options('/orders')
.set('Origin', 'https://app.example.com')
.set('Access-Control-Request-Method', 'POST');
assert.equal(response.status, 204);
assert.equal(
response.headers['access-control-allow-origin'],
'https://app.example.com'
);
});
test('não reflete origem desconhecida', async () => {
const response = await request(app)
.get('/orders')
.set('Origin', 'https://evil.example');
assert.equal(
response.headers['access-control-allow-origin'],
undefined
);
});Evite testes dependentes de ordem, relógio real ou serviços externos instáveis. Injete relógio, geradores de identificadores e clientes de infraestrutura. Para fundamentos, consulte testes unitários com Jest.
Implantação e operação
Garanta que proxy, CDN e cache incluam Origin na chave quando a resposta varia por origem. O header Vary: Origin evita servir uma permissão destinada a outro site. Revise cabeçalhos após passar por todos os componentes da borda.
Implemente graceful shutdown: ao receber um sinal de encerramento, pare de aceitar trabalho novo, conclua o que estiver em andamento dentro de um prazo e feche conexões. Configure health checks que diferenciem processo vivo de serviço pronto para receber tráfego.
Erros comuns
- Tratar CORS como autenticação: clientes fora do navegador ignoram essa política.
- Refletir qualquer Origin: isso equivale a aceitar todos os sites.
- Usar curinga com cookies: a combinação é inválida e insegura.
- Comparar domínio com includes: origens maliciosas podem contornar a regra.
- Esquecer o cache: respostas com origem errada podem ser reutilizadas.
Checklist antes de publicar
- origens de produção explícitas;
- preflight testado;
- credentials usado apenas quando necessário;
- Vary: Origin confirmado;
- métodos e headers mínimos;
- subdomínios validados por hostname;
- autenticação independente;
- métricas de bloqueio configuradas.
Referências oficiais
Conteúdos relacionados
- O que é Node.js
- como criar uma API com Node.js
- otimizar APIs RESTful em Node.js
- testes unitários com Jest
Conclusão
CORS é uma política do navegador baseada em headers, não uma barreira completa de segurança. Uma configuração correta usa origens explícitas, credenciais com cuidado, preflight controlado e cache coerente. O backend continua responsável por autenticar, autorizar e validar cada operação.
O caminho mais seguro é implementar uma versão pequena, observável e testável, medir seu comportamento e evoluir com base em dados. Quando contratos, limites e falhas são tratados explicitamente, CORS em APIs Node.js deixa de ser apenas uma funcionalidade e se torna uma parte confiável da plataforma.



