Multi-tenant
Isolamento por tenant com middleware, header/subdomain e estrategias de schema.
O PlazerCLI suporta arquitetura multi-tenant quando voce habilita Organizations. O isolamento de dados garante que cada organizacao (tenant) so acesse seus proprios dados. O PlazerCLI implementa a estrategia de shared database com discriminator column.
Estrategia de isolamento
O PlazerCLI usa a coluna organizationId em todas as tabelas que devem ser isoladas por tenant:
// prisma/schema.prisma
model Project {
id String @id @default(cuid())
name String
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId])
}
model Document {
id String @id @default(cuid())
title String
content String?
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
@@index([organizationId])
}
Middleware de tenant
O middleware extrai o tenant do header ou do JWT e injeta no request:
// apps/api/src/middleware/tenant.middleware.ts
export function tenantMiddleware(req: Request, res: Response, next: NextFunction) {
// Estrategia 1: Header X-Tenant-ID
const tenantId = req.headers['x-tenant-id'] as string;
// Estrategia 2: Subdominio (tenant.app.com)
// const tenantId = req.hostname.split('.')[0];
// Estrategia 3: JWT claim (mais seguro)
// const tenantId = req.user?.orgId;
if (!tenantId) {
return res.status(400).json({ error: 'Tenant ID is required' });
}
req.tenantId = tenantId;
next();
}
// NestJS Guard equivalente:
@Injectable()
export class TenantGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const tenantId = request.user?.orgId || request.headers['x-tenant-id'];
if (!tenantId) throw new ForbiddenException('Tenant not found');
request.tenantId = tenantId;
return true;
}
}
Prisma com filtro automatico
O PlazerCLI gera um Prisma client estendido que aplica filtro de tenant automaticamente:
// apps/api/src/lib/prisma-tenant.ts
import { PrismaClient } from '@prisma/client';
export function createTenantPrisma(organizationId: string) {
const prisma = new PrismaClient();
return prisma.$extends({
query: {
$allModels: {
async findMany({ args, query }) {
args.where = { ...args.where, organizationId };
return query(args);
},
async findFirst({ args, query }) {
args.where = { ...args.where, organizationId };
return query(args);
},
async create({ args, query }) {
args.data = { ...args.data, organizationId };
return query(args);
},
async update({ args, query }) {
args.where = { ...args.where, organizationId };
return query(args);
},
async delete({ args, query }) {
args.where = { ...args.where, organizationId };
return query(args);
},
},
},
});
}
// Uso em um controller:
app.get('/api/projects', tenantMiddleware, async (req, res) => {
const db = createTenantPrisma(req.tenantId);
const projects = await db.project.findMany(); // automaticamente filtrado
res.json(projects);
});