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.

2026 · En producción

Captura de Klerk
Klerk — captura del proyecto.
01

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.

02

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.
03

Stack detallado

CapaTecnologíaDetalle
FrameworkNestJS 10 + ExpressDI por módulos, platform-express
ORM / BDTypeORM 0.3 + PostgreSQL (pg)autoLoadEntities, synchronize solo dev
IA@google/generative-aiRotación GEMINI_API_KEY(s) ante 429; fallback gemini-2.5-flash-lite → 2.5-flash → 2.0-flash-lite → 1.5-flash
AuthPassport JWT + Google OAuth 2.0 + bcryptjsAccess 24h + refresh SHA-256; invitados con isGuest
EmailNodemailer SMTPVerificación de cuentas
SeguridadHelmet + throttler + express-rate-limit20 req/15min en /auth; límites diarios de generación
TestsJest + SupertestUnit + e2e
04

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.
05

Endpoints principales

MétodoRutaQué haceAuth
POST/auth/register · /auth/login · /auth/google · /auth/refresh · /auth/me · /auth/guestCiclo completo de autenticaciónx-api-key (+ JWT según ruta)
POST/exams/generate/topic_or_referenceGenerar examen por tema o archivoJWT + créditos
POST/flash-cards/generate/topic_or_referenceGenerar mazo por tema o archivoJWT + créditos
POST/notes/generate/topic_or_referenceGenerar nota por tema o archivoJWT + créditos
GET/exams · /exams/play/:id · /exams/code/:code · /exams/searchCRUD + jugar + compartir + buscarJWT
POST/messages/send · /messages/send/stream (SSE)Chat educativo JSON o streamingJWT
GET/credits/status · /credits/costsSaldo y costos de IAJWT
GET/health · /ping · /gemini/healthHealth checks públicosNinguna / x-api-key
06

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.
07

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.
09

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
10

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.
11

Retos y soluciones

Gemini devuelve markdown con JSON dentroJsonExtractor que limpia fences y parsea; responseSchema para forzar JSON mode.
429 de cuotaRotación automática de 2 keys + fallback de 4 modelos ordenados por costo/calidad.
Secretos expuestos en historialPurgado de git + rotación obligatoria documentada en README antes de publicar.
12

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.
13

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
14

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.
↗

Seguir explorando