Database Architecture
Single database, multi-schema architecture — the mandatory pattern for all NaaP plugins and services.
The Golden Rule#
Every plugin and service MUST use the unified
@naap/databasepackage. Never create per-plugin databases, Prisma schemas, or PrismaClient instances.
NaaP uses a single PostgreSQL database with multiple schemas for data isolation. All 70+ models across 9 schemas are defined in one Prisma schema file and shared via the @naap/database package.
Why This Architecture#
| Concern | Multi-DB (old, removed) | Single-DB Multi-Schema (current) |
|---|---|---|
| Docker containers | 10 separate PostgreSQL containers | 1 container |
| Connection pools | 10 x pool_size connections | 1 shared pool |
| Schema management | 7 separate Prisma schemas | 1 unified schema |
| Migrations | Run independently per plugin | Run once from packages/database |
| Cross-plugin queries | Impossible (different DBs) | Supported (same DB, Prisma relations) |
| Dev setup | ~30s to start all containers | ~3s for 1 container |
| Ops / backup | Back up 10 databases | Back up 1 database |
| Memory usage | ~500MB (10 x PostgreSQL) | ~50MB (1 x PostgreSQL) |
Architecture Diagram#
| 1 | ┌─────────────────────────────────────────────────────────┐ |
| 2 | │ Application Layer │ |
| 3 | │ │ |
| 4 | │ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌──────────┐ │ |
| 5 | │ │ base-svc │ │ community │ │ daydream │ │ capacity │ │ |
| 6 | │ └────┬─────┘ └─────┬─────┘ └────┬─────┘ └────┬─────┘ │ |
| 7 | │ └─────────────┬────────────┘ │ │ |
| 8 | │ │ │ │ |
| 9 | │ ┌──────▼──────────────────────────▼──┐ │ |
| 10 | │ │ @naap/database │ │ |
| 11 | │ │ Singleton PrismaClient │ │ |
| 12 | │ │ multiSchema: enabled │ │ |
| 13 | │ └──────────────┬──────────────────────┘ │ |
| 14 | └─────────────────────────────┼──────────────────────────┘ |
| 15 | │ |
| 16 | ┌─────────▼─────────┐ |
| 17 | │ naap-db │ |
| 18 | │ PostgreSQL 16 │ |
| 19 | │ │ |
| 20 | │ ┌──────────────┐ │ |
| 21 | │ │ public │ │ Core (User, Auth, Team, RBAC) |
| 22 | │ ├──────────────┤ │ |
| 23 | │ │ plugin_ │ │ Community Hub |
| 24 | │ │ community │ │ |
| 25 | │ ├──────────────┤ │ |
| 26 | │ │ plugin_ │ │ Daydream Video |
| 27 | │ │ daydream │ │ |
| 28 | │ ├──────────────┤ │ |
| 29 | │ │ plugin_ │ │ My Wallet |
| 30 | │ │ wallet │ │ |
| 31 | │ ├──────────────┤ │ |
| 32 | │ │ plugin_ │ │ Dashboard, |
| 33 | │ │ dashboard ...│ │ Capacity, Dev API |
| 34 | │ ├──────────────┤ │ |
| 35 | │ │ plugin_ │ │ Service Gateway |
| 36 | │ │ service_ │ │ (Connectors, Keys, |
| 37 | │ │ gateway │ │ Plans, Usage, etc.) |
| 38 | │ └──────────────┘ │ |
| 39 | └────────────────────┘ |
Schema Map#
| PostgreSQL Schema | Package/Plugin | Example Models |
|---|---|---|
public | base-svc | User, Team, Plugin, Auth, RBAC |
plugin_community | community | CommunityProfile, CommunityPost, CommunityComment |
plugin_wallet | my-wallet | WalletConnection, WalletTransactionLog |
plugin_dashboard | my-dashboard | Dashboard, DashboardUserPreference |
plugin_daydream | daydream-video | DaydreamSettings, DaydreamSession |
plugin_capacity | capacity-planner | CapacityRequest, CapacitySoftCommit |
plugin_developer_api | developer-api | DevApiAIModel, DevApiKey |
plugin_service_gateway | service-gateway | ServiceConnector, ConnectorEndpoint, GatewayApiKey, GatewayPlan, GatewayUsageRecord, GatewayConnectorTemplate, GatewayHealthCheck, ConnectorPricing, GatewayMasterKey, ConnectorMetrics, ConnectorCapabilityRanking |
How It Works#
1. Single Source of Truth#
All models are defined in one Prisma schema file:
packages/database/prisma/schema.prismaEvery model uses @@schema("plugin_xxx") to assign it to a PostgreSQL schema:
| 1 | model CommunityPost { |
| 2 | id String @id @default(uuid()) |
| 3 | title String |
| 4 | content String |
| 5 | authorId String |
| 6 | createdAt DateTime @default(now()) |
| 7 | updatedAt DateTime @updatedAt |
| 8 | |
| 9 | @@schema("plugin_community") // ← This is mandatory |
| 10 | } |
2. One Prisma Client, Shared by All#
The @naap/database package exports a singleton client:
| 1 | // packages/database/src/index.ts |
| 2 | import { PrismaClient } from './generated/client'; |
| 3 | |
| 4 | export const prisma = globalForPrisma.prisma ?? new PrismaClient({...}); |
Every plugin imports from this package — never from a local generated client:
// plugins/community/backend/src/db/client.ts
import { prisma } from '@naap/database';
export const db = prisma;3. Schema Isolation at the PostgreSQL Level#
The docker/init-schemas.sql file runs on first boot and creates all schemas:
| 1 | CREATE SCHEMA IF NOT EXISTS plugin_community; |
| 2 | CREATE SCHEMA IF NOT EXISTS plugin_wallet; |
| 3 | CREATE SCHEMA IF NOT EXISTS plugin_daydream; |
| 4 | -- ... etc |
Tables are created inside their schema automatically by Prisma's multiSchema feature.
Mandatory Rules#
These rules must be followed by every plugin and service:
Rule 1: No Per-Plugin Prisma Schemas#
❌ plugins/my-plugin/backend/prisma/schema.prisma ← NEVER
✅ packages/database/prisma/schema.prisma ← ALWAYSRule 2: No Per-Plugin Generated Clients#
❌ import { PrismaClient } from '../generated/client' ← NEVER
❌ import { PrismaClient } from '@prisma/client' ← NEVER
✅ import { prisma } from '@naap/database' ← ALWAYSRule 3: No Per-Plugin Database Containers#
| 1 | # ❌ NEVER — separate containers per plugin |
| 2 | gateway-db: |
| 3 | image: postgres:16-alpine |
| 4 | ports: ["5433:5432"] |
| 5 | |
| 6 | # ✅ ALWAYS — one unified container |
| 7 | database: |
| 8 | image: postgres:16-alpine |
| 9 | ports: ["5432:5432"] |
| 10 | volumes: |
| 11 | - ./docker/init-schemas.sql:/docker-entrypoint-initdb.d/01-init-schemas.sql |
Rule 4: Every Model Must Have @@schema()#
| 1 | // ❌ NEVER — model without schema annotation |
| 2 | model MyWidget { |
| 3 | id String @id @default(uuid()) |
| 4 | } |
| 5 | |
| 6 | // ✅ ALWAYS — explicit schema |
| 7 | model MyWidget { |
| 8 | id String @id @default(uuid()) |
| 9 | @@schema("plugin_my_plugin") |
| 10 | } |
Rule 5: Single DATABASE_URL#
All .env files must point to the same database:
Connection String#
| Environment | URL |
|---|---|
| Local dev | postgresql://postgres:postgres@localhost:5432/naap |
| Production | Neon PostgreSQL via DATABASE_URL environment variable |
| Preview | Neon preview branch (auto-created per Vercel preview deployment) |
Preview Database Branches#
For Vercel preview deployments, NaaP automatically creates a Neon preview database branch. Each pull request gets its own isolated database branch, allowing schema changes to be tested without affecting production data.
The preview branch is provisioned during the Vercel build step and the DATABASE_URL is set automatically for the preview environment.
Next Steps#
- Database Setup Tutorial — Step-by-step guide for adding a schema
- Database Plugin Example — Full worked example
- AI Prompt: Database Compliance — Copy-paste prompt for AI assistants