This page is the high-level architecture reference for Innova Backend Serverless. It covers every tier of the system — from the client apps through API Gateway and JWT validation, into the Lambda functions and SQS queues, and out to storage and the AI engine. Read this before making structural changes to the codebase, adding Lambda functions, or modifying the SQS topology.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/vruizz22/innova-backend-serverless/llms.txt
Use this file to discover all available pages before exploring further.
System overview
The system is divided into three tiers that communicate through well-defined interfaces: Client apps (innova-clients monorepo). Three web/mobile surfaces built by the front-end team — a teacher/student/parent web app (Next.js), a mobile practice and parent app (Expo), and a public landing page (Astro). All three talk exclusively to api.superprofes.app; none call AI providers or databases directly.
API layer. AWS API Gateway receives every HTTP request and routes it to the api Lambda function. Before any controller code runs, the Supabase JWT guard validates the RS256 token against Supabase’s JWKS endpoint, extracts the role claim, and enforces RolesGuard on protected routes. Routes decorated with @Public() — GET / and all POST /auth/* endpoints (register, login, refresh, forgot-password, confirm-forgot-password) — bypass the guard.
Serverless backend. One NestJS application (src/) deployed as six Lambda functions via the Serverless Framework. The api function handles all HTTP traffic. The remaining five functions are background workers triggered by SQS events, S3 events, and a cron schedule. Together they own the SQS queues and S3 buckets that innova-ai-engine consumes — this ownership constraint determines the deploy order.
Request flow
The following sequence describes what happens when a student submits a digital math attempt end-to-end:Client sends POST /attempts to API Gateway
The client app (web or mobile) sends a JSON payload containing
studentId, topicCode, rawSteps (an array of {expression, isFinal} objects), expectedAnswer, and studentAnswer to https://api.superprofes.app/attempts. The optional exerciseId and courseId fields may also be included.Supabase JWT guard validates the RS256 token
API Gateway invokes the
api Lambda. The NestJS SupabaseStrategy (passport-jwt) fetches the Supabase JWKS, verifies the token’s RS256 signature and expiry, and populates the @CurrentUser() decorator. RolesGuard then checks the role claim. Invalid or expired tokens return 401; insufficient role returns 403.AttemptsController calls the Rule Engine
AttemptsController.create() deserialises the request body through CreateAttemptDto (class-validator + class-transformer with whitelist: true, forbidNonWhitelisted: true), then calls the Rule Engine service. The factory maps topicCode to the correct RuleStrategy implementation and runs synchronous pattern matching in under 5 ms.Classified path — BKT update, Postgres upsert, telemetry FIFO
If the strategy returns a concrete
errorTag (anything other than UNCLASSIFIED):MasteryService.updateBkt()computes the closed-form Bayesian update and upserts theStudentTopicMasteryrow in Postgres.TelemetryServicepublishes the raw attempt event to theattempt-stream.fifoSQS queue.- The controller returns
201with{ attemptId, isCorrect, errorTag, confidence, source: "rule", pKnown }.
Unclassified path — SQS LLM queue, immediate 201
If the rule engine returns
{ errorTag: "UNCLASSIFIED" }, the attempt row is written to Postgres with that tag, and attemptId is published to the llm-classify-queue SQS Standard queue. The controller still returns 201 immediately with { attemptId, isCorrect, errorTag: "UNCLASSIFIED" }. The resolved classification arrives asynchronously — clients can poll GET /attempts/:id/status.Lambda functions
Theserverless.yml defines six Lambda functions. All run on nodejs20.x with esbuild bundling.
| Function | Handler | Trigger | Purpose |
|---|---|---|---|
api | src/lambda.handler | HTTP (API Gateway) | All REST endpoints — handles every route in the NestJS app via @vendia/serverless-express |
telemetryWorker | src/infrastructure/workers/telemetry-persister.handler | SQS FIFO batch (attempt-stream.fifo) | Persists raw attempt telemetry events to MongoDB Atlas and archives a copy to S3 |
llmClassifierWorker | src/infrastructure/workers/llm-classifier.handler | SQS Standard (llm-classify-queue) | Forwards UNCLASSIFIED attempt IDs to innova-ai-engine via Anthropic Claude; writes the resolved errorTag back to Postgres |
ocrWorker | src/infrastructure/workers/ocr-worker.handler | S3 ObjectCreated on ocr-uploads bucket | Transcribes handwritten math photos; sends transcription to Gemini; publishes result to attempt-reprocess-queue |
alertGenerator | src/infrastructure/workers/alert-generator.handler | EventBridge cron (hourly) | Queries StudentTopicMastery for students whose pKnown has dropped or stagnated; upserts TeacherAlert rows |
attemptReprocessWorker | src/infrastructure/workers/attempt-reprocess.handler | SQS Standard (attempt-reprocess-queue) | Converts OCR-transcribed step arrays back into POST /attempts-equivalent payloads and routes them through the rule engine |
The
api Lambda caches the NestJS application instance in a module-level variable (cachedServer) across warm invocations. Cold starts re-run bootstrap() and recreate the Prisma and Mongoose connections. If bootstrap fails, subsequent invocations return a structured 500 rather than crashing silently.Storage layers
The system uses three storage technologies with distinct responsibilities: Supabase Postgres (Prisma, relational). The source of truth for all structured, relational data: users, teachers, students, courses, enrollment, curriculum (subjects → units → topics), exercises, attempts, mastery state (StudentTopicMastery with pKnown and trend7d), teacher alerts, guides, and the ErrorTag catalog. Managed via Prisma 7 migrations; accessed through @prisma/adapter-pg with a pg connection pool configured for serverless (connection_limit=1).
MongoDB Atlas (raw telemetry). Schema-less, high-volume storage for attempt events (keystrokes, intermediate steps, replay data) and AI job audit records (raw LLM request/response pairs, cost tracking per operation). Written exclusively by the telemetryWorker Lambda; read by analytics and debugging tooling. Uses Mongoose via @nestjs/mongoose.
AWS S3. Object storage for three categories of binary assets: teacher worksheet PDFs (S3_GUIDES_BUCKET), student submission photos (S3_SUBMISSIONS_BUCKET), and handwritten OCR upload images ({service}-{stage}-ocr-uploads — managed bucket, no env var override). Submission photos use random UUID filenames and are purged by an S3 lifecycle policy (30-day expiry). Presigned PUT URLs (TTL from GUIDES_PRESIGNED_PUT_TTL, default 600 s) are issued by POST /guides so clients upload directly to S3 without proxying through Lambda.
innova-ai-engine integration
Heavy AI work is intentionally isolated in a separate Python Lambda service (innova-ai-engine) to avoid inflating the Node.js bundle, manage Python dependency conflicts, and allow independent scaling and cost control. The AI engine handles:
- PDF extraction — parsing uploaded teacher worksheets into structured question objects
- OCR — transcribing handwritten student math photos via Gemini Vision
- LLM classification — classifying
UNCLASSIFIEDattempts via Claude against the 2,600+ error taxonomy - BKT/IRT nightly calibration — recalculating per-topic BKT parameters and per-student IRT ability estimates
- Exercise generation — producing new exercises for a given topic and difficulty level
attemptId, guideId); no PII reaches the AI providers.
Deploy topology
| Property | Value |
|---|---|
| Framework | Serverless Framework 3 |
| Bundler | serverless-esbuild |
| Runtime | nodejs20.x |
| Region | us-east-1 |
| AWS account | 751871643325 |
| Custom domain | api.superprofes.app (via serverless-domain-manager) |
| CI/CD | GitHub Actions — ci.yml (type-check + lint + tests on PRs), deploy.yml (deploy on merge to main) |
main):
Explore further
Lambda Functions
Detailed configuration, memory settings, timeout values, and dead-letter queue setup for each of the six Lambda functions.
SQS Queues
Queue names, visibility timeouts, batch sizes, retry policies, and DLQ configuration for every SQS queue owned by this backend.
Data Model
Full Prisma v9 schema walkthrough — entities, enums, relations, and the cost-accounting and privacy design decisions.
Deployment Guide
Step-by-step production deployment runbook, GitHub Actions secrets reference, and the back-merge release flow.