Klerk
API backend con IA para LearnYos (NestJS)
API backend de la plataforma educativa LearnYos. Construida con NestJS 10, TypeORM y PostgreSQL, integra Google Gemini con rotación de keys y fallback de modelos para generar exámenes, flashcards, notas y chat educativo en streaming. Auth con JWT + Google OAuth, créditos diarios, likes y chat global. Licencia AGPL-3.0.
Contexto y propósito
Klerk es el backend del sistema Kire / LearnYos: API educativa con IA que genera exámenes (quiz e ICFES), flashcards y notas desde un tema o archivo, con chat educativo en streaming, créditos diarios y comunidad. Documentado en README y ARCHITECTURE.md.
Decisión clave: monolito modular NestJS en vez de microservicios, para que un solo desarrollador pueda mantener 11 dominios con un único deploy y una sola base PostgreSQL.
Propósito declarado: Servir y persistir todo el dominio educativo de LearnYos y orquestar la generación con IA (exámenes, flashcards, notas, chat) con control de costos por créditos.
Arquitectura
Monolito modular NestJS 10 por features (auth, users, exams, flash-cards, notes, messages, gemini, credits, likes, global-chat, exam-attempts). TypeORM + PostgreSQL, ValidationPipe estricta, Helmet, rate-limit por endpoint, ApiKeyGuard global + JWT.
┌──────────┐ HTTPS + x-api-key + JWT ┌─────────────────────────────────┐
│ Frontend │ ────────────────────────▶ │ Klerk API (NestJS 10, Express) │
│ LearnYos │ │ auth │ users │ exams │ flash-cards │
└──────────┘ │ notes │ messages │ gemini │ credits │
│ likes │ global-chat │ exam-attempts │
└──────┬──────────────┬───────────────┘
│ │
┌──────────▼─────┐ ┌──────▼──────────────┐
│ PostgreSQL │ │ Gemini API (rotación│
│ TypeORM │ │ keys + fallback) + │
└────────────────┘ │ SMTP + Google OAuth │
└─────────────────────┘ - Capas por módulo: *.controller.ts (HTTP, DTOs, guards) → *.service.ts (negocio) → entities/*.entity.ts (TypeORM) → dto/*.dto.ts (class-validator). Transversal en src/common (guards, filtros, decoradores, storage).
- Arranque en src/main.ts: CORS por env, rate-limit por ruta sensible, Helmet con CSP, ValidationPipe global (whitelist + forbidNonWhitelisted + transform), sanitización XSS, AllExceptionsFilter y ApiKeyGuard global.
- Módulo gemini centraliza prompts (AI_PROMPTS.ts), salida JSON por responseSchema, JsonExtractor anti-markdown, multimodal base64 y streaming SSE vía generateContentStream.
Stack detallado
| Capa | Tecnología | Detalle |
|---|---|---|
| Framework | NestJS 10 + Express | DI por módulos, platform-express |
| ORM / BD | TypeORM 0.3 + PostgreSQL (pg) | autoLoadEntities, synchronize solo dev |
| IA | @google/generative-ai | Rotación GEMINI_API_KEY(s) ante 429; fallback gemini-2.5-flash-lite → 2.5-flash → 2.0-flash-lite → 1.5-flash |
| Auth | Passport JWT + Google OAuth 2.0 + bcryptjs | Access 24h + refresh SHA-256; invitados con isGuest |
Nodemailer SMTP | Verificación de cuentas | |
| Seguridad | Helmet + throttler + express-rate-limit | 20 req/15min en /auth; límites diarios de generación |
| Tests | Jest + Supertest | Unit + e2e |
Módulos / servicios
- auth: Registro/login local, Google (código e ID token), invitados, refresh, verificación email.
- users: CRUD + borrado lógico pendingDeletion.
- exams: Exámenes quiz/ICFES con Exam → ExamQuestion → ExamOption.
- flash-cards: Mazos Card → FlashCard.
- notes: Note → NoteContent (Markdown enriquecido).
- messages: Chats con IA en JSON o SSE, con archivos adjuntos.
- gemini: Wrapper IA: prompts, schemas, rotación, multimodal, streaming.
- credits: 30 créditos/día por usuario para generación.
- likes: Likes polimórficos exam|card|note con UNIQUE(userId, cardType, cardId).
- global-chat: Chat comunitario.
- exam-attempts: Intentos + estadísticas; formatters deck/exam.
Endpoints principales
| Método | Ruta | Qué hace | Auth |
|---|---|---|---|
POST | /auth/register · /auth/login · /auth/google · /auth/refresh · /auth/me · /auth/guest | Ciclo completo de autenticación | x-api-key (+ JWT según ruta) |
POST | /exams/generate/topic_or_reference | Generar examen por tema o archivo | JWT + créditos |
POST | /flash-cards/generate/topic_or_reference | Generar mazo por tema o archivo | JWT + créditos |
POST | /notes/generate/topic_or_reference | Generar nota por tema o archivo | JWT + créditos |
GET | /exams · /exams/play/:id · /exams/code/:code · /exams/search | CRUD + jugar + compartir + buscar | JWT |
POST | /messages/send · /messages/send/stream (SSE) | Chat educativo JSON o streaming | JWT |
GET | /credits/status · /credits/costs | Saldo y costos de IA | JWT |
GET | /health · /ping · /gemini/health | Health checks públicos | Ninguna / x-api-key |
Datos y conexiones
User 1──* Note 1──1 NoteContent User 1──* Exam 1──* ExamQuestion 1──* ExamOption User 1──* Card 1──* FlashCard User 1──* Chat 1──* Message User 1──* DailyCredits · CardLike · ExamAttempt Códigos de 5 caracteres en Note/Exam/Card para compartir.
- Índices en userId y createdAt; cascadas en hijos; UNIQUE en likes para evitar dobles.
- Uploads en /uploads servidos estático + enviados en base64 a Gemini para análisis multimodal.
Autenticación y seguridad
- ApiKeyGuard global exige x-api-key salvo /health, /ping, /auth/* o JWT válido.
- JwtGuard / JwtAuthGuard / RequireAuthGuard + decoradores @RequireAuth/@AllowGuest para bloquear invitados donde no apliquen.
- Límite 1 MB por body (413), sanitización <script>/on*= en body permitiendo Markdown, TypeORM parametrizado anti-SQLi, frameguard deny.
Despliegue
Dónde: Render/Railway/VPS + PostgreSQL gestionado (Aiven/Neon con SSL)
- pnpm install --frozen-lockfile && pnpm build
- NODE_ENV=production, synchronize=false + migraciones TypeORM
- Reverse proxy Nginx/Caddy con HTTPS; BACKEND_URL al dominio público
- Puerto configurable (default 2300)
Variables de entorno clave (.env — tú pones los valores):
DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME JWT_SECRET, JWT_EXPIRATION, API_KEY GEMINI_API_KEY, GEMINI_API_KEY_2 GOOGLE_CLIENT_ID/SECRET, MAIL_USER/MAIL_PASS, CORS_ORIGINS
Metodología y decisiones
- Docs como código: README y ARCHITECTURE.md se actualizan con cada módulo para mantener la documentación al día sin leer todo el código.
- Seguridad por defecto: todo cerrado salvo lo explícitamente público; validación estricta que rechaza propiedades extra.
- Economía de IA: créditos + rate-limit + rotación de keys para no quemar cuota en un abuso.
Retos y soluciones
Qué aprendí
- Diseño de APIs modulares NestJS a escala (11 dominios).
- Orquestación de LLMs en producción: prompts, schemas, streaming y costos.
- Endurecimiento real: Helmet, rate-limit granular, XSS, ApiKey global.
Ejecutar en local
git clone https://github.com/xenoxf/klerk && cd klerk pnpm install cp .env.example .env y completar DB, JWT_SECRET, API_KEY, GEMINI_API_KEY(s) pnpm run start:dev → http://localhost:2300
Próximos pasos
- Migraciones TypeORM versionadas para producción.
- Cola (BullMQ) para generaciones largas con reintentos.
- Evaluación automática de calidad de exámenes generados.