Multi-Tenancy no Node.js permite que uma aplicação atenda várias organizações, clientes ou contas mantendo dados e configurações isolados. Cada tenant compartilha parte da infraestrutura, mas não deve acessar informações de outro.
O desafio principal não é adicionar um tenant_id. A identidade do tenant precisa atravessar autenticação, autorização, queries, cache, filas, logs, métricas e operações administrativas.
Modelos de isolamento
Existem três estratégias principais:
- banco compartilhado e schema compartilhado: todas as tabelas possuem
tenant_id; - schema por tenant: cada organização usa um schema separado;
- banco por tenant: cada tenant possui banco ou cluster próprio.
Também é possível usar modelo híbrido, mantendo tenants menores compartilhados e tenants regulados em bancos dedicados.
Banco compartilhado
CREATE TABLE projetos (
tenant_id uuid NOT NULL,
id uuid NOT NULL,
nome text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (tenant_id, id)
);Incluir tenant_id na chave e nos índices reduz colisões e melhora consultas isoladas.
Resolver o tenant
O tenant pode vir de subdomínio, token, API key ou mapeamento do usuário. Não confie em um header enviado livremente sem validar a associação.
export function tenantMiddleware(req, res, next) {
const tenantId = req.auth?.tenantId;
if (!tenantId) return res.status(403).json({ error: 'Tenant obrigatório' });
req.tenant = { id: tenantId };
next();
}Contexto por requisição
Use um objeto explícito ou AsyncLocalStorage para propagar tenant, usuário e trace. Contexto implícito ajuda em logs, mas repositories críticos ainda podem exigir tenantId como argumento.
Consulte AsyncLocalStorage no Node.js.
Repository isolado
export function criarProjetoRepository(db) {
return {
async obterPorId({ tenantId, projetoId }) {
const result = await db.query(
`SELECT * FROM projetos
WHERE tenant_id = $1 AND id = $2`,
[tenantId, projetoId]
);
return result.rows[0] ?? null;
}
};
}Nunca busque apenas por id se o identificador não for global e o dado for tenant-scoped.
Row-Level Security
PostgreSQL pode aplicar Row-Level Security como defesa adicional.
ALTER TABLE projetos ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON projetos
USING (tenant_id = current_setting('app.tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);Dentro da transação:
await client.query(
"SELECT set_config('app.tenant_id', $1, true)",
[tenantId]
);Use SET LOCAL ou configuração transacional para não vazar contexto entre conexões do pool.
Connection Pooling
Conexões são reutilizadas. Qualquer estado de sessão precisa ser limpo ou limitado à transação. Não associe permanentemente uma conexão compartilhada a um tenant.
Schema por tenant
O schema oferece separação maior, mas aumenta custo de migrations, conexões, catálogo e operação. Nunca construa search_path diretamente com entrada não validada.
Banco por tenant
Esse modelo melhora isolamento, backup e residência de dados, porém exige roteamento de conexões, muitos pools e automação de provisionamento.
Evite criar um pool ilimitado por tenant. Use cache com limite, expiração e fechamento de conexões.
Cache
A chave de cache deve incluir tenant.
const key = `tenant:${tenantId}:project:${projectId}`;Uma chave sem tenant pode vazar dados mesmo quando o banco está protegido.
Filas e eventos
Inclua tenantId no envelope e valide antes de processar. Use a mesma informação para particionamento, autorização e observabilidade.
{
"id": "evt-1",
"type": "project.created.v1",
"tenantId": "t-7",
"data": { "projectId": "p-3" }
}Autorização
Pertencer ao tenant não concede acesso a todos os recursos. Combine isolamento com RBAC ou ABAC.
Leia RBAC no Node.js e ABAC no Node.js.
Usuários em vários tenants
Modele associação separada entre usuário e tenant, com papéis próprios. A troca de tenant deve gerar novo contexto autorizado, não apenas alterar um valor no frontend.
Configuração por tenant
Planos, limites, integrações e feature flags podem variar. Valide configurações e mantenha defaults explícitos. Não leia configurações sensíveis de um tenant ao processar outro.
Rate limiting
Aplique limites por tenant, usuário e rota. Um tenant ruidoso não deve consumir toda a capacidade compartilhada.
Jobs e cron
Jobs globais precisam iterar tenants com paginação e limites. Registre checkpoint para retomar sem reprocessar todo o conjunto.
Migrations
No banco compartilhado, a migration é única. Em schema ou banco por tenant, execute versões de forma controlada e acompanhe tenants pendentes.
Consulte Database Migrations no Node.js.
Logs e métricas
Inclua tenant ID técnico em logs, traces e métricas, evitando nomes e dados pessoais. Cuidado com cardinalidade excessiva em métricas.
Backups e exclusão
Defina como restaurar ou excluir um tenant. Em banco compartilhado, restauração seletiva é mais complexa. Em banco dedicado, o ciclo de vida é mais isolado.
Administração
Ferramentas internas que atravessam tenants precisam de autorização reforçada, auditoria e motivo de acesso. Evite “modo superadmin” sem rastreabilidade.
Testes de isolamento
- usuário de um tenant consulta ID de outro;
- cache possui IDs iguais em tenants diferentes;
- job recebe tenant incorreto;
- conexão do pool mantém contexto anterior;
- evento sem tenant;
- exportação e backup;
- admin acessa com auditoria;
- RLS bloqueia query sem contexto.
Observabilidade
Monitore consumo, latência, erros e quotas por tenant. Tenants muito grandes podem precisar de particionamento ou infraestrutura dedicada.
Erros comuns
- confiar em
tenantIdenviado pelo cliente; - query sem filtro;
- cache sem namespace;
- estado de sessão vazando no pool;
- evento sem tenant;
- logs com dados sensíveis;
- pool ilimitado por tenant;
- admin sem auditoria;
- testar apenas caminho de sucesso.
Checklist
- tenant vem de identidade validada;
- queries e chaves incluem tenant;
- RLS é considerada como defesa adicional;
- cache e filas mantêm isolamento;
- pool não vaza contexto;
- autorização é aplicada dentro do tenant;
- migrations e backups possuem estratégia;
- testes tentam cruzar limites.
Conclusão
Multi-Tenancy no Node.js exige isolamento em todas as camadas, não apenas no banco. O tenant deve ser parte da identidade operacional da requisição e de qualquer mensagem assíncrona.
Comece pelo modelo mais simples que atende aos requisitos e adicione defesas como RLS, quotas e auditoria. Planeje desde cedo migrations, backups e tenants de grande porte.
Consulte a documentação de Row-Level Security do PostgreSQL e Connection Pooling no Node.js.



