This repository contains the IWords learning platform built as a Turborepo monorepo with:
- apps/admin — Admin dashboard (Next.js)
- apps/app — Learner-facing app (Next.js)
- apps/api — Backend API (NestJS + Prisma)
Supporting services are provisioned with Docker (PostgreSQL, Redis, Mailhog). Commit workflow is standardized with Husky, Commitlint, and Commitizen.
![]() |
![]() |
|---|---|
![]() |
![]() |
![]() |
- Monorepo & DX
- Turborepo (task pipeline and caching)
- TypeScript, ESLint, Prettier
- Husky + Commitlint + Commitizen
- Frontends
- Next.js 15
- React 19
- Tailwind CSS 4
- Radix UI, lucide-react, tanstack/react-query
- Backend
- NestJS 11 (CORS + Helmet)
- Prisma ORM (PostgreSQL)
- Redis (caching/queues)
- Swagger (OpenAPI docs under /api/docs)
- Clerk (auth/webhooks integration)
.
├─ apps/
│ ├─ admin/ # Next.js Admin (port 3000)
│ ├─ app/ # Next.js App (port 3001)
│ └─ api/ # NestJS API (port 3003 by default)
├─ docker-compose.yml # Postgres, Redis, Mailhog
├─ turbo.json # Turborepo task pipeline
├─ package.json # Workspace scripts
└─ README.md
- Node.js >= 18
- npm (repo configured with npm@11.x in package.json)
- Docker Desktop (or Docker Engine + Compose plugin)
- Clone and install dependencies
npm install
- Start infrastructure (Postgres, Redis, Mailhog)
docker compose up -d
- Postgres: localhost:5432 (db: iwords_db, user: postgres, pass: postgres)
- Redis: localhost:6379
- Mailhog UI: http://localhost:8025 (SMTP on 1025)
- Create environment files
Create per-app .env files as needed.
apps/api/.env (example)
# Database (Prisma)
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/iwords_db?schema=public"
# JWT
JWT_SECRET="change-me"
# Redis
REDIS_URI="redis://localhost:6379"
# Frontend URLs (used by backends to craft links)
ADMIN_FRONTEND_URL="http://localhost:3000"
# Clerk (Auth)
# Note: code references CLERK_PUBLIC_KEY in main.ts and schema expects CLERK_PUBLISHABLE_KEY.
# Provide both to be safe for local dev.
CLERK_SECRET_KEY="sk_test_xxx"
CLERK_WEBHOOK_SECRET="whsec_xxx"
CLERK_PUBLISHABLE_KEY="pk_test_xxx"
CLERK_PUBLIC_KEY="pk_test_xxx"
# Optional
# PORT=3003 # default fallback if not set
apps/admin/.env.local (example)
# Backend base URL used by admin frontend
BACKEND_URL="http://localhost:3003"
# Self URL (useful for links)
SELF_URL="http://localhost:3000"
# Secret for admin-only flows if required by the app
# (Referenced in src/lib/constants.ts)
SECRET_KEY="change-me"
apps/app/.env.local (example)
# Backend base URL used by learner app
BACKEND_URL="http://localhost:3003"
# Optionally define these if later required by code
# SELF_URL="http://localhost:3001"
# SECRET_KEY="change-me"
- Prepare the database (Prisma)
# From repo root or apps/api
cd apps/api
npm run prisma:generate
npm run prisma:migrate # generates/updates your local schema
# Optional: seed (if a seeding script is configured)
npm run prisma:seed
- Run all apps in dev mode (Turborepo)
From repository root:
npm run dev
This runs turbo run dev and launches:
- Admin: http://localhost:3000
- App: http://localhost:3001
- API: http://localhost:3003 (Swagger: http://localhost:3003/api/docs)
You can also run each app individually:
-
Admin
cd apps/admin npm run dev # port 3000 -
App
cd apps/app npm run dev # port 3001 -
API
cd apps/api npm run dev:watch # or npm run dev
Root scripts (package.json):
npm run dev—turbo run dev(starts all workspace apps with dev scripts)npm run build—turbo run buildnpm run lint—turbo run lintnpm run check-types—turbo run check-typesnpm run format— Prettier write for ts/tsx/md
API scripts (apps/api/package.json):
dev,dev:watch,start,start:prodbuildlinttest,test:watch,test:cov,test:e2e- Prisma:
prisma:generate,prisma:migrate,prisma:migrate:deploy,prisma:migrate:reset,prisma:studio,prisma:seed create:superuser— runs CLI command from compiled dist:# Build first to generate dist/ npm run build node dist/src/cli/cli.js create-admin-user
Admin/App scripts (apps/admin & apps/app):
dev,build,start,lint
- Global prefix:
/api - Swagger:
/api/docs(on the API base URL) - Default dev port:
3003(configurable viaPORT) - Security middleware: Helmet, CORS enabled
- Auth: Clerk (see env vars in
apps/api/env-schema.tsandapps/api/src/main.ts)
- Conventional commits are enforced.
- Use Commitizen for commits:
npm run commit - Husky hooks + Commitlint validate commit messages.
- Postgres:
postgresql://postgres:postgres@localhost:5432/iwords_db - Redis:
redis://localhost:6379 - Mailhog UI: http://localhost:8025 (SMTP: 1025)
Stop services:
docker compose down
This project is currently UNLICENSED.
This section expands on the backend service located at apps/api (NestJS 11 + Prisma).
- Framework: NestJS with global prefix
/api. - Middlewares: Helmet (security headers), CORS enabled.
- Validation:
ValidationPipewithwhitelist,forbidNonWhitelisted, and automatic transformation. - Documentation: Swagger at
/api/docswith two bearer schemes (admin-auth,app-auth). - Authn/Authz: Clerk middleware initialized early in the pipeline; JWTs for protected endpoints.
- Data: PostgreSQL via Prisma ORM.
- Caching/Queues: Redis (available; used where applicable by modules/background tasks).
- CQRS/Events:
@nestjs/cqrsused across modules. You will see application event handlers in several domains (see below).
Defined/validated in apps/api/env-schema.ts using Joi:
DATABASE_URL(required) — PostgreSQL connection string, e.g.postgresql://postgres:postgres@localhost:5432/iwords_db?schema=public.JWT_SECRET(required) — secret used for signing/validating JWT.REDIS_URI(required) — Redis connection URI, e.g.redis://localhost:6379.ADMIN_FRONTEND_URL(required) — base URL of the Admin app (used for links in emails, redirects, etc.).CLERK_WEBHOOK_SECRET(required) — secret for verifying Clerk webhooks.CLERK_SECRET_KEY(required) — Clerk backend secret.CLERK_PUBLISHABLE_KEY(required) — Clerk publishable key.
Notes:
- In
src/main.ts, the Clerk middleware readsCLERK_PUBLIC_KEY. For local dev, set bothCLERK_PUBLISHABLE_KEYandCLERK_PUBLIC_KEYto your publishable key value to avoid confusion. - The service listens on
PORTif set; otherwise defaults to3003.
Common scripts (see apps/api/package.json):
npm run dev— start Nest in dev mode.npm run dev:watch— dev mode with watch (recommended during active development).npm run build— compile todist/.npm start— start (non-watch).npm run start:prod— run compiled app fromdist/main.- Lint/Tests:
npm run lint,npm test,npm run test:watch,npm run test:cov,npm run test:e2e.
Prisma workflow:
npm run prisma:generate— generate Prisma client (run after schema changes).npm run prisma:migrate— create/apply dev migration.npm run prisma:migrate:deploy— apply migrations in non-dev environments.npm run prisma:migrate:reset— reset DB (destructive; use in local dev only).npm run prisma:studio— open Prisma Studio.npm run prisma:seed— seed database (if a seeding script is present).
Admin bootstrap (CLI):
- After
npm run build, you can create a superuser via:node dist/src/cli/cli.js create-admin-user
The codebase follows a modular, event-driven approach (CQRS). Representative modules and their responsibilities include:
- Dictionary
- Event handlers such as:
sentence-created.event-handler.tssentence-updated.event-handler.tssentence-deleted.event-handler.tsexpression-phrase-updated.event-handler.ts
- Event handlers such as:
- Repetition (spaced-repetition/learning flow)
repetition/application/event-handlers/answer-checked.event-handler.tsreacts to answer verification.
- Gamification (points, badges, streaks)
gamification/application/event-handlers/answer-checked.event-handler.tsupdates gamified state after answers are checked.
- Email Notification
- Event handlers for user lifecycle and admin operations:
user-identity-user-created.event-handler.tsadmin-requested-reset-password.event-handler.tsadmin-admin-user-invited.event-handler.ts
- Event handlers for user lifecycle and admin operations:
These handlers listen to domain/application events and perform side effects (e.g., updating aggregates, sending emails, publishing further events).
- The API integrates
@nestjs-modules/mailerfor email. - In local development, start Mailhog (
docker compose up -d) and browsehttp://localhost:8025to inspect outgoing emails. - SMTP is available on
localhost:1025.
- HTTP security headers via Helmet; CORS is enabled by default for local dev.
- Validation is strict: unknown fields are stripped (
whitelist) and explicitly forbidden (forbidNonWhitelisted). DTOs are transformed to expected types. - Authentication strategies:
- Clerk middleware validates requests; modules can additionally use Nest guards.
- JWT bearer tokens are documented in Swagger as two schemes:
admin-auth— Admin-facing endpoints.app-auth— Learner app endpoints.
- Open Swagger UI at
http://localhost:3003/api/docs. - Use the Authorize button to paste a JWT under either
admin-authorapp-authdepending on the endpoint.
Example curl (adjust endpoint and token accordingly):
curl -H "Authorization: Bearer <JWT>" \
-H "Content-Type: application/json" \
http://localhost:3003/api/<your-endpoint>
- Ensure all required env vars are provided; do not run with default/secrets from examples.
- Use
prisma migrate deployduring deployments; runprisma generateas part of build. - Configure
CORSorigins and Helmet policies appropriately per environment. - Provide durable Redis and Postgres services; set connection URIs accordingly.
- Keep Swagger enabled for internal environments only, or protect it behind auth.




