MULTI_APP_SETUP - nself-org/cli GitHub Wiki
nself now supports multiple frontend applications sharing a single backend infrastructure. This allows you to build complex systems with:
- One database (PostgreSQL)
- One auth service (Hasura Auth)
- One storage service (MinIO/Hasura Storage)
- Multiple frontend apps with their own routes and APIs
All apps share the same core services:
- Database: Single PostgreSQL instance with schema-based separation
- Auth: One auth service handling all apps with per-app redirect URLs
- Storage: Shared S3-compatible storage
- Cache: Single Redis instance
When you define a REMOTE_SCHEMA_URL for an app, you automatically get:
-
Per-app API route:
api.app1.localhost→ Your app's GraphQL endpoint -
Per-app auth route:
auth.app1.localhost→ Proxies to shared auth service -
Table namespacing: Using
TABLE_PREFIXto separate data (e.g.,app1_users)
# .env or .env.dev
# Core services
POSTGRES_ENABLED=true
HASURA_ENABLED=true
AUTH_ENABLED=true
STORAGE_ENABLED=true
# Define multiple frontend apps
FRONTEND_APP_COUNT=3
# App 1: Admin Portal
FRONTEND_APP_1_DISPLAY_NAME="Admin Portal"
FRONTEND_APP_1_SYSTEM_NAME=admin
FRONTEND_APP_1_TABLE_PREFIX=adm_ # Tables: adm_users, adm_posts, etc.
FRONTEND_APP_1_PORT=3001
FRONTEND_APP_1_ROUTE=admin.localhost
FRONTEND_APP_1_REMOTE_SCHEMA_NAME=admin_schema
FRONTEND_APP_1_REMOTE_SCHEMA_URL=api.admin # Creates api.admin.localhost
# App 2: Customer Portal
FRONTEND_APP_2_DISPLAY_NAME="Customer Portal"
FRONTEND_APP_2_SYSTEM_NAME=customer
FRONTEND_APP_2_TABLE_PREFIX=cust_ # Tables: cust_users, cust_orders, etc.
FRONTEND_APP_2_PORT=3002
FRONTEND_APP_2_ROUTE=customer.localhost
FRONTEND_APP_2_REMOTE_SCHEMA_NAME=customer_schema
FRONTEND_APP_2_REMOTE_SCHEMA_URL=api.customer # Creates api.customer.localhost
# App 3: Mobile App (uses main API, no remote schema)
FRONTEND_APP_3_DISPLAY_NAME="Mobile App"
FRONTEND_APP_3_SYSTEM_NAME=mobile
FRONTEND_APP_3_TABLE_PREFIX=mob_ # Tables: mob_sessions, mob_devices, etc.
FRONTEND_APP_3_PORT=3003
FRONTEND_APP_3_ROUTE=mobile.localhost
# No REMOTE_SCHEMA_URL = uses main Hasura API at api.localhostFor apps with remote schemas, you need to provide the actual API service:
# Define custom services for per-app APIs
CS_1=node-ts:admin-api:4001:api.admin
CS_2=node-ts:customer-api:4002:api.customer
# CS_N format: type:name:port:route[:internal]
# - type: Service template (node-ts, python, go, etc.)
# - name: Service name
# - port: Internal port
# - route: External route subdomain
# - internal: Optional, set to "true" for internal-only servicesWith the above configuration, nself automatically creates:
- Frontend:
https://admin.localhost - API:
https://api.admin.localhost/graphql - Auth:
https://auth.admin.localhost
- Frontend:
https://customer.localhost - API:
https://api.customer.localhost/graphql - Auth:
https://auth.customer.localhost
- Frontend:
https://mobile.localhost - Main API:
https://api.localhost/graphql(shared) - Main Auth:
https://auth.localhost(shared)
Each app can have its own schema based on TABLE_PREFIX:
-- Core schemas (always created)
auth.* -- Shared authentication tables
storage.* -- Shared file storage metadata
public.* -- Shared/common tables
-- App-specific schemas (from TABLE_PREFIX)
adm.* -- Admin app tables
cust.* -- Customer app tables
mob.* -- Mobile app tablesWhen using TABLE_PREFIX, tables are namespaced:
-- Without prefix (shared tables)
auth.users -- All users across all apps
public.settings -- Global settings
-- With prefix (app-specific tables)
adm.users -- Admin-specific user extensions
cust.orders -- Customer orders
mob.sessions -- Mobile app sessions- All apps use the same auth service
- Auth service accepts redirect URLs for all configured apps
- Each app can have its own auth subdomain (
auth.app1.localhost) - The auth subdomain proxies to the shared auth service while preserving the Host header
nself automatically configures allowed redirect URLs:
// Automatically generated for auth service
AUTH_ALLOWED_REDIRECT_URLS = [
"http://admin.localhost/*",
"https://admin.localhost/*",
"http://auth.admin.localhost/*",
"https://auth.admin.localhost/*",
"http://api.admin.localhost/*",
"https://api.admin.localhost/*",
// ... same for all other apps
]SSL certificates are automatically generated for all domains:
# Run after configuration
nself ssl # Generate certificates
nself trust # Trust them in your system
# Certificates include:
# - *.localhost (wildcard)
# - app1.localhost, auth.app1.localhost, api.app1.localhost
# - app2.localhost, auth.app2.localhost, api.app2.localhost
# - All other configured routesnself automatically generates nginx configurations for:
-
Frontend routes:
app.localhost→ Container at port 3000+ -
API routes:
api.app.localhost→ Custom service -
Auth routes:
auth.app.localhost→ Shared auth service - Core services: Storage, mail, database admin, etc.
For complete app isolation, enable per-app auth tables:
FRONTEND_APP_1_ISOLATE_AUTH=true # Creates adm.users instead of using auth.usersApps automatically send identifying headers:
// Remote schema receives:
headers: {
"X-App-Name": "admin" // From FRONTEND_APP_1_SYSTEM_NAME
}Use environment cascading for different stages:
# .env.dev - Development settings (committed)
FRONTEND_APP_COUNT=3
# .env.staging - Staging overrides
FRONTEND_APP_1_ROUTE=admin.staging.example.com
# .env.prod - Production settings
FRONTEND_APP_1_ROUTE=admin.example.com
BASE_DOMAIN=example.com# Initialize project
nself init
# Build configuration
nself build
# Start services
nself start
# Check status
nself status
# View all URLs
nself urls
# Database management
nself db
# View logs
nself logs [service]- Use TABLE_PREFIX for data isolation between apps
- Define REMOTE_SCHEMA_URL only when the app needs its own API
- Share the auth service - don't duplicate authentication logic
- Use schemas instead of separate databases for multi-tenancy
-
Test locally with
.localhostdomains before production -
Version control your
.env.devfile for team collaboration -
Keep secrets in
.env.secrets(never commit)
# Regenerate certificates with all domains
nself ssl --force
nself trust# Check current configuration
nself config | grep AUTH_ALLOWED_REDIRECT_URLS
# Rebuild if needed
nself build --force# View all routes
nself urls
# Check nginx configuration
cat nginx/conf.d/*.conf# Connect to database
nself db
# Check schemas
\dn
# Check tables in schema
\dt adm.*# Three apps sharing one backend
FRONTEND_APP_COUNT=3
# Customer storefront
FRONTEND_APP_1_SYSTEM_NAME=store
FRONTEND_APP_1_TABLE_PREFIX=store_
FRONTEND_APP_1_ROUTE=shop.example.com
FRONTEND_APP_1_REMOTE_SCHEMA_URL=api.shop
# Vendor dashboard
FRONTEND_APP_2_SYSTEM_NAME=vendor
FRONTEND_APP_2_TABLE_PREFIX=vendor_
FRONTEND_APP_2_ROUTE=vendor.example.com
FRONTEND_APP_2_REMOTE_SCHEMA_URL=api.vendor
# Admin panel
FRONTEND_APP_3_SYSTEM_NAME=admin
FRONTEND_APP_3_TABLE_PREFIX=admin_
FRONTEND_APP_3_ROUTE=admin.example.com
FRONTEND_APP_3_REMOTE_SCHEMA_URL=api.admin
# API services for each app
CS_1=node-ts:store-api:4001:api.shop
CS_2=node-ts:vendor-api:4002:api.vendor
CS_3=node-ts:admin-api:4003:api.adminThis creates a complete e-commerce platform with:
- Shared user authentication
- Shared product catalog (in public schema)
- App-specific tables (store_carts, vendor_products, admin_logs)
- Per-app GraphQL APIs with custom business logic
- Single database, optimized resource usage