Ir al contenido

Tutorial 2 — Multi-Tenant App with Auth

Esta página aún no está disponible en tu idioma.

This tutorial builds a multi-tenant SaaS app using three PyGo native packages working together: multitenancy, auth, and cache.

A SaaS user management app where:

  • Each tenant (company) has isolated data
  • Users log in via email + password
  • JWT tokens expire after 24 hours
  • Roles: user, admin, superadmin
myapp/
├── app/
│ ├── web/
│ │ └── saas.pgo ← Models, handlers, routes
│ └── templates/
│ └── dashboard.html ← Protected dashboard
└── pygo.toml

📝 Step 1: Define Models & Initialize Services

Section titled “📝 Step 1: Define Models & Initialize Services”

Create app/web/saas.pgo:

# Import native packages
from multitenancy import Store as TenantStore
from auth import Manager as AuthManager
from cache import NewMemoryCache
# Initialize services (runs on startup)
tenants = TenantStore()
auth = AuthManager(jwt_secret="CHANGE_ME_IN_PRODUCTION")
cache = NewMemoryCache()
# Register a tenant
tenants.register(id="acme", name="Acme Corp", plan="pro")
model User:
id: UUID
email: Email
name: String
role: String = "user"
tenant_id: UUID
created: DateTime
model Company:
id: UUID
name: String
plan: Enum(free, pro, enterprise)
created: DateTime
# Detect tenant from subdomain or header
handler dashboard:
dashboard(user_id: UUID) -> Dict:
user = User.find(user_id)
tenant = tenants.from_request(request)
return {
"user": user,
"tenant": tenant,
}
# Auth routes
handler login:
login(email: Email, password: String) -> String:
user = User.where(email=email).first()
token = auth.generate_token(user)
auth.create_session(response, user)
return redirect("/dashboard")
handler logout:
logout() -> String:
auth.destroy_session(request, response)
return redirect("/")
# Protected route (middleware checks session)
route GET /dashboard -> auth.require_auth -> dashboard
route POST /login -> login
route POST /logout -> auth.require_auth -> logout

Create app/templates/dashboard.html:

<!-- auth.require_auth middleware injects the user -->
<div x-data="{ user: {{ user | json }} }">
<h1>Welcome, <span x-text="user.name"></span>!</h1>
<p>Tenant: <span x-text="tenant.name"></span></p>
<p>Plan: <span x-text="tenant.plan"></span></p>
<form @submit.prevent="fetch('/logout', { method: 'POST' })">
<button>Logout</button>
</form>
</div>
Terminal window
# Start the app
pygo dev
# Login as a user
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "email=user@example.com&password=secret"
# Access protected route (needs session cookie)
curl -b cookies.txt http://localhost:8080/dashboard

PyGo supports multiple multi-tenancy strategies — all built into the multitenancy package:

Strategy Isolation Level When to Use
tenant_id (default) Shared DB, row-level Small to medium SaaS
schema Shared DB, separate schemas Medium SaaS, isolation needed
database Separate DB per tenant Enterprise, strict isolation
# Choose strategy in pygo.toml
[multitenancy]
strategy = "schema" # or "database" or "tenant_id"

The auth package provides built-in role checks:

# Only admins can access
route GET /admin -> auth.require_role("admin") -> admin_panel
# Superadmins bypass all checks
route GET /superadmin -> auth.require_role("superadmin") -> super_panel

Tenant detection happens automatically:

  1. Subdomain: acme.myapp.pygo → tenant acme
  2. Header: X-Tenant-ID: acme → tenant acme
  3. Path: /acme/dashboard → tenant acme