TUTORIAL 05 · ADVANCED

Build a Multi-Tenant SaaS

Add Tenant identity and tenant-scoped Ticket access so one generated ServiceDesk can safely serve multiple organizations.

RAIQL 1.030 minProject: ServiceDesk
By the end of this tutorial
  • Declare a Tenant entity
  • Scope Ticket by tenantId
  • Combine tenant isolation with ownership
  • Understand the x-raiql-tenant-id request context

Add tenant identity

CREATE TENANT ENTITY establishes the tenant identity model. TENANT Ticket BY tenantId makes tenantId a server-managed scope in generated PostgreSQL APIs.

app.raiql
CREATE APPLICATION "RAIQL ServiceDesk";
TYPE WEB;

USE STACK
  FRONTEND NEXTJS,
  BACKEND NEXTJS,
  DATABASE POSTGRESQL;

CREATE TENANT ENTITY Tenant;
CREATE USER ENTITY User;

CREATE ENTITY Ticket (
  id UUID PRIMARY,
  title STRING REQUIRED,
  description STRING,
  status STRING REQUIRED,
  priority STRING REQUIRED,
  ownerId UUID REQUIRED,
  tenantId UUID REQUIRED
);

CREATE CRUD Tenant;
CREATE API Tenant;
CREATE CRUD Ticket;
CREATE API Ticket;

ADD AUTHENTICATION EMAIL_PASSWORD, JWT;
ADD ROLE ADMIN, AGENT, USER;
DEFAULT ROLE USER;

RELATE Ticket.ownerId TO User.id;
RELATE Ticket.tenantId TO Tenant.id;
OWN Ticket BY ownerId;
TENANT Ticket BY tenantId;

ALLOW ADMIN CREATE Ticket, READ Ticket, UPDATE Ticket, DELETE Ticket, CREATE Tenant, READ Tenant, UPDATE Tenant, DELETE Tenant;
ALLOW AGENT READ Ticket, UPDATE Ticket;
ALLOW USER CREATE OWN Ticket, READ OWN Ticket, UPDATE OWN Ticket;

ENVIRONMENT development, staging, production;
SECRET DATABASE_URL;
SECRET RAIQL_JWT_SECRET;

BUILD;
DEPLOY DOCKER;

Tenant and ownership are different

  • Tenant scope answers: which organization may see this record?
  • Ownership answers: which user owns this record?
  • A request can be both tenant-scoped and owner-scoped
  • ADMIN permissions can still be broader within the generated policy

Generate the SaaS application

terminal
raiql validate
raiql generate
raiql dev --env development

Send tenant context

Generated tenant-aware APIs use x-raiql-tenant-id. The generated TypeScript SDK can also carry tenant context so application callers do not hand-build the header on every request.

HTTP
GET /api/ticket
Authorization: Bearer <jwt>
x-raiql-tenant-id: <tenant-uuid>

Isolation rule

RAIQL ruleTenant-scoped generated CRUD must never trust tenantId from the request body. The adapter applies tenant context server-side.