Este artigo é um guia prático para quem deseja criar um SaaS multi-tenant moderno, com autenticação, controle de acesso baseado em papéis (RBAC), organizações, convites, projetos e permissões implementadas tanto no backend quanto no frontend. Aqui você encontra dicas de arquitetura, exemplos de código, sugestões de modelagem e melhores práticas para estruturar seu próprio SaaS, servindo como referência para projetos onde usuários podem criar e gerenciar projetos dentro de organizações.
Visão Geral da Arquitetura
-
Monorepo: Organização do código em múltiplos pacotes (backend, frontend, libs compartilhadas).
-
Backend: API REST com Fastify, Prisma, autenticação JWT, RBAC e documentação automática com Swagger.
-
Frontend: Next.js, consumo da API, checagem de permissões no client.
-
Banco de Dados: PostgreSQL, modelagem multi-tenant (usuários, organizações, membros, projetos).
-
RBAC: Controle de acesso por papéis (Owner, Admin, Member, Billing, Anonymous) usando CASL.
Estrutura do Monorepo
/apps
/api # Backend (Fastify, Prisma)
/web # Frontend (Next.js)
...
/packages
/auth # Lógica de autenticação e RBAC compartilhada
/env # Configuração de variáveis de ambiente
...
/prisma # Schemas e migrations do banco
Vantagens do monorepo:
- Compartilhamento de código (ex: regras de permissão).
- Deploy e testes unificados.
- Facilidade de manutenção.
Modelagem de Dados
Principais Entidades:
-
User: Usuário do sistema.
-
Organization: Organização (tenant).
-
Member: Relaciona usuário à organização com um papel (role).
-
Project: Projeto criado dentro de uma organização.
-
Invite: Convite para novos membros.
Exemplo de Schema (Prisma)
model User {
id String @id @default(uuid())
name String?
email String @unique
password String?
members Member[]
// ...
}
model Organization {
id String @id @default(uuid())
name String
slug String @unique
owner User @relation(fields: [ownerId], references: [id])
ownerId String
members Member[]
projects Project[]
// ...
}
model Member {
id String @id @default(uuid())
role Role
organization Organization @relation(fields: [organizationId], references: [id])
organizationId String
user User @relation(fields: [userId], references: [id])
userId String
}
model Project {
id String @id @default(uuid())
name String
description String
organization Organization @relation(fields: [organizationId], references: [id])
organizationId String
owner User @relation(fields: [ownerId], references: [id])
ownerId String
}
Backend: API, Autenticação e RBAC
Autenticação:
-
Suporte a e-mail/senha e OAuth (ex: GitHub).
-
JWT para autenticação de rotas protegidas.
-
Recuperação de senha via e-mail.
-
RBAC (Role-Based Access Control)
-
Papéis: Owner, Admin, Member, Billing, Anonymous.
-
Permissões centralizadas em um módulo compartilhado (/packages/auth).
Implementação de permissões com CASL:
export const permissions: Record<Role, PermissionsByRole> = {
ADMIN: (user, { can, cannot }) => {
can('manage', 'all')
cannot(['transfer_ownership', 'update'], 'Organization')
can(['transfer_ownership', 'update'], 'Organization', {
ownerId: { $eq: user.id },
})
},
MEMBER: (user, { can }) => {
can(['create', 'get'], 'Project')
can(['update', 'delete'], 'Project', { ownerId: { $eq: user.id } })
},
// ...
}
Rotas Principais
-
POST /users: Criação de usuário.
-
POST /organizations: Criação de organização.
-
POST /organizations/:slug/projects: Criação de projeto.
-
GET /organizations/:slug/projects: Listagem de projetos.
-
POST /invites: Envio de convite para membro.
Exemplo de Criação de Organização
app.post('/organizations', { ... }, async (request, reply) => {
const userId = await request.getCurrentUserId()
const { name, domain } = request.body
// Validações...
const organization = await prisma.organization.create({
data: {
name,
slug: createSlug(name),
ownerId: userId,
members: { create: { userId, role: 'ADMIN' } }
}
})
return reply.status(201).send({ organizationId: organization.id })
})
Frontend: Permissões e Experiência do Usuário
-
Next.js: Rotas protegidas, SSR/SSG, integração com API.
-
Checagem de Permissões: Antes de renderizar botões ou páginas, verifica se o usuário pode executar a ação.
Exemplo de Checagem de Permissão
const permissions = await ability()
if (permissions?.cannot('create', 'Project')) {
redirect('/dashboard')
}
Exemplo de Exibição Condicional
{
permissions?.can('create', 'Project') && (
<Button>
<Link href={`/org/${currentOrg}/create-project`}>Criar projeto</Link>
</Button>
)
}
Fluxos Principais do SaaS
Cadastro e Login:
- Usuário cria conta ou faz login via OAuth.
Criação de Organização:
- Usuário cria uma organização e se torna admin.
Convite de Membros:
- Admin convida membros por e-mail, atribuindo papéis.
Criação de Projetos:
- Membros com permissão criam projetos dentro da organização.
Gestão de Permissões
- Ações disponíveis variam conforme o papel do usuário.
Billing (Opcional)
- Gestão de cobrança por organização, projeto ou membro.
Exemplos de Código
Definição de Permissões (CASL)
import { AbilityBuilder, createMongoAbility } from '@casl/ability'
export function defineAbilityFor(user) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility)
if (user.role === 'ADMIN') {
can('manage', 'all')
}
if (user.role === 'MEMBER') {
can('create', 'Project')
can(['update', 'delete'], 'Project', { ownerId: user.id })
}
// ...
return build()
}
Middleware de Autenticação
app.addHook('preHandler', async (request) => {
request.getCurrentUserId = async () => {
const { sub } = await request.jwtVerify()
return sub
}
})
Criação de Projeto (Backend)
app.post('/organizations/:slug/projects', { ... }, async (request, reply) => {
const { slug } = request.params
const userId = await request.getCurrentUserId()
// Busca organização, valida permissão...
const project = await prisma.project.create({
data: {
name: request.body.name,
description: request.body.description,
organizationId: organization.id,
ownerId: userId
}
})
return reply.status(201).send({ projectId: project.id })
})
Boas Práticas e Dicas Finais
Centralize as regras de permissão em um único módulo compartilhado. Use migrations e schemas versionados para o banco de dados.
Documente a API com Swagger/OpenAPI.
Implemente testes automatizados para fluxos críticos (auth, RBAC, criação de entidades).
Utilize variáveis de ambiente para secrets e configurações sensíveis.
Garanta que o frontend respeite as permissões do backend, evitando exposição de ações não permitidas.
Pense em extensibilidade: novos papéis, entidades e integrações podem ser adicionados facilmente.
Conclusão
Construir um SaaS multi-tenant robusto exige atenção à arquitetura, segurança e experiência do usuário. Com uma base sólida de RBAC, monorepo e boas práticas, você estará pronto para escalar seu produto e atender múltiplos clientes com diferentes necessidades e níveis de acesso.