9
Switch language to العربية

Database

PreviousNext

Multi-tenant PostgreSQL database architecture with Prisma ORM, row-level security, and domain-based tenant isolation

The database is engineered as a multi-tenant school management system that supports multiple schools in a single database with complete data isolation. Built on PostgreSQL with Prisma ORM, it provides a robust foundation for educational institutions while maintaining strict security boundaries between tenants.

Core Principles

Our database design is guided by fundamental principles that ensure scalability, security, and maintainability:

  • Row-Level Security – Every model includes a schoolId field ensuring complete data isolation between schools
  • Shared Schema Architecture – All schools use the same database schema, optimizing resource utilization
  • Domain-Based Tenant Identification – Schools are uniquely identified by subdomain (e.g., hogwarts.hogwarts.app), with optional custom domains via CNAME
  • Type-Safe Database Operations – Prisma ORM provides end-to-end type safety from database to frontend
  • Multi-File Schema Organization – Schema is organized into logical domains for better maintainability

Database Design

The database follows a comprehensive relational design that captures all aspects of school management from core infrastructure to daily operations.

Database Schema Diagram

The entity relationship diagram above illustrates the interconnected nature of our school management system, showing how entities relate through foreign key relationships while maintaining multi-tenant isolation through the schoolId field present in every table.

Multi-Tenant Isolation Strategy

The tenant isolation strategy ensures that each school's data remains completely separate while sharing the same database infrastructure.

Tenant Entity (School Model)

model School {
  id          String  @id @default(cuid())
  name        String
  domain      String  @unique // e.g., "hogwarts"
  logoUrl     String?
  address     String?
  phoneNumber String?
  email       String?
  timezone    String  @default("UTC")
 
  // Subscription/billing
  planType    String  @default("basic")
  maxStudents Int     @default(100)
  maxTeachers Int     @default(10)
  isActive    Boolean @default(true)
}

Data Isolation Example

// Correct - Always include schoolId
const students = await prisma.student.findMany({
  where: { schoolId: "school_123" }
})
 
// Wrong - Missing schoolId (security risk)
const students = await prisma.student.findMany()

Directory Structure

The Prisma directory follows a well-organized structure that supports both development and production scenarios with clear separation of concerns.

prisma/Database schema and migrations
schema.prismaMain config with datasource and generator
models/Schema files organized by domain (30+ models)
school.prismaSchool, SchoolYear, Period, Term, YearLevel
auth.prismaUser, Account, tokens, 2FA
staff.prismaTeacher, Department, TeacherDepartment
students.prismaStudent, Guardian, StudentGuardian, Emergency
subjects.prismaSubject, Class, StudentClass, ScoreRange
classrooms.prismaClassroom, ClassroomType
assessments.prismaAssignment, AssignmentSubmission
attendance.prismaAttendance with status tracking
attendance-enhanced.prismaAdvanced attendance features
geo-attendance.prismaLocation-based attendance
finance.prismaFees, payments, invoices, receipts
subscription.prismaPlans, billing, Stripe integration
admission.prismaApplications, enrollment workflow
exam.prismaExams, grades, report cards
qbank.prismaQuestion bank, assessments
quiz-game.prismaGamified learning
stream.prismaLive streaming, recordings
timetable.prismaSchedule, periods, slots
messages.prismaChat, conversations, threads
notifications.prismaPush, email, in-app alerts
files.prismaFile storage, attachments
library.prismaBooks, borrowing, catalog
lessons.prismaLesson plans, resources
announcement.prismaSchool-wide announcements
branding.prismaTheme, logo, customization
theme.prismaUI theme preferences
legal.prismaConsent, compliance, GDPR
domain.prismaCustom domain requests
audit.prismaActivity logging
task.prismaTask management
schedule.prismaEvent scheduling
generator/Database seeding scripts
seed.tsMain seeding orchestrator
seed-demo.tsDemo data for showcases
seed-community.tsCommunity edition data
seed-qbank-simple.tsQuestion bank seeder
seed-modules/Modular seeders
admission.tsAdmission test data
finance.tsFinancial test data
seed/Additional seed data
finance.seed.tsFinance module seeder
timetable-enhanced.tsTimetable seeder
migrations/Auto-generated migration files
20250807.../Initial multi-tenant schema
20250810.../Audit and domain request
20250811.../Stripe billing models
20250812.../Timetable init
migration_lock.tomlMigration provider lock
sql/Raw SQL scripts
geo-spatial-indexes.sqlGeo-location indexes
geo-triggers.sqlGeo-location triggers
schema-optimizations.sqlPerformance optimizations
README.mdDatabase documentation
ISSUE.mdKnown issues and fixes

Schema Tables

Each model contains realistic sample data that demonstrates the relationships and data structure of our multi-tenant school management system.

School (Tenant Entity)

Main tenant entities representing different schools in the multi-tenant system.

IDSchool NameDomainPlanMax StudentsActive
sch_001Hogwartshogwartspremium1000Yes
sch_002Beauxbatonsbeauxbatonspremium500Yes
sch_003Durmstrangdurmstrangbasic300Yes

Teacher

Teaching staff with school-specific isolation and department assignments.

IDSchool IDFirst NameLast NameEmailGender
tch_001sch_001MinervaMcGonagallm.mcg@hogwarts.eduF
tch_002sch_001SeverusSnapes.snape@hogwarts.eduM
tch_003sch_001RubeusHagridr.hagrid@hogwarts.eduM

Student

Student enrollment records with guardian relationships and year-level tracking.

IDSchool IDFirst NameMiddleLast NameDate of BirthGender
std_001sch_001HarryJPotter1980-07-31M
std_002sch_001HermioneJGranger1979-09-19F
std_003sch_001RonBWeasley1980-03-01M

Department

Academic departments organizing subjects and teacher assignments.

IDSchool IDDepartment Name
dep_001sch_001Transfiguration
dep_002sch_001Potions
dep_003sch_001Creatures

Subject

Academic subjects within departments that form the basis of classes.

IDSchool IDDepartment IDSubject Name
sub_001sch_001dep_001Transfiguration
sub_002sch_001dep_002Potions
sub_003sch_001dep_003Creatures

Classroom

Physical classroom locations with capacity and type information.

IDSchool IDRoom NameCapacityType
cls_001sch_001Great Hall400Hall
cls_002sch_001Dungeon30Lab
cls_003sch_001Room 10135Class

Assignment

Assessment tasks including homework, quizzes, tests, and projects.

IDSchool IDTitleTypeStatusPointsDue Date
asg_001sch_001EssayESSAYPUBLISHED1002024-02-15
asg_002sch_001QuizQUIZCOMPLETED502024-02-10
asg_003sch_001ProjectPROJECTIN_PROGRESS1502024-02-20

Attendance

Daily attendance tracking per student per class with detailed status information.

IDSchool IDStudent IDDateStatusNotes
att_001sch_001std_0012024-02-08PRESENT
att_002sch_001std_0022024-02-08PRESENT
att_003sch_001std_0032024-02-08LATELate

User (Authentication)

Multi-tenant user accounts with role-based access control and school isolation.

IDEmailRoleSchool IDVerified
usr_001harry@hog.eduSTUDENTsch_001Yes
usr_002mcg@hog.eduTEACHERsch_001Yes
usr_003admin@dev.comDEVELOPERnull (all schools)Yes

Subscription & Pricing

The subscription system supports tiered pricing plans with flexible discounting capabilities.

Subscription & Pricing Models

model SubscriptionTier {
  id                String   @id @default(cuid())
  name              String   // basic, premium, enterprise
  description       String
  monthlyPrice      Int     // Price in cents
  annualPrice       Int     // Price in cents
  maxStudents       Int
  maxTeachers       Int
  maxClasses        Int
  features          String[] // Array of feature identifiers
  isActive          Boolean  @default(true)
 
  // Relationships
  subscriptions     Subscription[]
  discounts         Discount[]
}
 
model Discount {
  id                String   @id @default(cuid())
  schoolId          String
  tierId            String
  code              String   @unique
  type              String   // percentage, fixed
  value             Int      // Percentage or fixed amount
  description       String
  validFrom         DateTime
  validUntil        DateTime
  maxUses           Int?
  currentUses       Int      @default(0)
  isActive          Boolean  @default(true)
 
  // Relationships
  school            School           @relation(fields: [schoolId], references: [id])
  subscriptionTier  SubscriptionTier @relation(fields: [tierId], references: [id])
  appliedDiscounts  AppliedDiscount[]
}
 
model AppliedDiscount {
  id          String   @id @default(cuid())
  schoolId    String
  discountId  String
  invoiceId   String
  amount      Int      // Amount saved in cents
  appliedAt   DateTime @default(now())
 
  // Relationships
  school      School   @relation(fields: [schoolId], references: [id])
  discount    Discount @relation(fields: [discountId], references: [id])
  invoice     Invoice  @relation(fields: [invoiceId], references: [id])
}

Key Features:

  • Tiered subscription plans with different limits and features
  • Flexible discount system supporting both percentage and fixed amounts
  • Usage tracking for limited-use discount codes
  • Complete audit trail of applied discounts
  • Multi-tenant safety with schoolId scoping

Example Usage

// Create a new subscription tier
const tier = await prisma.subscriptionTier.create({
  data: {
    name: "premium",
    description: "Premium school plan",
    monthlyPrice: 19900, // $199.00
    annualPrice: 199900, // $1,999.00
    maxStudents: 500,
    maxTeachers: 50,
    maxClasses: 100,
    features: ["analytics", "api_access", "priority_support"]
  }
})
 
// Create a discount code
const discount = await prisma.discount.create({
  data: {
    schoolId: "school_123",
    tierId: tier.id,
    code: "WELCOME2024",
    type: "percentage",
    value: 20, // 20% off
    description: "New year promotion",
    validFrom: new Date(),
    validUntil: new Date("2024-12-31"),
    maxUses: 100
  }
})

The legal system provides comprehensive consent tracking and compliance logging.

model LegalConsent {
  id                String   @id @default(cuid())
  schoolId          String
  userId            String
  documentType      String   // terms, privacy, data-processing
  documentVersion   String   // Version consented to
  consentType       String   // explicit, implicit, parental
  ipAddress         String?
  userAgent         String?
  consentedAt       DateTime @default(now())
  revokedAt         DateTime?
  metadata          Json?
 
  // Relationships
  school            School   @relation(fields: [schoolId], references: [id])
  user              User     @relation(fields: [userId], references: [id])
 
  @@unique([schoolId, userId, documentType, documentVersion])
}
 
model LegalDocument {
  id                String   @id @default(cuid())
  schoolId          String
  type              String   // terms, privacy, data-processing
  version           String   // Semantic version
  content           String   @db.Text
  effectiveFrom     DateTime
  effectiveUntil    DateTime?
  isActive          Boolean  @default(true)
  requiresExplicit  Boolean  @default(true)
  metadata          Json?
 
  // Relationships
  school            School   @relation(fields: [schoolId], references: [id])
}
 
model ComplianceLog {
  id                String   @id @default(cuid())
  schoolId          String
  eventType         String   // consent events, document updates
  eventData         Json
  userId            String?
  timestamp         DateTime @default(now())
  metadata          Json?
 
  // Relationships
  school            School   @relation(fields: [schoolId], references: [id])
  user              User?    @relation(fields: [userId], references: [id])
}

Key Features:

  • Comprehensive consent tracking with version control
  • Support for multiple document types and consent types
  • Audit trail with detailed event logging
  • GDPR-compliant consent evidence collection
  • Multi-tenant safety with schoolId scoping

Example Usage

// Record user consent
const consent = await prisma.legalConsent.create({
  data: {
    schoolId: "school_123",
    userId: "user_456",
    documentType: "terms",
    documentVersion: "1.0.0",
    consentType: "explicit",
    ipAddress: "192.168.1.1",
    userAgent: "Mozilla/5.0...",
    metadata: {
      source: "onboarding",
      platform: "web"
    }
  }
})
 
// Log compliance event
await prisma.complianceLog.create({
  data: {
    schoolId: "school_123",
    eventType: "consent-given",
    eventData: {
      documentType: "terms",
      version: "1.0.0",
      consentId: consent.id
    },
    userId: "user_456"
  }
})

Authentication & User Roles

The system supports 8 distinct user roles with varying levels of access and school-specific constraints.

RoleDescriptionSchool AccessTypical Use Case
DEVELOPERPlatform administratorAll schoolsSystem maintenance, cross-tenant operations
ADMINSchool administratorSingle schoolSchool management, user oversight
TEACHERTeaching staffSingle schoolClass management, grading, attendance
STUDENTEnrolled studentsSingle schoolLearning management, assignment submission
GUARDIANStudent parents/guardiansSingle schoolMonitor child's progress, communication
ACCOUNTANTFinance staffSingle schoolBilling, financial reporting
STAFFGeneral school staffSingle schoolAdministrative support
USERDefault roleSingle schoolBase level access

Security & Data Protection

Automatic Tenant Filtering

Every database query must include the schoolId to ensure proper data isolation:

// Server action with tenant context
export async function getStudents() {
  const schoolId = getSchoolId() // From middleware context
 
  return await prisma.student.findMany({
    where: { schoolId }
  })
}

Unique Constraints with School Scope

All unique constraints include schoolId to allow data duplication across schools:

// Teachers can have same email across different schools
@@unique([schoolId, emailAddress])
 
// Departments can have same names across schools
@@unique([schoolId, departmentName])

Performance Optimization

-- Critical indexes for multi-tenant queries
CREATE INDEX idx_students_school_id ON students(school_id);
CREATE INDEX idx_teachers_school_id ON teachers(school_id);
CREATE INDEX idx_classes_school_id ON classes(school_id);
CREATE INDEX idx_attendance_school_id ON attendance(school_id);

Query Performance Guidelines

  • Always filter by schoolId first in WHERE clauses
  • Use composite indexes for frequently queried field combinations
  • Implement connection pooling for high-traffic scenarios
  • Consider read replicas for reporting and analytics

Deployment Configuration

Environment Variables

# Database Connection
DATABASE_URL="postgresql://username:password@host:port/database"
 
# Multi-tenancy Configuration
DEFAULT_SCHOOL_DOMAIN="demo"
ALLOW_SCHOOL_SIGNUP="true"
 
# Subscription Limits
BASIC_MAX_STUDENTS=100
PREMIUM_MAX_STUDENTS=500
ENTERPRISE_MAX_STUDENTS=2000

Migration Commands

# Generate Prisma Client
pnpm prisma generate
 
# Create and apply migrations
pnpm prisma migrate dev --name init
 
# Deploy to production
pnpm prisma migrate deploy
 
# Seed sample data
pnpm prisma db seed

Sample Schools & Test Data

The seeding system creates three sample schools with complete datasets:

SchoolDomainDescription
Hogwartshogwarts.comSchool of Witchcraft and Wizardry
Beauxbatonsbeauxbatons.comAcademy of Magic
Durmstrangdurmstrang.comInstitute for Magical Learning

Sample Data Statistics

Hogwarts:

  • Complete academic structure
  • 4 houses with qualified professors
  • 1000+ active students
  • Full magical curriculum

Beauxbatons:

  • French academic system
  • 6 departments
  • 700+ students
  • Specialized courses

Durmstrang:

  • Nordic education model
  • 5 faculties
  • 500+ students
  • Advanced programs

This comprehensive database architecture provides the foundation for building scalable, secure, and feature-rich educational management systems.