Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Multi-Tenancy no Node.js

Atualizado em: 26 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

O Multi-Tenancy no Node.js permite que uma aplicação atenda várias organizações mantendo isolamento de dados, configuração, limites e identidade. Cada organização é um tenant, e todas compartilham parte da infraestrutura sem compartilhar informações indevidamente.

O principal risco não é desempenho, mas vazamento entre tenants. Um filtro ausente, cache mal indexado ou job sem contexto pode entregar dados de outra empresa. Por isso, o tenant precisa ser derivado da identidade autenticada e aplicado em todas as camadas.

Neste guia, você aprenderá modelos de banco, contexto por requisição, PostgreSQL, caches, filas, arquivos, rate limiting, observabilidade, migrações, testes e segurança.

O que é Multi-Tenancy?

Multi-tenancy é uma arquitetura em que uma aplicação serve vários clientes isolados. A documentação Multitenant solutions da Microsoft apresenta modelos e decisões. A documentação de Row Security do PostgreSQL mostra uma camada de isolamento no banco.

Para autorização, consulte JWT Seguro no Node.js. Para contexto assíncrono, veja AsyncLocalStorage no Node.js.

Tenant versus usuário

Um usuário pode pertencer a vários tenants e ter funções diferentes em cada um. Não trate userId como tenant ID.

Identificação do tenant

Opções comuns:

  • claim validado no token;
  • subdomínio;
  • domínio customizado;
  • seleção de organização na sessão;
  • header interno assinado;
  • segmento de URL, quando autorizado.

Não confie no corpo

{
  "tenantId": "outra-empresa",
  "name": "Documento"
}

O cliente não deve escolher livremente o tenant. Derive da sessão e ignore ou valide o valor enviado.

Contexto da requisição

const context = {
  requestId,
  userId: identity.userId,
  tenantId: identity.activeTenantId,
  roles: identity.roles
};

Propague esse contexto para repositories, logs e chamadas internas.

AsyncLocalStorage

const { AsyncLocalStorage } = require('node:async_hooks');
const storage = new AsyncLocalStorage();

app.use((req, res, next) => {
  const context = authenticateAndResolveTenant(req);

  storage.run(context, () => next());
});

O contexto não substitui parâmetros explícitos em operações críticas, mas ajuda na observabilidade.

Modelo 1: banco compartilhado

Todas as tabelas possuem tenant_id:

CREATE TABLE projects (
  tenant_id UUID NOT NULL,
  id UUID NOT NULL,
  name TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL,
  PRIMARY KEY (tenant_id, id)
);

É econômico e simples de operar, mas exige filtros corretos.

Chave composta

Use tenant na chave e em foreign keys:

CREATE TABLE tasks (
  tenant_id UUID NOT NULL,
  id UUID NOT NULL,
  project_id UUID NOT NULL,
  title TEXT NOT NULL,
  PRIMARY KEY (tenant_id, id),
  FOREIGN KEY (tenant_id, project_id)
    REFERENCES projects (tenant_id, id)
);

Isso impede relacionar tarefa de um tenant a projeto de outro.

Unique por tenant

UNIQUE (tenant_id, slug)

Um slug pode existir em organizações diferentes.

Índices

Comece índices com tenant quando as queries filtram por ele:

CREATE INDEX idx_projects_tenant_created
ON projects (tenant_id, created_at DESC);

Queries sempre filtradas

SELECT id, name
FROM projects
WHERE tenant_id = $1
  AND id = $2;

Não busque apenas por ID, mesmo que UUID pareça imprevisível.

Repository seguro

class ProjectRepository {
  constructor(pool) {
    this.pool = pool;
  }

  async findById({ tenantId, projectId }) {
    const result = await this.pool.query(`
      SELECT id, name
      FROM projects
      WHERE tenant_id = $1
        AND id = $2
    `, [tenantId, projectId]);

    return result.rows[0] || null;
  }
}

Row-Level Security

RLS adiciona proteção no PostgreSQL. O próximo artigo aprofunda o tema.

Modelo 2: schema por tenant

Cada tenant recebe um schema:

tenant_a.projects
tenant_b.projects

O isolamento melhora, mas migrations, conexões e quantidade de objetos ficam mais complexos.

search_path

Alterar search_path por requisição pode ser perigoso com pool. Use transação e SET LOCAL, nomes validados e cleanup garantido.

SQL injection em identificadores

Parâmetros SQL não protegem nomes de schema. Use mapeamento interno e quoting seguro; nunca concatene subdomínio diretamente.

Modelo 3: banco por tenant

Cada organização possui banco próprio. O isolamento e restauração são melhores, mas operações, migrations e pools se multiplicam.

Pool por tenant

Não mantenha milhares de pools abertos. Use cache limitado, expiração e proxy de conexões.

Tenant grande isolado

Uma estratégia híbrida mantém pequenos tenants no banco compartilhado e move clientes grandes para bancos dedicados.

Catálogo de tenants

tenant_id | database_key | region | status | plan

Não armazene senha em texto. Resolva credenciais por Secret.

Roteamento de banco

const database = await tenantDatabaseResolver.resolve(tenantId);

O resolver deve validar status, região e autorização.

Transações

Uma transação deve permanecer no banco do tenant. Não tente uma transação local entre bancos distintos.

Veja Transações PostgreSQL no Node.js.

Cache

Toda chave precisa incluir tenant:

tenant:${tenantId}:project:${projectId}

Uma chave sem tenant pode vazar dados.

Cache de autorização

Inclua tenant, user e versão das permissões. Invalide quando a associação muda.

Cache compartilhado

Defina quotas e evite que um tenant expulse todos os dados dos demais.

Arquivos

Use prefixo por tenant:

tenants/{tenantId}/documents/{documentId}

O bucket policy ou URL assinada também deve restringir o prefixo.

Nomes de arquivo

Não use nome enviado pelo usuário como caminho. Gere IDs e preserve o nome apenas como metadado.

Filas

Inclua tenant na mensagem:

{
  "messageId": "...",
  "tenantId": "...",
  "type": "report.generate",
  "data": {}
}

O consumidor valida o tenant antes de acessar dados.

Contexto não deve vazar entre jobs

Crie um novo escopo por mensagem. Não reutilize AsyncLocalStorage de outro job.

Prioridade e fairness

Um tenant com milhões de jobs pode atrasar todos. Use quotas, filas separadas ou scheduling justo.

Rate limiting

Limite por tenant e usuário. Consulte Rate Limiting no Node.js.

Quotas

Planos podem limitar:

  • usuários;
  • armazenamento;
  • requisições;
  • jobs;
  • projetos;
  • exportações;
  • retenção.

Quota atômica

Não conte e depois insira sem proteção. Use transação, constraint ou update condicional.

Feature flags

Uma flag pode variar por tenant, mas autorização e plano ainda precisam ser validados.

Veja Feature Flags no Node.js.

Configuração por tenant

Valide schema, defaults e tamanho. Não permita configuração arbitrária executar código ou selecionar URL interna.

Custom domains

Mapeie domínio para tenant após validar Host. Proteja contra host header injection.

Subdomínio

acme.example.com pode resolver tenant, mas o token precisa confirmar que o usuário pertence à organização.

Troca de tenant

Ao trocar organização ativa, gere nova sessão ou atualize contexto autenticado. Não aceite apenas um header frontend.

Impersonação

Suporte administrativo precisa de consentimento, justificativa, tempo limitado e auditoria. Nunca use senha do cliente.

Autorização por recurso

O tenant limita o conjunto, mas o usuário ainda pode não acessar todos os projetos.

Jobs globais

Um job administrativo que percorre tenants deve processar em lotes e definir contexto explicitamente para cada um.

Noisy neighbor

Um tenant pode saturar CPU, banco ou filas. Monitore consumo e aplique limites.

Connection pool

Conexões são recurso compartilhado. Limite concorrência por tenant para evitar monopolização.

Query pesada

Relatórios grandes devem ter timeout, fila e quota. Não execute sincronicamente na API.

Particionamento

Tabelas enormes podem ser particionadas por hash do tenant ou tempo. Avalie índices e manutenção.

Região de dados

Alguns tenants exigem residência regional. O catálogo pode direcionar banco e storage para a região apropriada.

Criptografia

Chaves por tenant podem reduzir impacto de comprometimento e permitir rotação independente.

Segredos por tenant

Integrações externas podem ter credenciais próprias. Armazene em gerenciador de segredos e não em JSON comum.

Migrations compartilhadas

No banco compartilhado, uma migration afeta todos. Use expand-and-contract.

Consulte Migrações de Banco no Node.js.

Migrations por schema ou banco

Execute com controle de concorrência, checkpoints e métricas. Não tente migrar milhares de tenants ao mesmo tempo.

Versões diferentes

Evite manter cada tenant em um schema de versão diferente por muito tempo. A matriz de compatibilidade cresce rapidamente.

Onboarding

  1. Criar tenant.
  2. Provisionar recursos.
  3. Aplicar configuração.
  4. Criar administrador.
  5. Validar quotas.
  6. Registrar auditoria.
  7. Ativar acesso.

Provisionamento idempotente

Retries não devem criar bancos, buckets ou usuários duplicados.

Offboarding

Desative acesso, exporte quando necessário, aplique retenção e exclua dados com processo auditado.

Backup e restore

No banco compartilhado, restaurar um tenant isolado é difícil. Prepare exportação lógica e ferramentas de recuperação.

Exclusão

Delete por tenant precisa considerar tabelas, arquivos, cache, filas, backups e analytics.

Observabilidade

Logs devem incluir tenant ID, mas métricas não podem usar milhares de tenants como label em todas as séries.

Cardinalidade

Use métricas agregadas por plano ou região. Para investigação por tenant, use logs ou sistema analítico.

Logs

Inclua tenant, request ID e user ID de forma controlada. Não registre dados de negócio sensíveis.

Consulte Logs com Pino no Node.js.

Tracing

Tenant pode ser atributo do span, respeitando políticas de privacidade e cardinalidade da plataforma.

Métricas

Monitore:

  • requisições por plano;
  • erros de isolamento;
  • queries sem tenant;
  • uso de pool;
  • jobs por tenant;
  • quota excedida;
  • latência;
  • armazenamento.

Auditoria

Registre alteração de membros, permissões, configurações, exportações e impersonação.

Testes unitários

Repositories devem exigir tenantId no tipo e nos parâmetros.

Teste cruzado

test('tenant A não acessa projeto do tenant B', async () => {
  const project = await createProject({ tenantId: tenantB });

  const result = await repository.findById({
    tenantId: tenantA,
    projectId: project.id
  });

  assert.equal(result, null);
});

Teste de cache

Crie IDs iguais em dois tenants e confirme chaves separadas.

Teste de fila

Envie jobs intercalados e confirme que cada um usa o contexto correto.

Teste de pool

Se usar search_path ou variável de sessão, confirme reset após cada transação.

Teste de propriedade

Gere operações aleatórias em vários tenants e verifique que resultados nunca cruzam fronteiras.

Static analysis

Regras de lint podem proibir repositories sem tenant e acesso direto ao pool fora da camada segura.

Erros comuns

  • Tenant vindo do corpo: cliente escolhe outra organização.
  • Query por ID apenas: dados vazam.
  • Cache sem tenant: resposta é compartilhada.
  • Foreign key sem tenant: relações cruzadas aparecem.
  • Contexto global mutável: requisições se misturam.
  • Pool por milhares de tenants: conexões explodem.
  • Métrica com tenant label: cardinalidade cresce.

Boas práticas

  • Derive tenant da identidade.
  • Inclua tenant em todas as chaves.
  • Use constraints compostas.
  • Adicione RLS quando adequado.
  • Separe cache e storage.
  • Propague contexto em jobs.
  • Aplique quotas.
  • Teste isolamento cruzado.
  • Audite ações privilegiadas.
  • Planeje backup e exclusão.

Conclusão

O Multi-Tenancy no Node.js permite compartilhar infraestrutura mantendo cada organização isolada. O tenant deve fazer parte da identidade, das queries, das chaves de cache, dos arquivos e das mensagens.

A segurança depende de defesa em camadas: filtros explícitos, constraints compostas, RLS, contexto correto e testes cruzados. Com quotas, observabilidade e processos de onboarding e exclusão, a arquitetura escala sem transformar uma economia de infraestrutura em risco de vazamento entre clientes.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita