Multi Tenant README - nself-org/nchat GitHub Wiki
nself-chat now supports full multi-tenant SaaS deployment with:
-
Subdomain Routing:
tenant1.nchat.app,tenant2.nchat.app -
Custom Domains:
chat.acme.com(optional) - Schema-Level Isolation: Each tenant gets isolated PostgreSQL schema
- Stripe Billing: Integrated subscription management
- Resource Limits: Per-plan usage enforcement
- Usage Tracking: Real-time monitoring and analytics
# .env.local
ENABLE_MULTI_TENANCY=true
NEXT_PUBLIC_BASE_DOMAIN=nchat.appcd .backend
nself db migrate up 030_multi_tenant_system.sql# .env.local
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...curl -X POST http://localhost:3000/api/tenants/create \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"slug": "acme",
"ownerEmail": "[email protected]",
"ownerName": "John Doe",
"ownerPassword": "securepass123",
"plan": "free",
"trial": true
}'Visit: http://acme.localhost:3000 (development)
Or: https://acme.nchat.app (production)
public (global)
├── tenants → Tenant metadata
├── tenant_usage → Usage statistics
├── tenant_settings → Configuration
├── tenant_invitations → Invite system
├── tenant_audit_logs → Audit trail
└── stripe_webhooks → Billing events
tenant_acme (isolated)
├── nchat_users → Acme's users
├── nchat_channels → Acme's channels
├── nchat_messages → Acme's messages
└── ... → All chat tables
tenant_demo (isolated)
├── nchat_users
├── nchat_channels
└── ...
1. User → acme.nchat.app
2. Middleware → Extract subdomain "acme"
3. Database → Fetch tenant metadata
4. Context → Set X-Tenant-Id, X-Tenant-Schema
5. Queries → All scoped to tenant_acme schema
6. Response → Tenant-branded UI
Core service for tenant CRUD operations:
import { getTenantService } from '@/lib/tenants/tenant-service'
const service = getTenantService()
// Create tenant
const tenant = await service.createTenant({
name: 'Acme Corp',
slug: 'acme',
ownerEmail: '[email protected]',
// ...
})
// Get tenant
const tenant = await service.getTenantBySlug('acme')
// Update tenant
await service.updateTenant(tenant.id, {
name: 'Acme Corporation',
})
// Delete tenant
await service.deleteTenant(tenant.id)Stripe integration for subscriptions:
import { getStripeBillingService } from '@/lib/billing/stripe-service'
const billing = getStripeBillingService()
// Create checkout session
const session = await billing.createCheckoutSession(tenant, 'pro', 'monthly', successUrl, cancelUrl)
// Manage subscription
await billing.updateSubscription(tenant, 'enterprise', 'yearly')
await billing.cancelSubscription(tenant)Resolves tenant from domain:
// Automatic tenant resolution in middleware
export async function middleware(request: NextRequest) {
const config = getDefaultTenantConfig()
const response = await tenantMiddleware(request, config)
return response
}React context for tenant data:
import { useTenant, useTenantFeature } from '@/contexts/tenant-context'
function MyComponent() {
const { tenant, isLoading } = useTenant()
const hasVideoCalls = useTenantFeature('videoCalls')
return (
<div>
<h1>{tenant?.name}</h1>
{hasVideoCalls && <VideoCallButton />}
</div>
)
}- 10 users
- 5 channels
- 1 GB storage
- 1,000 API calls/month
- Basic features only
- 100 users
- 50 channels
- 100 GB storage
- 50,000 API calls/month
- All features + analytics
- Unlimited users
- Unlimited channels
- Unlimited storage
- Unlimited API calls
- All features + SSO + priority support
- Custom limits
- Custom features
- Dedicated support
- SLA guarantees
-
POST /api/tenants/create- Create new tenant -
GET /api/tenants/[id]- Get tenant -
PUT /api/tenants/[id]- Update tenant -
DELETE /api/tenants/[id]- Delete tenant -
GET /api/tenants/by-slug?slug=acme- Get by subdomain -
GET /api/tenants/by-domain?domain=chat.acme.com- Get by custom domain
-
POST /api/billing/checkout- Create Stripe checkout -
POST /api/billing/portal- Access billing portal -
POST /api/billing/webhook- Stripe webhook endpoint
See .env.multi-tenant.example for complete configuration.
Critical Variables:
# Multi-tenancy
ENABLE_MULTI_TENANCY=true
NEXT_PUBLIC_BASE_DOMAIN=nchat.app
# Stripe
STRIPE_SECRET_KEY=sk_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Database
DATABASE_URL=postgresql://...- Wildcard DNS configured (
*.nchat.app) - Wildcard SSL certificate installed
- Stripe webhook endpoint configured
- Database migration run
- Redis cache configured
- Environment variables set
- Rate limiting enabled
- Backups automated
docker build -t nchat-multi:latest .
docker run -d \
--name nchat \
-p 3000:3000 \
--env-file .env.production \
nchat-multi:latestvercel --prod
vercel env add ENABLE_MULTI_TENANCY production
vercel env add STRIPE_SECRET_KEY productionAll tenant data protected by PostgreSQL RLS:
-- Only super admins can see all tenants
CREATE POLICY tenants_super_admin_all ON public.tenants
USING (
EXISTS (
SELECT 1 FROM auth.users
WHERE auth.users.id = auth.uid()
AND auth.users.raw_app_meta_data->>'role' = 'super_admin'
)
);
-- Tenant owners can see their own tenant
CREATE POLICY tenants_owner_select ON public.tenants
FOR SELECT
USING (owner_id = auth.uid());- Each tenant schema completely isolated
- Middleware enforces schema scoping
- No shared tables between tenants
- Cross-tenant queries impossible
Per-tenant rate limits based on plan:
- Free: 60 requests/minute
- Pro: 120 requests/minute
- Enterprise: 300 requests/minute
-- Current month usage
SELECT * FROM public.tenant_usage
WHERE tenant_id = '{id}' AND period = '2026-01';-- Tenant actions
SELECT * FROM public.tenant_audit_logs
WHERE tenant_id = '{id}'
ORDER BY created_at DESC;# Application health
curl https://nchat.app/api/health
# Tenant health
curl https://acme.nchat.app/api/health-
Tenant Not Found
- Check DNS propagation
- Verify tenant exists in database
- Check middleware logs
-
Stripe Webhook Failing
- Verify webhook secret
- Check Stripe Dashboard events
- Review webhook logs table
-
Cross-Tenant Data Leak
- CRITICAL: Suspend all tenants
- Review RLS policies
- Audit recent queries
-- 1. Create default tenant for existing data
INSERT INTO public.tenants (
name, slug, status, owner_email, owner_name,
schema_name, billing_plan
) VALUES (
'Default Organization',
'default',
'active',
'[email protected]',
'Owner',
'nchat', -- Point to existing nchat schema
'enterprise'
);
-- 2. Enable multi-tenancy
-- .env.local
ENABLE_MULTI_TENANCY=true
DISABLE_MULTI_TENANCY=false
DEFAULT_TENANT_SLUG=default-
Documentation:
/docs/Multi-Tenant-Deployment.md - GitHub Issues: https://github.com/yourusername/nself-chat/issues
- Email: [email protected]
Version: 0.5.0 Last Updated: January 30, 2026 Status: Production Ready