A RESTful API backend for a vocabulary learning application, built with NestJS and TypeScript. Features include multi-method authentication, spaced repetition study tracking, AI-powered vocabulary suggestions, real-time notifications, and background job processing.
- Local Authentication: Username/password sign-up with email verification (OTP via email)
- Google OAuth 2.0: Authorization Code Flow with automatic user provisioning
- Magic Link: Passwordless email sign-in with time-limited verification tokens
- JWT Token Management: Access token + refresh token pair with Token Rotation strategy
- Access token expires in configurable duration (default: 30 min)
- Refresh token expires in configurable duration (default: 14 days)
- Session-based JTI (JWT ID) stored in Redis for token invalidation
- Supports multiple concurrent device sessions
- Password Management: Change password & reset password (OTP-based) flows
- Rate Limiting: Configurable attempt limits on email verification and password reset requests
- Role-Based Access Control (RBAC): Global
AuthGuard+RoleBasedAccessControlGuardwith decorator-based configuration
- Full CRUD operations for vocabulary decks
- Visibility control: PUBLIC / PROTECTED (passcode) / PRIVATE
- Deck cloning: Clone shared decks with learner count tracking & real-time notification to owner
- Restart progress: Reset all card streaks and review dates
- Soft delete: Decks use
deletedAtfilter for soft deletion - Auto-slug generation: Deck slugs generated automatically via
slugifyon create/update - Pagination: Configurable limit, offset, search, orderBy, and sort order
- Unique constraints: Deck name and slug are unique per owner
- Cards track
streak(consecutive correct answers),reviewDate, andstatus - Automatic status transitions via MikroORM lifecycle hooks (
@BeforeCreate,@BeforeUpdate):newβ noreviewDatesetlearningβreviewDate β€ todayknownβreviewDate > today
- Study answers saved in batch with background job to update user statistics
- Study streaks: Current streak and longest streak tracking
- Total cards learned: Cumulative count of mastered cards
- Mastery rate: Percentage of known cards across all decks
- Last study date: Tracked for streak calculations
- Statistics updated asynchronously via BullMQ background jobs (
StudyProcessor)
- Semantic similarity search using Qdrant vector database
- Cohere embedding model (
embed-multilingual-v3.0) via LangChain for multilingual support - Term suggestion: Look up card definitions from a pre-embedded vocabulary dataset (cached in Redis)
- Next card suggestion: Recommend related vocabulary cards based on semantic similarity
- Batch data embedding: Admin-only endpoint to embed vocabulary data into Qdrant
- WebSocket gateway using Socket.IO with custom
SocketIOAdapter - Authenticated WebSocket connections (JWT verification on handshake)
- Room-based notification delivery (user-specific rooms)
- Clone notifications sent in real-time when a user clones another's deck
- Transactional emails via Resend
- React Email templates for OTP verification and magic link emails
- Asynchronous processing: Emails sent through BullMQ queue (
MailProducerβMailConsumer) - Template preview available via
pnpm email:dev
- Avatar upload: File upload with validation (image type/size)
- Background processing: Images uploaded to ImageKit asynchronously via BullMQ (
UserProcessor) - Local file cleanup after successful upload
| Category | Technology |
|---|---|
| Framework | NestJS |
| Language | TypeScript |
| Database | PostgreSQL |
| ORM | MikroORM (with migrations & seeding) |
| Caching | Redis via @nestjs/cache-manager + Keyv |
| Message Queue | BullMQ (Redis-backed) |
| Vector Database | Qdrant |
| AI/Embeddings | LangChain + Cohere |
| WebSocket | Socket.IO via @nestjs/websockets |
| Authentication | JWT + argon2 + Google OAuth 2.0 |
| Image Storage | ImageKit |
| Resend + React Email | |
| API Documentation | Swagger via @nestjs/swagger |
| Validation | class-validator + class-transformer |
| Security | helmet, compression, CORS |
| Linting & Formatting | Biome |
| Commit Convention | Commitlint + Husky + lint-staged |
| Testing | Jest + Supertest |
| Containerization | Docker (multi-stage build, Node 24 Alpine) |
| Reverse Proxy | Caddy |
| Package Manager | pnpm |
vocabify_be/
βββ src/
β βββ main.ts # Application bootstrap (CORS, guards, pipes, filters, Swagger, WebSocket adapter)
β βββ app.module.ts # Root module (ConfigModule, MikroORM, CacheModule, BullModule)
β βββ app.controller.ts # Health check endpoint
β βββ socket-io.adapter.ts # Custom Socket.IO adapter with JWT authentication
β β
β βββ config/ # Environment configuration namespaces
β β βββ app.config.ts # App settings (host, port, environment, API prefix, frontend URL)
β β βββ auth.config.ts # JWT expiry settings
β β βββ database.config.ts # PostgreSQL connection settings
β β βββ google.config.ts # Google OAuth credentials
β β βββ integration.config.ts # Third-party API keys (Cohere, ImageKit)
β β βββ mail.config.ts # Email service settings (Resend)
β β βββ redis.config.ts # Redis connection settings
β β βββ vector-db.config.ts # Qdrant connection settings
β β βββ validate-config.ts # Configuration validation helper
β β
β βββ db/ # Database layer
β β βββ entities/ # MikroORM entities
β β β βββ base.entity.ts # BaseEntity (id, createdAt, updatedAt) & SoftDeleteBaseEntity (+deletedAt)
β β β βββ user.entity.ts # User (username, email, password, role, avatarUrl)
β β β βββ deck.entity.ts # Deck (name, slug, visibility, passcode, viewCount, learnerCount)
β β β βββ card.entity.ts # Card (term, definition, languages, streak, reviewDate, status)
β β β βββ card-suggestion.entity.ts # CardSuggestion (pre-loaded vocabulary data)
β β β βββ notification.entity.ts # Notification (content, readAt, actor, recipient)
β β β βββ user-statistics.entity.ts # UserStatistic (streaks, masteryRate, totalCardsLearned)
β β βββ migrations/ # Database migrations
β β βββ seeders/ # Data seeders
β β
β βββ modules/ # Feature modules
β β βββ auth/ # Authentication & authorization
β β β βββ auth.controller.ts # Auth endpoints (login, sign-up, logout, refresh, OAuth, magic-link, password reset)
β β β βββ auth.service.ts # Auth business logic (token creation, verification, OTP)
β β β βββ auth.dto.ts # Request DTOs
β β β βββ auth.res.dto.ts # Response DTOs
β β β
β β βββ deck/ # Deck management
β β β βββ deck.controller.ts # CRUD + clone + restart + shared deck endpoints
β β β βββ deck.service.ts # Deck business logic
β β β βββ deck.enum.ts # Visibility, CardStatus enums
β β β βββ dtos/ # Request & response DTOs
β β β
β β βββ study/ # Study & spaced repetition
β β β βββ study.controller.ts # Save answers, get user stats
β β β βββ study.service.ts # Save answers with batch card updates
β β β βββ study.processor.ts # BullMQ worker: update user statistics asynchronously
β β β
β β βββ suggestion/ # AI-powered vocabulary suggestions
β β β βββ suggestion.controller.ts # Term suggestion, next card suggestion, embed data (admin)
β β β βββ suggestion.service.ts # Qdrant vector search + Cohere embeddings + Redis caching
β β β
β β βββ user/ # User management
β β β βββ user.controller.ts # Avatar upload endpoint
β β β βββ user.service.ts # User business logic
β β β βββ user.processor.ts # BullMQ worker: upload avatar to ImageKit asynchronously
β β β
β β βββ notification/ # Real-time notifications
β β β βββ notification.gateway.ts # WebSocket gateway (Socket.IO)
β β β βββ notification.service.ts # Notification business logic
β β β
β β βββ mail/ # Email service
β β β βββ mail.producer.ts # Enqueues email jobs to BullMQ
β β β βββ mail.consumer.ts # Processes email jobs from queue
β β β βββ mail.service.ts # Sends emails via Resend
β β β βββ render-email.tsx # React Email renderer
β β β βββ templates/ # React Email templates
β β β
β β βββ redis/ # Redis wrapper service
β β β βββ redis.service.ts # Get/set/delete values, rate limiting (attempt tracking)
β β β
β β βββ image-kit/ # ImageKit integration
β β βββ image-kit.module.ts # Provides ImageKit client as injectable token
β β
β βββ common/ # Shared utilities & components
β βββ constants/ # App-wide constants (rate limits, etc.)
β βββ decorators/ # Custom decorators
β β βββ api-endpoint.decorator.ts # Combined Swagger + auth decorator
β β βββ api-file.decorator.ts # File upload decorator
β β βββ api-public.decorator.ts # Mark endpoints as public (skip auth)
β β βββ rbac.decorator.ts # Role-based access control
β β βββ use-cache.decorator.ts # HTTP response caching
β β βββ user.decorator.ts # Extract user from JWT payload
β β βββ validators.decorator.ts # Custom validation decorators
β β βββ transforms.decorator.ts # DTO transform decorators
β βββ dtos/ # Shared DTOs (PaginatedDto, SuccessResponseDto)
β βββ enums/ # Shared enums (NodeEnv, UserRole, JwtToken, QueueName, JobName)
β βββ filters/ # GlobalExceptionFilter
β βββ guards/ # AuthGuard, RoleBasedAccessControlGuard
β βββ interceptors/ # HttpCacheInterceptor
β βββ interfaces/ # Shared interfaces
β βββ pipes/ # FieldsValidationPipe, image validation pipe
β βββ types/ # Shared types (UUID, JWT payloads, job data types)
β βββ utils/ # Utility functions (UUID, Redis keys, pagination, etc.)
β
βββ test/ # End-to-end tests
βββ uploads/ # Temporary local file uploads
β
βββ .docker/ # Docker base service definitions
βββ caddy/ # Caddy reverse proxy configuration
βββ compose.yml # Production Docker Compose
βββ compose.dev.yml # Development Docker Compose
βββ compose.local.yml # Local development (DB, Redis, Qdrant only)
βββ Dockerfile # Multi-stage build (Node 24 Alpine)
βββ Makefile # Shortcut commands for Docker operations
β
βββ mikro-orm.config.ts # MikroORM CLI configuration
βββ biome.json # Biome linter & formatter config
βββ commitlint.config.ts # Commit message linting
βββ jest.config.ts # Jest test configuration
βββ tsconfig.json # TypeScript configuration (path aliases)
βββ package.json
User ββ1:NβββΆ Deck ββ1:NβββΆ Card
β β
β βββ clonedFrom βββΆ Deck (self-reference)
β β
β βββ createdBy / updatedBy βββΆ UUID
β
βββ1:1βββΆ UserStatistic
β
βββ1:NβββΆ Notification (recipient)
βββ actor βββΆ User (nullable)
CardSuggestion (standalone, pre-loaded vocabulary data for AI suggestions)
| Queue | Job | Processor | Description |
|---|---|---|---|
STUDY |
UPDATE_USER_STATS |
StudyProcessor |
Updates streak, mastery rate, total cards learned |
IMAGE |
UPLOAD_USER_AVATAR |
UserProcessor |
Uploads avatar to ImageKit, updates DB, cleans up local file |
MAIL |
SEND_OTP / SEND_MAGIC_LINK |
MailConsumer |
Sends transactional emails via Resend |
| Component | Description |
|---|---|
helmet |
HTTP security headers |
compression |
Response compression |
CORS |
Configured for frontend origin with credentials |
AuthGuard |
Global JWT authentication (skippable via @ApiPublic()) |
RoleBasedAccessControlGuard |
RBAC enforcement via @RoleBaseAccessControl() |
FieldsValidationPipe |
Global DTO validation (transform + whitelist) |
GlobalExceptionFilter |
Centralized error handling |
SocketIOAdapter |
Custom WebSocket adapter with JWT auth on handshake |
- Node.js β₯ 18.0.0
- pnpm
- Docker & Docker Compose (for PostgreSQL, Redis, Qdrant)
# Clone the repository
git clone <repository-url>
cd vocabify_be
# Install dependencies
pnpm install- Copy
.env.exampleto.env.local:cp .env.example .env.local
- Fill in the required environment variables:
- Database:
DB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORD - Redis:
REDIS_HOST,REDIS_PORT,REDIS_PASSWORD - Auth:
JWT_SECRET,JWT_EXPIRES_IN,REFRESH_TOKEN_EXPIRES_IN - Google OAuth:
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GOOGLE_REDIRECT_URI - ImageKit:
IMAGEKIT_PUBLIC_KEY,IMAGEKIT_PRIVATE_KEY,IMAGEKIT_URL_ENDPOINT - Cohere:
COHERE_API_KEY - Qdrant:
VECTOR_DB_HOST,VECTOR_DB_PORT,VECTOR_DB_COLLECTION_NAME - Mail: Resend API key and sender configuration
- App:
HOST,PORT,NODE_ENV,API_PREFIX,FRONTEND_URL
- Database:
# Start PostgreSQL, Redis, and Qdrant containers
docker compose -f compose.local.yml --env-file .env.local up -d
# Or use Makefile shortcut
make upLocal# Development (watch mode)
pnpm start:dev
# Production
pnpm start:prodOnce running, Swagger UI is available at: http://localhost:<PORT>/<API_PREFIX>/docs
| Script | Description |
|---|---|
pnpm start:dev |
Start in development mode with watch & .env.local |
pnpm start:debug |
Start in debug mode with watch |
pnpm start:prod |
Start production build |
pnpm build |
Build the application |
pnpm lint |
Run Biome linter |
pnpm lint:fix |
Fix linting issues |
pnpm format |
Check code formatting |
pnpm format:fix |
Fix formatting issues |
pnpm check |
Run all Biome checks |
pnpm check:fix |
Fix all Biome issues |
pnpm test |
Run unit tests |
pnpm test:e2e |
Run end-to-end tests |
pnpm test:cov |
Run tests with coverage |
pnpm email:dev |
Preview React Email templates |
pnpm schema:fresh |
Drop & recreate database schema |
pnpm migration:create |
Create a new migration |
pnpm migration:up |
Run pending migrations |
pnpm mikro:debug |
Debug MikroORM configuration |
| Command | Description |
|---|---|
make upLocal |
Start local infrastructure (DB, Redis, Qdrant) |
make downLocal |
Stop local infrastructure |
make build |
Build and start production containers |
make up |
Start production containers |
make down |
Stop production containers |
make db |
Access PostgreSQL container shell |
make dev |
Run dev server inside Docker |
make prod |
Run production server inside Docker |
The project uses a multi-stage Dockerfile (Node 24 Alpine):
- Base: Install dependencies with
pnpm install --frozen-lockfile - Development: Full source code with dev dependencies
- Builder: Compile TypeScript & prune dev dependencies
- Production: Minimal image with only compiled code & production dependencies
| File | Purpose |
|---|---|
compose.local.yml |
Local development β PostgreSQL, Redis, Qdrant only |
compose.dev.yml |
Development β includes app container |
compose.yml |
Production β full stack with Caddy reverse proxy |
This project is private and proprietary.