Todo SaaS B2B começa parecendo um sistema de um usuário só. Então chega o primeiro cliente com três funcionários. Depois, uma agência pede acesso a cinco empresas. Logo surgem convites, equipes, papéis, filiais e a pergunta que define o risco do produto: esta pessoa pode fazer esta operação dentro desta organização?
No SurrealDB, um SaaS multi-tenant pode representar usuários, organizações e papéis como registros; vínculos de trabalho como relações de grafo; e permissões como dados consultáveis. Isso aproxima o controle de acesso do modelo do negócio — desde que isolamento e testes sejam tratados como requisitos, não como detalhes do painel.
O que é multi-tenancy?
Multi-tenancy é a arquitetura em que uma aplicação atende várias organizações, chamadas tenants, mantendo dados e permissões separados. Uma mesma pessoa pode pertencer a mais de uma organização e ter um papel diferente em cada uma.
Exemplos brasileiros aparecem em quase todo setor:
- um escritório contábil atende várias empresas;
- uma agência gerencia clientes diferentes;
- uma rede de clínicas possui unidades e profissionais;
- uma escola divide turmas, coordenação e responsáveis;
- um software financeiro separa contas por CNPJ.
O erro clássico é colocar organization_id nas tabelas e considerar o problema resolvido. Esse campo ajuda no escopo, mas não define sozinho quem pode convidar pessoas, exportar relatórios, gerenciar cobrança ou enxergar uma filial.
Um modelo que separa identidade, vínculo e autorização
Uma base sustentável usa cinco conceitos:
| Conceito | Responsabilidade |
|---|---|
user | identidade da pessoa |
organization | empresa ou espaço de trabalho |
permission | ação permitida, como billing/read |
role | conjunto de permissões dentro da organização |
member_of | vínculo entre pessoa e organização, com papel e status |
Esse desenho evita colocar um campo global is_admin no usuário. Alguém pode ser proprietário da própria empresa, analista numa empresa cliente e não ter acesso a uma terceira.
Schema base no SurrealDB
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD name ON user TYPE string;
DEFINE FIELD email ON user TYPE string
ASSERT string::is_email($value);
DEFINE INDEX user_email ON user FIELDS email UNIQUE;
DEFINE TABLE organization SCHEMAFULL;
DEFINE FIELD name ON organization TYPE string;
DEFINE FIELD slug ON organization TYPE string;
DEFINE FIELD owner ON organization TYPE record<user>;
DEFINE FIELD created_at ON organization TYPE datetime
DEFAULT time::now();
DEFINE INDEX organization_slug ON organization FIELDS slug UNIQUE;
DEFINE TABLE permission SCHEMAFULL;
DEFINE FIELD name ON permission TYPE string;
DEFINE FIELD description ON permission TYPE string;
DEFINE INDEX permission_name ON permission FIELDS name UNIQUE;
DEFINE TABLE role SCHEMAFULL;
DEFINE FIELD organization ON role TYPE record<organization>;
DEFINE FIELD name ON role TYPE string;
DEFINE FIELD permissions ON role TYPE array<record<permission>> DEFAULT [];
DEFINE INDEX role_name_per_org ON role
FIELDS organization, name UNIQUE;
DEFINE TABLE member_of SCHEMAFULL
TYPE RELATION IN user OUT organization;
DEFINE FIELD role ON member_of TYPE record<role>;
DEFINE FIELD status ON member_of TYPE string
ASSERT $value IN ['invited', 'active', 'suspended'];
DEFINE FIELD joined_at ON member_of TYPE option<datetime>;
DEFINE INDEX membership_unique ON member_of FIELDS in, out UNIQUE;A relação member_of é mais que uma tabela de junção: ela representa o contrato atual entre uma pessoa e uma organização. Status, cargo, data de entrada e papel pertencem ao vínculo, não ao usuário.
O template oficial de Organizations e Teams do SurrealKit segue a mesma direção e inclui organizações, papéis, permissões, convites e extensões para equipes, unidades e subsidiárias.
[ CONTEÚDO_INTEGRAL_DISPONÍVEL ]
Continuar: permissões, isolamento e testes multi-tenant CLIQUE PARA DESBLOQUEAR A LEITURA
Construindo aplicações robustas com arquitetura de alto nível e infraestrutura otimizada na edge do Cloudflare Workers com SurrealDB.
Segurança de dados e conformidade com privacidade desde o primeiro dia de código com tratamento de estados e sessões resilientes.
Métricas de performance para monitorar a experiência do usuário e otimizar custos operacionais em produção com dashboards estruturados.
Construindo aplicações robustas com arquitetura de alto nível e infraestrutura otimizada na edge do Cloudflare Workers com SurrealDB.
Segurança de dados e conformidade com privacidade desde o primeiro dia de código com tratamento de estados e sessões resilientes.
Métricas de performance para monitorar a experiência do usuário e otimizar custos operacionais em produção com dashboards estruturados.
Permissões como dados deixam o SaaS evoluir
Quando permissões ficam presas num enum do código, cada novo recurso exige alteração e deploy coordenados. Como registros, elas podem ser criadas em seed e agrupadas em papéis por organização.
UPSERT permission:members_read CONTENT {
name: 'members/read',
description: 'Visualizar membros da organização'
};
UPSERT permission:members_manage CONTENT {
name: 'members/manage',
description: 'Convidar, alterar e suspender membros'
};
UPSERT permission:billing_read CONTENT {
name: 'billing/read',
description: 'Visualizar plano, faturas e cobrança'
};
UPSERT permission:billing_manage CONTENT {
name: 'billing/manage',
description: 'Alterar plano e dados de cobrança'
};Um papel “Financeiro” pode ter leitura e gestão de cobrança sem receber acesso a membros. Um papel “Gestor da unidade” pode enxergar somente dados de uma filial. O nome amigável do papel pode variar; as permissões continuam sendo contratos estáveis.
Como verificar uma autorização
Autorização deve responder a três perguntas:
- qual é o usuário autenticado?
- qual é a organização da operação?
- o vínculo ativo aponta para um papel com a permissão necessária?
DEFINE FUNCTION fn::can(
$user: record<user>,
$organization: record<organization>,
$permission_name: string
) {
RETURN array::len(
SELECT VALUE id
FROM member_of
WHERE in = $user
AND out = $organization
AND status = 'active'
AND $permission_name IN role.permissions.name
LIMIT 1
) > 0;
};Adapte e valide a sintaxe à versão usada no projeto. Mais importante que copiar a função é testar o comportamento: acesso permitido no tenant correto, negado no tenant errado e negado quando o vínculo está suspenso.
O isolamento precisa chegar a cada registro
Considere uma tabela de projetos. Todo projeto deve pertencer a uma organização, e toda consulta pública precisa ser escopada por ela.
DEFINE TABLE project SCHEMAFULL;
DEFINE FIELD organization ON project TYPE record<organization>;
DEFINE FIELD name ON project TYPE string;
DEFINE FIELD status ON project TYPE string
ASSERT $value IN ['draft', 'active', 'archived'];
DEFINE FIELD created_by ON project TYPE record<user>;
DEFINE FIELD created_at ON project TYPE datetime DEFAULT time::now();
DEFINE INDEX project_by_org ON project FIELDS organization;Não aceite organization do formulário como verdade. O servidor deve derivar o tenant do contexto autorizado e verificar o vínculo. Caso contrário, trocar um ID na requisição pode mover ou criar dados dentro de outra empresa.
WHERE organization = $org só é seguro se $org veio de um contexto que o usuário pode acessar. Um parâmetro enviado pelo navegador não prova pertencimento.Convites precisam ser tratados como credenciais
Um convite é uma autorização pendente. Ele deve ter token imprevisível, validade, uso único, organização, papel e destinatário. Evite guardar o token puro se um hash atender ao fluxo. Ao aceitar:
- valide expiração e uso anterior;
- confirme a identidade ou o e-mail esperado;
- crie o vínculo de forma idempotente;
- marque o convite como usado na mesma transação;
- registre o evento para auditoria.
Enviar o mesmo formulário duas vezes não pode criar duas memberships. O índice único em member_of(in, out) funciona como última barreira, mas o fluxo também precisa responder de forma previsível à repetição.
Equipes, unidades e subsidiárias: quando adicionar?
Não modele uma hierarquia corporativa inteira no primeiro dia.
- Equipes fazem sentido quando pessoas colaboram em subconjuntos de projetos.
- Unidades fazem sentido para filiais, departamentos ou regiões com dados próprios.
- Subsidiárias entram quando uma empresa-mãe precisa delegar acesso a organizações juridicamente ou operacionalmente separadas.
Comece com organização, membership e papel. Adicione outro nível quando existir uma pergunta concreta de autorização que o nível atual não consegue responder.
LGPD, auditoria e suporte
Multi-tenancy aumenta o impacto de qualquer falha: um bug pode expor dados de uma empresa para outra. Além de permissões, planeje:
- log de alterações sensíveis com ator, tenant e horário;
- exportação e exclusão com escopo correto;
- retenção de convites e contas suspensas;
- suporte administrativo sem “virar” qualquer usuário silenciosamente;
- mascaramento de dados em ambientes não produtivos;
- alertas para volume incomum de leitura ou exportação.
Logs também contêm dados. Não registre token de convite, sessão, documento completo ou corpo indiscriminado de requisições.
Testes que um SaaS multi-tenant precisa ter
Uma suíte mínima deve provar:
- membro ativo lê dados da própria organização;
- o mesmo membro não lê dados de outra organização;
- membro sem
billing/readnão vê cobrança; - membro suspenso perde acesso;
- convite expirado falha;
- convite repetido não duplica vínculo;
- usuário em duas organizações recebe permissões diferentes;
- trocar o ID da organização na requisição não contorna autorização;
- exclusão de usuário não deixa relações órfãs sem decisão explícita;
- tarefas internas não usam um tenant recebido sem validação.
Peça para a IA gerar esses testes, mas confira se eles exercitam a conexão com privilégios reais. Testar tudo como root apenas prova que root pode fazer tudo.
Perguntas frequentes sobre multi-tenancy no SurrealDB
Devo criar um banco por cliente?
Depende do contrato, escala e isolamento exigido. Um banco compartilhado com tenant explícito simplifica operação e visão agregada; bancos separados aumentam isolamento, mas multiplicam migrações, conexões e observabilidade. Não escolha sem mapear backup, suporte e custo.
Papel e permissão são a mesma coisa?
Não. Permissão representa uma ação; papel agrupa permissões. Isso permite que cada organização tenha papéis como “Financeiro” ou “Operações” sem mudar o código da aplicação.
Posso confiar apenas na API para separar tenants?
Uma API bem construída é uma camada importante, mas permissões e invariantes próximas dos dados criam defesa em profundidade. A falha de uma rota não deve transformar o banco inteiro em leitura pública.
Preciso de equipes desde o MVP?
Geralmente não. Implemente primeiro organização, vínculo e papel. Equipe só entra quando usuários realmente precisam compartilhar uma parte do tenant com regras diferentes.
Uma base pequena, mas impossível de atravessar
O melhor MVP multi-tenant não é o que tem a matriz de permissões mais sofisticada. É o que consegue provar que uma pessoa nunca atravessa a fronteira da organização errada.
Comece pelo guia SurrealDB para vibe coding, modele um único recurso com tenant obrigatório e escreva os testes negativos antes de construir o painel de administração.
Do vibe ao produto. Com método.



