Monorepo Setup - nself-org/nchat GitHub Wiki

Monorepo Setup Guide

Version: 0.9.2 Last Updated: February 12, 2026 Prerequisites: nSelf CLI v0.4.2+


Overview

This guide explains how to deploy multiple nSelf applications (e.g., Ι³Chat, Ι³TV, Ι³Family) that share a single backend, single authentication system, and single user baseβ€”while maintaining per-app role-based access control (RBAC).

What You Get:

  • πŸ” One authentication system - Users log in once across all apps (SSO)
  • πŸ‘₯ One user base - Single auth.users table shared by all apps
  • 🎭 Per-app roles - User can be admin in Ι³Chat, regular user in Ι³TV
  • πŸ”Œ Independent frontends - Each app has its own UI and features
  • πŸ—„οΈ Shared backend - One nSelf CLI instance serves all apps

Table of Contents

  1. Architecture
  2. Prerequisites
  3. Directory Structure
  4. Step-by-Step Setup
  5. Per-App RBAC System
  6. User Role Management
  7. App Configuration
  8. Common Scenarios
  9. Troubleshooting

Architecture

System Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Users' Browsers                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚
β”‚  β”‚  Ι³Chat   β”‚  β”‚   Ι³TV    β”‚  β”‚ Ι³Family  β”‚          β”‚
β”‚  β”‚ :3000    β”‚  β”‚  :3001   β”‚  β”‚  :3002   β”‚          β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚             β”‚             β”‚
        β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”
        └───►  Nginx Reverse Proxy         β”‚
            β”‚  api.localhost               β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚             β”‚                 β”‚
  β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Hasura    β”‚ β”‚   Auth   β”‚ β”‚    Storage     β”‚
  β”‚ GraphQL    β”‚ β”‚  (Nhost) β”‚ β”‚    (MinIO)     β”‚
  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚            β”‚                 β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
             β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
             β”‚   PostgreSQL   β”‚
             β”‚                β”‚
             β”‚  Shared Tables:β”‚
             β”‚  - auth.users  β”‚
             β”‚  - apps        β”‚
             β”‚  - app_user_roles
             β”‚                β”‚
             β”‚  App Tables:   β”‚
             β”‚  - nchat_*     β”‚
             β”‚  - ntv_*       β”‚
             β”‚  - nfamily_*   β”‚
             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Concepts

Shared Components:

  • PostgreSQL database
  • Hasura GraphQL engine
  • Nhost Auth service
  • MinIO storage
  • User accounts (auth.users)

Per-App Components:

  • Frontend application (Next.js, React, etc.)
  • App-specific tables (e.g., nchat_channels, ntv_videos)
  • App-specific roles (app_user_roles table)
  • App configuration (NEXT_PUBLIC_APP_ID)

Cross-App Features:

  • Single sign-on (SSO)
  • Shared user profiles
  • Cross-app notifications (optional)

Prerequisites

  1. nSelf CLI - Version 0.4.2 or later

    npm install -g @nself/cli@latest
  2. Multiple nSelf Apps - Clone the apps you want to run:

    git clone https://github.com/nself/nself-chat.git
    git clone https://github.com/nself/nself-tv.git
    git clone https://github.com/nself/nself-family.git
  3. System Requirements:

    • Docker Desktop 20.10+
    • Node.js 20.0+
    • pnpm 9.0+
    • 8GB RAM minimum (16GB recommended)

Directory Structure

Create a monorepo directory structure:

mkdir nself-monorepo
cd nself-monorepo

Recommended structure:

nself-monorepo/
β”œβ”€β”€ backend/              # Shared nSelf CLI backend
β”‚   β”œβ”€β”€ db/
β”‚   β”‚   └── migrations/   # All migrations (ordered)
β”‚   β”œβ”€β”€ docker-compose.yml (generated by nself)
β”‚   └── .env
β”œβ”€β”€ nchat/                # Ι³Chat application
β”‚   └── frontend/
β”‚       β”œβ”€β”€ src/
β”‚       β”œβ”€β”€ package.json
β”‚       └── .env.local
β”œβ”€β”€ ntv/                  # Ι³TV application
β”‚   └── frontend/
β”‚       β”œβ”€β”€ src/
β”‚       β”œβ”€β”€ package.json
β”‚       └── .env.local
└── nfamily/              # Ι³Family application
    └── frontend/
        β”œβ”€β”€ src/
        β”œβ”€β”€ package.json
        └── .env.local

Step-by-Step Setup

Step 1: Create Directory Structure

mkdir -p nself-monorepo/{backend,nchat,ntv,nfamily}
cd nself-monorepo

Step 2: Initialize Shared Backend

cd backend

# Initialize nSelf CLI with demo configuration
nself init --demo

# Follow the prompts:
# βœ“ Project name: nself-monorepo
# βœ“ Enable services: PostgreSQL, Hasura, Auth, MinIO, Redis, MeiliSearch
# βœ“ Enable monitoring: Yes (Grafana + Prometheus + Loki)
# βœ“ Domain: api.localhost

This creates:

  • docker-compose.yml (all services)
  • .env (backend configuration)
  • Database with core tables

Step 3: Clone Applications

cd ..

# Clone apps into monorepo
git clone https://github.com/nself/nself-chat.git nchat
git clone https://github.com/nself/nself-tv.git ntv
git clone https://github.com/nself/nself-family.git nfamily

Step 4: Prepare Migrations

Collect all database migrations into one place:

cd backend
mkdir -p db/migrations

# Copy migrations in order
cp ../nchat/backend/db/migrations/20260212_add_per_app_rbac.sql db/migrations/001_per_app_rbac.sql
cp ../nchat/backend/db/migrations/*.sql db/migrations/002_nchat_tables.sql
cp ../ntv/backend/db/migrations/*.sql db/migrations/003_ntv_tables.sql
cp ../nfamily/backend/db/migrations/*.sql db/migrations/004_nfamily_tables.sql

Step 5: Start Backend

cd backend
nself start

# Wait for all services to start (2-3 minutes)
nself status

Expected output:

βœ“ PostgreSQL running on :5432
βœ“ Hasura running on :8080
βœ“ Auth running on :4000
βœ“ MinIO running on :9000
βœ“ Redis running on :6379
βœ“ MeiliSearch running on :7700
βœ“ Grafana running on :3000

Step 6: Run Migrations

cd backend

# Run migrations in order
for migration in db/migrations/*.sql; do
  echo "Running $migration..."
  nself exec postgres psql -U postgres -d nself -f "/migrations/$(basename $migration)"
done

Verify migrations:

nself exec postgres psql -U postgres -d nself -c "\dt public.app*"

Expected tables:

  • public.apps
  • public.app_user_roles
  • public.app_role_permissions

Step 7: Register Apps in Database

nself exec postgres psql -U postgres -d nself

Then run:

INSERT INTO public.apps (app_id, app_name, app_url, is_active) VALUES
  ('nchat', 'Ι³Chat', 'http://localhost:3000', true),
  ('ntv', 'Ι³TV', 'http://localhost:3001', true),
  ('nfamily', 'Ι³Family', 'http://localhost:3002', true);

-- Verify
SELECT * FROM public.apps;

Step 8: Configure Frontend Apps

Ι³Chat (.env.local):

cd ../nchat/frontend
cp .env.example .env.local

Edit .env.local:

# App Identity
NEXT_PUBLIC_APP_ID=nchat
NEXT_PUBLIC_APP_NAME=Ι³Chat

# Backend URLs (shared)
NEXT_PUBLIC_GRAPHQL_URL=http://api.localhost/v1/graphql
NEXT_PUBLIC_AUTH_URL=http://auth.localhost/v1/auth
NEXT_PUBLIC_STORAGE_URL=http://storage.localhost/v1/storage

# Environment
NEXT_PUBLIC_ENV=development

Ι³TV (.env.local):

cd ../../ntv/frontend
cp .env.example .env.local

Edit .env.local:

# App Identity
NEXT_PUBLIC_APP_ID=ntv
NEXT_PUBLIC_APP_NAME=Ι³TV

# Backend URLs (shared - SAME AS NCHAT)
NEXT_PUBLIC_GRAPHQL_URL=http://api.localhost/v1/graphql
NEXT_PUBLIC_AUTH_URL=http://auth.localhost/v1/auth
NEXT_PUBLIC_STORAGE_URL=http://storage.localhost/v1/storage

# Environment
NEXT_PUBLIC_ENV=development

Ι³Family (.env.local):

cd ../../nfamily/frontend
cp .env.example .env.local

Edit .env.local:

# App Identity
NEXT_PUBLIC_APP_ID=nfamily
NEXT_PUBLIC_APP_NAME=Ι³Family

# Backend URLs (shared - SAME AS NCHAT)
NEXT_PUBLIC_GRAPHQL_URL=http://api.localhost/v1/graphql
NEXT_PUBLIC_AUTH_URL=http://auth.localhost/v1/auth
NEXT_PUBLIC_STORAGE_URL=http://storage.localhost/v1/storage

# Environment
NEXT_PUBLIC_ENV=development

Step 9: Install Dependencies

# Ι³Chat
cd ../../nchat/frontend
pnpm install

# Ι³TV
cd ../../ntv/frontend
pnpm install

# Ι³Family
cd ../../nfamily/frontend
pnpm install

Step 10: Start All Apps

Open 3 terminals:

Terminal 1 - Ι³Chat:

cd nchat/frontend
pnpm dev
# Runs on http://localhost:3000

Terminal 2 - Ι³TV:

cd ntv/frontend
pnpm dev --port 3001
# Runs on http://localhost:3001

Terminal 3 - Ι³Family:

cd nfamily/frontend
pnpm dev --port 3002
# Runs on http://localhost:3002

Step 11: Create First User

Visit any app (e.g., http://localhost:3000) and sign up:

This creates a user in the shared auth.users table.

Step 12: Assign Roles

cd backend
nself exec postgres psql -U postgres -d nself
-- Get the user ID
SELECT id, email FROM auth.users WHERE email = '[email protected]';

-- Assign roles (replace UUID with actual user ID)
INSERT INTO public.app_user_roles (app_id, user_id, role) VALUES
  ('nchat', 'user-uuid-here', 'owner'),
  ('ntv', 'user-uuid-here', 'admin'),
  ('nfamily', 'user-uuid-here', 'member');

-- Verify
SELECT * FROM public.app_user_roles;

Per-App RBAC System

Overview

The per-app RBAC system allows users to have different roles in different applications while sharing authentication.

Tables

1. apps - Application Registry

CREATE TABLE public.apps (
    id UUID PRIMARY KEY,
    app_id TEXT UNIQUE NOT NULL,  -- 'nchat', 'ntv', 'nfamily'
    app_name TEXT NOT NULL,       -- 'Ι³Chat', 'Ι³TV', 'Ι³Family'
    app_url TEXT,                 -- 'https://chat.example.com'
    is_active BOOLEAN DEFAULT true
);

2. app_user_roles - Per-App User Roles

CREATE TABLE public.app_user_roles (
    id UUID PRIMARY KEY,
    app_id TEXT NOT NULL REFERENCES apps(app_id),
    user_id UUID NOT NULL REFERENCES auth.users(id),
    role TEXT NOT NULL,           -- 'owner', 'admin', 'moderator', 'member', 'guest'
    granted_by UUID REFERENCES auth.users(id),
    granted_at TIMESTAMPTZ DEFAULT NOW(),
    expires_at TIMESTAMPTZ        -- Optional expiration
);

3. app_role_permissions - Per-Role Permissions

CREATE TABLE public.app_role_permissions (
    id UUID PRIMARY KEY,
    app_id TEXT NOT NULL REFERENCES apps(app_id),
    role TEXT NOT NULL,
    permission TEXT NOT NULL,     -- 'channels.create', 'messages.delete'
    resource TEXT                 -- Optional: specific resource ID
);

Role Types

Role Description Typical Permissions
owner Complete control All permissions, billing, settings
admin User/content management Create/delete channels, manage users, moderation
moderator Content moderation Delete messages, warn/timeout users
member Standard user Send messages, join channels, upload files
guest Limited access View channels, read messages (no posting)

Frontend Integration

Check permissions in React:

import { useAppPermissions } from '@/hooks/use-app-permissions'

function DeleteChannelButton({ channelId }: { channelId: string }) {
  const { hasPermission, isAdmin } = useAppPermissions()

  if (!hasPermission('channels.delete')) {
    return null // Hide button if no permission
  }

  return (
    <button onClick={() => deleteChannel(channelId)}>
      Delete Channel
    </button>
  )
}

Check roles:

const { hasRole, userRoles } = useAppPermissions()

if (hasRole('owner')) {
  // Show owner-only features
}

User Role Management

Assigning Roles via SQL

-- Grant admin role in Ι³Chat
INSERT INTO public.app_user_roles (app_id, user_id, role)
VALUES ('nchat', 'user-uuid', 'admin');

-- Grant member role in Ι³TV
INSERT INTO public.app_user_roles (app_id, user_id, role)
VALUES ('ntv', 'user-uuid', 'member');

Assigning Roles via GraphQL

mutation GrantUserRole {
  insert_app_user_roles_one(
    object: {
      app_id: "nchat"
      user_id: "user-uuid"
      role: "admin"
      granted_by: "admin-user-uuid"
    }
  ) {
    id
    role
  }
}

Auto-Assign Default Role

Create a trigger to assign default role to new users:

CREATE OR REPLACE FUNCTION assign_default_app_role()
RETURNS TRIGGER AS $$
BEGIN
  -- Assign 'member' role in all active apps
  INSERT INTO public.app_user_roles (app_id, user_id, role)
  SELECT app_id, NEW.id, 'member'
  FROM public.apps
  WHERE is_active = true;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER on_user_created
  AFTER INSERT ON auth.users
  FOR EACH ROW
  EXECUTE FUNCTION assign_default_app_role();

App Configuration

Environment Variables

Each app must set:

# Required
NEXT_PUBLIC_APP_ID=nchat  # Unique app identifier
NEXT_PUBLIC_APP_NAME=Ι³Chat  # Display name

# Shared backend
NEXT_PUBLIC_GRAPHQL_URL=http://api.localhost/v1/graphql
NEXT_PUBLIC_AUTH_URL=http://auth.localhost/v1/auth
NEXT_PUBLIC_STORAGE_URL=http://storage.localhost/v1/storage

Hasura Configuration

Grant permissions in Hasura console (http://localhost:8080):

  1. Track tables:

    • public.apps
    • public.app_user_roles
    • public.app_role_permissions
  2. Add permissions for user role:

    select:
      filter:
        user_id: { _eq: X-Hasura-User-Id }
  3. Grant function permissions:

    • user_has_app_role
    • user_has_app_permission
    • get_user_app_roles

Common Scenarios

Scenario 1: User is Admin in One App, Member in Another

Setup:

INSERT INTO public.app_user_roles (app_id, user_id, role) VALUES
  ('nchat', 'alice-uuid', 'admin'),
  ('ntv', 'alice-uuid', 'member');

Result:

  • Alice can create/delete channels in Ι³Chat
  • Alice can only view/post content in Ι³TV
  • Alice logs in once, seamless switch between apps

Scenario 2: Temporary Moderator Role

Grant temporary role:

INSERT INTO public.app_user_roles (app_id, user_id, role, expires_at)
VALUES ('nchat', 'bob-uuid', 'moderator', NOW() + INTERVAL '7 days');

Result:

  • Bob has moderator permissions for 7 days
  • Role automatically expires
  • No manual cleanup needed

Scenario 3: Owner Across All Apps

Grant owner role everywhere:

INSERT INTO public.app_user_roles (app_id, user_id, role)
SELECT app_id, 'owner-uuid', 'owner'
FROM public.apps
WHERE is_active = true;

Result:

  • User has full control across all apps
  • New apps automatically grant owner role (via trigger)

Troubleshooting

Issue: User Has No Roles

Symptom: User can log in but has no permissions.

Solution:

-- Check if user has roles
SELECT * FROM public.app_user_roles WHERE user_id = 'user-uuid';

-- If empty, assign default role
INSERT INTO public.app_user_roles (app_id, user_id, role)
VALUES ('nchat', 'user-uuid', 'member');

Prevention: Implement the assign_default_app_role() trigger.

Issue: Permission Denied on app_user_roles

Symptom: GraphQL query returns "permission denied".

Solution:

-- Grant Hasura access
GRANT SELECT ON public.app_user_roles TO hasura;
GRANT SELECT ON public.app_role_permissions TO hasura;
GRANT EXECUTE ON FUNCTION public.user_has_app_role TO hasura;
GRANT EXECUTE ON FUNCTION public.user_has_app_permission TO hasura;

Issue: Apps Not Showing in GraphQL

Symptom: apps table not visible in Hasura.

Solution:

  1. Visit Hasura console: http://localhost:8080
  2. Data tab β†’ Schema β†’ public
  3. Track tables: apps, app_user_roles, app_role_permissions
  4. Reload metadata: nself exec hasura hasura-cli metadata reload

Issue: Session Not Shared Between Apps

Symptom: User must log in separately for each app.

Solution:

  • Verify all apps use the same NEXT_PUBLIC_AUTH_URL
  • Check that cookies have the same domain (e.g., .localhost)
  • In production, use a shared domain (e.g., *.example.com)

Issue: Database Migrations Conflict

Symptom: Migrations fail due to table conflicts.

Solution:

  • Prefix app-specific tables with app ID (e.g., nchat_channels, ntv_videos)
  • Use schema separation:
    CREATE SCHEMA nchat;
    CREATE TABLE nchat.channels (...);
    
    CREATE SCHEMA ntv;
    CREATE TABLE ntv.videos (...);

Next Steps

  1. Security: Review Security Guide
  2. Production: Read Deployment Guide
  3. Scaling: Check Performance Guide
  4. Monitoring: Set up Observability

Additional Resources


Questions? Open an issue on GitHub.

⚠️ **GitHub.com Fallback** ⚠️