Skip to main content

Backend Overview

The backend is a NestJS application running on the Fastify HTTP adapter. It serves as the REST API for the GradeBook application, handling authentication, school management, academic structure, student enrollment, grading, and grade calculations.

Tech Stack

TechnologyPurpose
NestJS 11Application framework
FastifyHTTP server (replaces Express for performance)
SupabaseDatabase (PostgreSQL), authentication, and row-level security
TypeScript 5Type safety
Bun TestTesting framework (built-in bun:test)
SwaggerAPI documentation (auto-generated at /docs)
class-validatorDTO validation

Project Structure

backend/
├── api/
│ └── index.ts # Vercel serverless entry point
├── vercel.json # Vercel routing and build config
├── src/
│ ├── createApp.ts # Shared app bootstrap (used by main.ts and api/index.ts)
│ ├── main.ts # Local dev entry point (calls createApp + listen)
│ ├── worker.ts # Cloudflare Workers entry point
│ ├── app.module.ts # Root module
│ ├── app.controller.ts # Health check endpoint
│ ├── app.service.ts # Health check service
│ ├── stubs/
│ │ └── empty.js # Stub for optional NestJS deps during CF Workers bundling
│ ├── types/
│ │ └── database.types.ts # Generated Supabase schema types
│ ├── supabase/ # Supabase client provider
│ ├── auth/ # Authentication (OTP, JWT, onboarding)
│ ├── school/ # School management
│ ├── academic-year/ # Academic year lifecycle
│ ├── term/ # Term management within years
│ ├── pagination/ # Reusable pagination service (offset + cursor)
│ ├── versioning/ # Header-based response versioning service
│ ├── student/ # Student records
│ ├── subject/ # Subject definitions
│ ├── class/ # Class (student group) management
│ ├── enrollment/ # Student enrollment and subject assignment
│ ├── grading/ # Assessments and grades
│ ├── calculation/ # Grade calculations and summaries
│ ├── reporting/ # Report generation, status workflow, file storage
│ ├── images/ # Image upload service (avatar, resumable TUS uploads)
│ └── cache/ # Pluggable caching (memory or Redis)

Application Bootstrap

The app bootstrap logic lives in src/createApp.ts and is shared between all entry points. It configures:

  • Adapter: Fastify
  • Global prefix: api - all routes are prefixed with this
  • Swagger: Available at /docs
  • Validation: Global ValidationPipe with whitelist, forbidNonWhitelisted, and transform enabled
  • CORS: Allows requests from FRONTEND_URL environment variable (defaults to http://localhost:3000)
  • Multipart: @fastify/multipart with 10MB file size limit

createApp() calls app.init() but does not call app.listen(). Each entry point handles the serving strategy:

Entry PointFileBehavior
Local devsrc/main.tsCalls createApp() then app.listen() on PORT (default 3001)
Vercelapi/index.tsCalls createApp(), caches the app instance, pipes Node.js requests through Fastify
Cloudflare Workerssrc/worker.tsLazy init, transfers env to process.env, uses Fastify .inject()

Deployment

Vercel (Serverless)

The backend deploys to Vercel as a single serverless function:

  • vercel.json routes all requests (/(.*)) to api/index.ts via @vercel/node
  • The NestJS app is created once on cold start and cached for subsequent requests
  • Environment variables are configured in the Vercel dashboard

Cloudflare Workers

  • wrangler.toml configures the worker with nodejs_compat flag
  • src/worker.ts handles the fetch event and uses Fastify's .inject() method
  • Optional NestJS dependencies are stubbed via [alias] in wrangler.toml

Root Module (app.module.ts)

Imports all feature modules and configures global providers:

  • ConfigModule.forRoot({ isGlobal: true }) - environment variable access
  • ThrottlerModule - rate limiting (100 requests per 60 seconds per client)
  • APP_GUARDThrottlerGuard - applied globally to all endpoints
  • APP_GUARDVersioningGuard - validates X-API-Version header globally before handlers run

Module Dependency Tree

AppModule
├── ConfigModule (global)
├── ThrottlerModule (global guard)
├── SupabaseModule (global - provides DB clients)
├── AuthModule (exports AuthGuard; imports ImagesModule for avatar endpoints)
├── SchoolModule
├── AcademicYearModule
├── TermModule
├── StudentModule
├── SubjectModule
├── ClassModule (exports ClassTeacherGuard)
├── EnrollmentModule (imports ClassModule for guard)
├── GradingModule
├── CalculationModule
├── ReportingModule
├── ImagesModule (exports ImagesService - used by AuthModule for avatar uploads)
├── PaginationModule (global - exports PaginationService for offset/cursor pagination)
├── VersioningModule (global - exports VersioningService; includes TransformerRegistry and VersioningGuard)
└── CacheModule (global - exports CacheService with memory or Redis store)

See versioning.md for how API versioning and pagination work.

Supabase Integration

The SupabaseModule is marked @Global() and provides SupabaseService to all modules. It offers two types of clients:

MethodUse CaseAuth
getServiceClient()Server-side operations that bypass RLSUses SUPABASE_SERVICE_ROLE_KEY
createUserClient(token, schema)User-context operations that respect RLSUses the user's JWT; schema can be public, student, grading, reporting, or staff

Database Schemas

The PostgreSQL database uses multiple schemas to organize tables:

SchemaContains
publicuser_profile, school, academic_year, term, subject, student_group
studentstudent, student_group_enrollment, student_subject_profile
staffteacher_group_assignment, teacher_subject_assignment
gradingassessment, grade
reportingreport_book, report_book_entry, report_book_pdf, class_report_file

Guards

GuardLocationBehavior
ThrottlerGuardGlobal (APP_GUARD)Rate limits all endpoints
VersioningGuardGlobal (APP_GUARD)Validates X-API-Version header; rejects invalid/non-existent versions with 400
AuthGuardauth/auth.guard.tsValidates Bearer JWT via Supabase getUser; populates request.user with { id, email, access_token }
ClassTeacherGuardclass/class-teacher.guard.tsChecks if the user is an admin or the class teacher for the :classId route parameter

Environment Variables

VariableRequiredDescription
SUPABASE_URLYesSupabase project URL
SUPABASE_SERVICE_ROLE_KEYYesService role key for admin operations
SUPABASE_PUSHABLE_KEYYesAnon/public key for user-context clients
FRONTEND_URLNoCORS origin (defaults to http://localhost:3000)
PORTNoServer port (defaults to 3001)
USE_REDISNoSet to true to use Redis for caching (defaults to in-memory)
REDIS_URLOnly if USE_REDIS=trueRedis connection URL (e.g., redis://localhost:6379)

Running the Backend

cd backend
bun install
bun run start:dev # Development with hot-reload
bun run build # Production build
bun run start:prod # Production server
bun run lint # ESLint check
bun run test # Unit tests (Bun test runner)

Deploying

# Vercel
vercel deploy # Deploy to Vercel (uses vercel.json)

# Cloudflare Workers
bun run deploy # Deploy via wrangler
bun run deploy:staging # Deploy to staging environment