Project Codename:
lin-v2
Version:0.0.1
License: UNLICENSED (Private)
Last Updated: 2026-07-23
- Project Overview
- Technology Stack
- Architecture & Design Philosophy
- Complete Folder Structure
- Core Layer (
src/core) - Common Layer (
src/common) - Infrastructure Layer (
src/infrastructure) - Business Modules (
src/modules) - Shared Layer (
src/shared) - Application Bootstrap & Entry Point
- API Routing & Endpoints
- Configuration & Environment
- Authentication & Authorization
- Database Schema
- Caching Strategy
- Job Queue System
- File Storage (S3)
- Logging System
- Error Handling
- Rate Limiting & Security
- Swagger API Documentation
- Testing Strategy
- DevOps & Deployment
- Development Roadmap (Todo)
- Quick Reference β All Environment Variables
Noviq is a comprehensive educational platform designed to connect students and teachers. The platform enables users to share notes and learning materials, enroll in courses, study collaboratively, and enhance their overall learning experience. This repository (noviq-api) is the backend API that powers the entire Noviq ecosystem.
- Provide a secure, scalable REST API for the Noviq learning platform.
- Manage user identities, authentication, and authorization.
- Serve as the central hub for course management, content delivery, and communication.
- Handle background processing (emails, notifications) via job queues.
- Store and serve user-uploaded content (notes, materials, media) via cloud storage.
- Modular Architecture: clear separation of concerns β each feature module owns its full vertical slice (domain, application, infrastructure, presentation).
- Infrastructure Abstraction: technical adapters (database, cache, queue, storage) are isolated in the infrastructure layer so business modules don't couple to specific implementations.
- Production-Ready: structured logging, health checks, rate limiting, global error handling, and environment-based configuration from day one.
- Testable: Jest-based unit and e2e testing with testcontainers for real infrastructure verification.
| Category | Technology | Version |
|---|---|---|
| Runtime | Node.js | 22.x |
| Language | TypeScript | 5.7.3 |
| Framework | NestJS | 11.x |
| Package Manager | npm | β |
| Package | Purpose |
|---|---|
nodemailer |
SMTP email sending |
| Package | Purpose |
|---|---|
@nestjs/core |
NestJS runtime core |
@nestjs/common |
Decorators, pipes, guards, interceptors |
@nestjs/config |
Environment-based configuration |
@nestjs/platform-express |
HTTP server (Express adapter) |
@nestjs/swagger |
OpenAPI / Swagger documentation |
@nestjs/terminus |
Health check endpoints |
@nestjs/throttler |
Rate limiting |
@nestjs/schedule |
Cron job scheduling |
@nestjs/passport |
Authentication strategy integration |
@nestjs/jwt |
JWT token generation & verification |
@nestjs/typeorm |
TypeORM integration for NestJS |
@nestjs/bullmq |
BullMQ job queue integration |
@nestjs/platform-socket.io |
WebSocket support |
@nestjs/websockets |
WebSocket gateway decorators |
| Package | Purpose |
|---|---|
typeorm (0.3.30) |
ORM for database operations |
pg |
PostgreSQL driver |
ioredis |
Redis client (cache + queue backend) |
bullmq |
Job queue library (backed by Redis) |
| Package | Purpose |
|---|---|
bcrypt |
Password hashing |
passport + passport-jwt + passport-google-oauth20 |
Authentication strategies |
helmet |
HTTP security headers |
cookie-parser |
Cookie parsing middleware |
| Package | Purpose |
|---|---|
@aws-sdk/client-s3 |
AWS S3 client SDK |
@aws-sdk/lib-storage |
Multipart S3 uploads |
@aws-sdk/s3-request-presigner |
Signed URL generation |
google-auth-library |
Google OAuth2 verification |
| Package | Purpose |
|---|---|
winston |
Structured logging framework |
winston-daily-rotate-file |
Log rotation by date |
nest-winston |
NestJS-Winston integration |
| Package | Purpose |
|---|---|
class-validator |
DTO validation decorators |
class-transformer |
Plain-to-class transformation |
multer |
File upload handling |
| Package | Purpose |
|---|---|
@nestjs/cli |
NestJS project scaffolding |
@swc/core + @swc/cli |
Fast TypeScript compilation |
eslint (9.x) |
Linting (flat config) |
prettier |
Code formatting |
jest |
Test runner |
ts-jest |
TypeScript transformation for Jest |
supertest |
HTTP assertion for e2e tests |
@testcontainers/postgresql + @testcontainers/redis |
Real DB/Redis for integration tests |
jest-mock-extended |
Type-safe mocking |
nock |
HTTP request interception |
@faker-js/faker |
Test data generation |
The codebase follows a layered modular monolith pattern. Each layer has a specific responsibility, and modules within layers own their full vertical slice.
ββββββββββββββββββββββββββββ
β src/main.ts β β Entry point
ββββββββββββββ¬ββββββββββββββ
β
ββββββββββββββΌββββββββββββββ
β src/app.module.ts β β Root module (wires everything)
ββββββββββββββ¬ββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ
β β β
ββββββββΌβββββββ ββββββββββΌβββββββββ βββββββββΌββββββββ
β core/ β β infrastructure/ β β modules/ β
β (config, β β (technical β β (business β
β bootstrap, β β adapters) β β features) β
β swagger) β β β β β
ββββββββ¬βββββββ ββββββββββ¬βββββββββ βββββββββ¬ββββββββ
β β β
ββββββββΌβββββββ β βββββββββΌββββββββ
β common/ β β β shared/ β
β (reusable ββββββββββββββββΌβββββββββββββββΊ (cross- β
β artifacts) β β β module) β
βββββββββββββββ β βββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ
β β β
βββββββΌββββββ ββββββββββββΌβββββββββββ ββββββββΌβββββββ
β Database β β Cache / Redis β β Storage β
β (Postgres)β β β β (AWS S3) β
βββββββββββββ βββββββββββββββββββββββ βββββββββββββββ
Each feature module inside src/modules/ follows a DDD-inspired folder structure:
modules/<feature>/
βββ application/ # Use cases, application services
βββ domain/ # Entities, value objects, domain services
βββ infrastructure/ # Feature-specific technical adapters
βββ presentation/ # Controllers, DTOs (the HTTP layer)
-
Separation of Concerns: Infrastructure adapters are isolated from business logic. A business module never imports an S3 client directly β it goes through
S3StorageService. -
Global-by-Default for Cross-Cutting Concerns:
CoreModuleregisters global filters, guards, and pipes so every module gets them automatically. -
Convention over Configuration: Environment variables follow a naming convention (e.g.,
DATABASE_HOST,REDIS_HOST), and sensible defaults exist for development. -
Fail-Fast on Misconfiguration: Production environments require explicit secrets (JWT keys, AWS credentials) β the app refuses to start without them.
-
Token Type Safety: JWT tokens carry a
typeclaim (accessorrefresh) and the verifier rejects mismatches β a refresh token cannot be used where an access token is expected.
noviq-api/
β
βββ .claude/ # Claude Code configuration
β βββ settings.local.json # Local permission allowlists
β
βββ .git/ # Git repository metadata
β
βββ coverage/ # Test coverage reports (generated)
β βββ clover.xml
β βββ coverage-final.json
β βββ lcov.info
β βββ lcov-report/ # HTML coverage report
β
βββ dist/ # Compiled output (generated by build)
β
βββ docs/ # Project documentation
β βββ PROJECT_DOCUMENTATION.md # This file
β
βββ logs/ # Runtime log files (generated)
β βββ application-YYYY-MM-DD.log # Application-level logs
β βββ error-YYYY-MM-DD.log # Error-level logs
β βββ .*-audit.json # Audit trail files
β
βββ node_modules/ # Installed dependencies (generated)
β
βββ src/ # βββββββββ SOURCE CODE βββββββββ
β β
β βββ main.ts # Application entry point
β βββ app.module.ts # Root NestJS module
β βββ README.md # Source layout documentation
β β
β βββ core/ # ββ CORE LAYER ββ
β β βββ core.module.ts # Process-wide setup module
β β βββ bootstrap/
β β β βββ app.bootstrap.ts # App-wide middleware & pipes
β β βββ config/
β β β βββ env.utils.ts # Env parsing helpers
β β βββ swagger/
β β βββ swagger.config.ts # Swagger/OpenAPI setup
β β
β βββ common/ # ββ COMMON LAYER ββ
β β βββ constants/
β β β βββ app.constants.ts # App-wide constant values
β β βββ decorators/ # Custom decorators (stub)
β β βββ dto/
β β β βββ pagination-query.dto.ts # Reusable pagination DTO
β β βββ exceptions/ # Custom exceptions (stub)
β β βββ filters/
β β β βββ http-exception.filter.ts # Global exception filter
β β βββ guards/ # Shared guards (stub)
β β βββ interceptors/ # Shared interceptors (stub)
β β βββ middleware/ # Shared middleware (stub)
β β βββ pipes/ # Shared pipes (stub)
β β βββ types/ # Shared TypeScript types (stub)
β β βββ utils/ # Shared utility functions (stub)
β β
β βββ infrastructure/ # ββ INFRASTRUCTURE LAYER ββ
β β βββ infrastructure.module.ts # Aggregator module
β β β
β β βββ database/ # PostgreSQL / TypeORM
β β β βββ database.module.ts
β β β βββ services/
β β β βββ database.service.ts
β β β
β β βββ cache/ # Redis caching
β β β βββ cache.module.ts
β β β βββ constants/
β β β β βββ cache.constants.ts
β β β βββ redis/
β β β β βββ redis.ts # Redis connection provider
β β β βββ services/
β β β βββ cache.service.ts
β β β βββ cache-key.service.ts
β β β
β β βββ logging/ # Winston logging
β β β βββ logging.module.ts
β β β βββ config/
β β β β βββ logging.config.ts
β β β β βββ logging.config.spec.ts
β β β βββ constants/
β β β βββ logging.constants.ts
β β β
β β βββ queue/ # BullMQ job queue
β β β βββ queue.module.ts
β β β βββ config/
β β β β βββ queue.config.ts
β β β β βββ queue.config.spec.ts
β β β βββ constants/
β β β β βββ queue.constants.ts
β β β βββ services/
β β β β βββ queue.service.ts
β β β β βββ queue.service.spec.ts
β β β βββ types/
β β β βββ queue-job.type.ts
β β β
β β βββ security/ # Authentication & tokens
β β β βββ index.ts # Barrel export
β β β βββ jwt/
β β β βββ jwt.module.ts
β β β βββ config/
β β β β βββ jwt.config.ts
β β β βββ constants/
β β β β βββ jwt.constants.ts
β β β βββ services/
β β β β βββ token.service.ts
β β β β βββ token.service.spec.ts
β β β βββ types/
β β β βββ jwt-config.type.ts
β β β βββ jwt-payload.type.ts
β β β βββ jwt-token-type.type.ts
β β β βββ token-pair.type.ts
β β β
β β βββ storage/ # AWS S3 file storage
β β β βββ storage.module.ts
β β β βββ config/
β β β β βββ s3.config.ts
β β β β βββ s3.config.spec.ts
β β β βββ constants/
β β β β βββ storage.constants.ts
β β β βββ providers/
β β β β βββ s3.provider.ts
β β β βββ services/
β β β β βββ s3-storage.service.ts
β β β β βββ s3-storage.service.spec.ts
β β β βββ types/
β β β βββ s3-storage-config.type.ts
β β β βββ storage-object.type.ts
β β β βββ upload-options.type.ts
β β β
β β βββ mail/ # Email sending & templates
β β β βββ mail.module.ts
β β β βββ config/
β β β β βββ mail.config.ts
β β β βββ constants/
β β β β βββ mail.constants.ts
β β β βββ processors/
β β β β βββ email.processor.ts
β β β βββ services/
β β β β βββ email-sender.service.ts
β β β β βββ mail-template.service.ts
β β β βββ templates/
β β β β βββ index.ts
β β β β βββ base.template.ts
β β β β βββ welcome.template.ts
β β β β βββ password-reset.template.ts
β β β β βββ email-verification.template.ts
β β β βββ types/
β β β βββ email-names.type.ts
β β β βββ mail-config.type.ts
β β β βββ mail-template.type.ts
β β β βββ send-email-options.type.ts
β β βββ telemetry/ # Observability (stub)
β β
β βββ modules/ # ββ BUSINESS MODULES ββ
β β β
β β βββ auth/ # Authentication
β β β βββ auth.module.ts
β β β βββ application/ # Use cases (stub)
β β β βββ domain/ # Domain models (stub)
β β β βββ infrastructure/ # Adapters (stub)
β β β βββ presentation/ # Controllers/DTOs (stub)
β β β βββ guards/
β β β β βββ jwt-auth.guard.ts
β β β βββ strategies/
β β β βββ jwt.strategy.ts
β β β
β β βββ users/ # User management
β β β βββ users.module.ts
β β β βββ application/ # Use cases (stub)
β β β βββ domain/
β β β β βββ index.ts # Barrel export
β β β β βββ entities/
β β β β βββ user.entity.ts
β β β βββ infrastructure/ # Adapters (stub)
β β β βββ presentation/
β β β βββ controller/
β β β βββ users.controller.ts
β β β
β β βββ health/ # Health monitoring
β β βββ health.module.ts
β β βββ presentation/
β β βββ controllers/
β β β βββ health.controller.ts
β β βββ dto/
β β βββ health-check.response.ts
β β
β βββ shared/ # ββ SHARED LAYER ββ
β βββ .gitkeep # Cross-module providers (stub)
β
βββ storage/ # Alternative log storage location
β βββ logs/ # Rotated log files
β
βββ test/ # End-to-end tests
β βββ app.e2e-spec.ts # Health check e2e test
β βββ jest-e2e.json # Jest config for e2e
β
βββ .dockerignore # Docker build exclusions (empty)
βββ .env # Default environment variables
βββ .env.development.local # Development overrides (empty)
βββ .env.local # Local overrides (empty)
βββ .env.production.local # Production overrides (empty)
βββ .env.test.local # Test overrides (empty)
βββ .gitignore # Git exclusion rules
βββ .prettierrc # Prettier formatting config
βββ docker-compose.yml # Docker Compose config (empty)
βββ Dockerfile # Container build definition (empty)
βββ eslint.config.mjs # ESLint flat config
βββ nest-cli.json # NestJS CLI configuration
βββ package.json # Project metadata & dependencies
βββ package-lock.json # Locked dependency tree
βββ README.md # Project README
βββ todo.md # Development roadmap
βββ tsconfig.json # TypeScript compiler config
βββ tsconfig.build.json # TypeScript build config (excludes tests)
The Core Layer is responsible for process-wide, cross-cutting configuration that applies to the entire application regardless of which business module is being accessed.
This is the foundation module that sets up three critical systems:
- Global: Available to all modules without importing.
- Cached: Prevents repeated file reads for the same config key.
- Environment File Loading: Uses
getEnvFilePaths()which loads files in this order:.env.{NODE_ENV}(e.g.,.env.development).env(fallback)
- Later files override earlier ones.
- Enables
@Cron()and@Interval()decorators. - Used for periodic background tasks (health checks, cleanup jobs, etc.).
- Rate Limiter: Protects against brute force and DoS.
- Default TTL:
60,000 ms(1 minute window). - Default Limit:
100requests per window. - Configured via
THROTTLE_TTLandTHROTTLE_LIMITenvironment variables.
| Provider | Scope | Purpose |
|---|---|---|
AllExceptionsFilter |
APP_FILTER |
Catches every unhandled exception |
ThrottlerGuard |
APP_GUARD |
Enforces rate limits on all routes |
Note: A global
AuthGuardis commented out and ready to be activated once auth is fully implemented.
The configureApplication() function applies middleware and pipes globally:
function configureApplication(app: INestApplication): void;What it does:
| Setting | Value | Rationale |
|---|---|---|
enableShutdownHooks() |
β | Graceful shutdown on SIGTERM/SIGINT |
helmet() |
CSP disabled | Security headers without breaking Swagger UI |
cookieParser() |
β | Parse cookies for auth flows |
enableCors() |
credentials: true, origin: true |
Allow credentialed cross-origin requests |
ValidationPipe |
whitelist: true, forbidNonWhitelisted: true, transform: true |
Strip unknown properties, reject them, auto-transform types |
Three pure helper functions used throughout the codebase:
| Function | Signature | Behavior |
|---|---|---|
getEnvFilePaths() |
() => string[] |
Returns [.env.{NODE_ENV}, .env] |
toNumber() |
(value, fallback) => number |
Parses numeric env var, returns fallback if invalid |
toBoolean() |
(value, fallback) => boolean |
Parses "true"/"1"/true, returns fallback otherwise |
Sets up OpenAPI documentation at the /docs endpoint.
Configuration:
- Title: "Noviq-api"
- Description: "Noviq single place to manage your entire life"
- Version: "1.0"
- Auth: Bearer JWT scheme named
access-token - URL:
/docs
Features:
- Custom dark theme (12 CSS rules for full dark-mode Swagger UI)
- Request duration display
- Methods sorted alphabetically (GET, POST, PUT, DELETE...)
- Tags sorted alphabetically
- Persistent authorization (token saved across page reloads)
Conditional Activation: Swagger is enabled when:
ENABLE_SWAGGER=trueis explicitly set, ORNODE_ENVis notproduction(enabled by default in development)
The Common Layer contains reusable artifacts that can be used by any module.
DEFAULT_THROTTLE_LIMIT = 100; // Max requests per window
DEFAULT_THROTTLE_TTL_MS = 60_000; // Window: 1 minuteThe AllExceptionsFilter catches every exception thrown during HTTP request processing.
Error Response Shape:
{
"success": false,
"statusCode": 500,
"timestamp": "2026-07-15T12:00:00.000Z",
"path": "/api/users",
"correlationId": "550e8400-e29b-41d4-a716-446655440000",
"message": "Internal server error",
"stack": "Error: ..." // Only in development
}Key Behaviors:
- Each error gets a unique
correlationId(UUID v4) for tracing. HttpExceptionmessages are extracted, including class-validator array messages.- Unknown exceptions get HTTP 500 with "Internal server error".
- Stack traces appear only in
NODE_ENV=development. - Errors are logged via Winston with correlation ID.
Reusable pagination query parameters:
class PaginationQueryDto {
page: number; // Default: 1, Min: 1
limit: number; // Default: 20, Min: 1, Max: 100
}Uses class-transformer for type coercion (query strings arrive as strings).
| Directory | Intended Purpose |
|---|---|
decorators/ |
Custom decorators (e.g., @CurrentUser(), @Roles()) |
exceptions/ |
Domain-specific exception classes |
guards/ |
Shared authorization guards |
interceptors/ |
Response transformation, logging interceptors |
middleware/ |
Request preprocessing middleware |
pipes/ |
Custom validation/transformation pipes |
types/ |
Shared TypeScript type definitions |
utils/ |
General-purpose utility functions |
The Infrastructure Layer contains technical adapters that abstract away third-party services and frameworks. Each sub-module is self-contained with its own config, constants, types, and services.
Aggregates and re-exports all infrastructure sub-modules:
InfrastructureModule
βββ DatabaseModule (PostgreSQL via TypeORM)
βββ CacheModule (Redis via ioredis) β
@Global()
βββ LoggingModule (Winston via nest-winston)
βββ SecurityJwtModule (JWT via @nestjs/jwt)
βββ SecurityPasswordModule (bcrypt)
βββ QueueModule (BullMQ via @nestjs/bullmq)
βββ StorageModule (AWS S3 via @aws-sdk)
βββ MailModule (Nodemailer + templates)
Uses TypeOrmModule.forRootAsync() with environment-driven config:
| Env Variable | Default | Description |
|---|---|---|
DATABASE_HOST |
(required) | PostgreSQL host |
DATABASE_PORT |
(required) | PostgreSQL port |
DATABASE_USER |
(required) | Database username |
DATABASE_PASSWORD |
(required) | Database password |
DATABASE_NAME |
(required) | Database name |
synchronize: truein development only β auto-creates/alters tables.autoLoadEntities: trueβ entities registered viaTypeOrmModule.forFeature()are automatically picked up.logging: truein development only β SQL queries are logged.maxQueryExecutionTime: 1000β queries taking >1s are logged as warnings.
- Implements
OnModuleInitto log connection status on startup. - Injects
DataSourcefor direct database access (raw queries, transactions).
Marked @Global() β available everywhere without re-importing.
Creates an ioredis client with:
| Feature | Setting |
|---|---|
| Connection | Lazy (lazyConnect: true) |
| Ready Check | Enabled |
| Retries | Max 2 per request |
| Backoff | Math.min(attempt Γ 100, 2000) ms |
| Key Prefix | noviq: (default) |
Implements OnModuleInit and OnModuleDestroy for lifecycle management.
Available Operations:
| Method | Signature | Description |
|---|---|---|
get<T>() |
(key) => T | null |
JSON-parsed value or null |
set<T>() |
(key, value, ttlMs?) => void |
JSON-stringified with PX expiry |
del() |
(key) => void |
Delete single key |
delMany() |
(keys) => number |
Delete multiple keys, returns count |
remember<T>() |
(key, ttlMs, loader) => T |
Cache-aside pattern (get β miss β fetch β set) |
invalidatePattern() |
(pattern) => number |
SCAN + delete matching keys |
increment() |
(key, ttlMs?) => number |
Atomic increment, optional PEXPIRE |
Error Handling: All methods catch Redis errors and log warnings β they never throw, ensuring the app remains operational if Redis is down.
Centralizes cache key naming conventions:
| Method | Key Pattern |
|---|---|
userProfile(userId) |
users:profile:{userId} |
userPermissions(userId) |
users:permissions:{userId} |
courseDetails(courseId) |
courses:details:{courseId} |
courseList(filtersHash) |
courses:list:{filtersHash} |
authSession(sessionId) |
auth:sessions:{sessionId} |
| Constant | Value | Description |
|---|---|---|
REDIS_CLIENT |
Symbol('REDIS_CLIENT') |
DI injection token |
CACHE_DEFAULT_TTL_MS |
60_000 |
1 minute default TTL |
CACHE_SCAN_COUNT |
100 |
Keys per SCAN iteration |
CACHE_KEY_PREFIX |
'noviq:' |
Global key namespace |
Uses nest-winston (WinstonModule) for structured logging.
Creates three Winston transports:
| Transport | Destination | Format | Level |
|---|---|---|---|
| Console | stdout | Nest-like (dev) / JSON (prod) | Configurable |
| DailyRotateFile | {logDir}/application-%DATE%.log |
JSON | Configurable |
| DailyRotateFile | {logDir}/error-%DATE%.log |
JSON | error only |
Development Console Format: Colorized, pretty-printed with timestamps and module names.
Production Console Format: JSON (structured, machine-readable).
| Constant | Default | Description |
|---|---|---|
DEFAULT_LOG_LEVEL |
info |
Default logging level |
DEFAULT_LOG_DIR |
logs |
Log output directory |
DEFAULT_LOG_MAX_SIZE |
20m |
Max size per file before rotation |
DEFAULT_LOG_MAX_FILES |
14d |
Retention period |
APPLICATION_LOG_FILENAME |
application-%DATE%.log |
App-level log pattern |
ERROR_LOG_FILENAME |
error-%DATE%.log |
Error log pattern |
Test Coverage: The logging config has a unit test verifying all three transports are created with correct paths.
The most sophisticated infrastructure module β provides dual-token authentication.
| Token | Default TTL | Secret (Env) | Purpose |
|---|---|---|---|
| Access | 15m |
JWT_ACCESS_SECRET |
Short-lived, used for API authorization |
| Refresh | 7d |
JWT_REFRESH_SECRET |
Long-lived, used to obtain new access tokens |
Two factory functions:
createJwtInfrastructureConfig()β buildsJwtInfrastructureConfigfrom env, validates production requires real secrets (refuses to start with dev defaults).createJwtModuleOptions()β wraps config for@nestjs/jwtmodule registration.
TTL Validation: Accepts values like 15m, 7d, 1h, 30s, 500ms β rejects invalid formats.
Core Operations:
| Method | Input | Output | Notes |
|---|---|---|---|
issueAccessToken() |
JwtPayload |
string |
Signs with access secret, adds type: "access" |
issueRefreshToken() |
JwtPayload |
string |
Signs with refresh secret, adds jti + type: "refresh" |
issueTokenPair() |
JwtPayload |
TokenPair |
Returns both tokens + expiresIn in seconds |
verifyAccessToken() |
string |
VerifiedJwtPayload |
Validates audience, issuer, secret, and token type |
verifyRefreshToken() |
string |
VerifiedJwtPayload |
Same as above but with refresh secret |
decode() |
string |
object | string | null |
Decode without verification |
getAccessExpiresInSeconds() |
β | number |
Converts TTL string to seconds |
Token Type Protection: The verifier checks payload.type β a refresh token (type: "refresh") will be rejected by verifyAccessToken() with "Invalid access token type".
type JwtPayload = {
sub: string; // Subject (user ID)
type?: JwtTokenType; // "access" | "refresh"
jti?: string; // JWT ID (unique per refresh token)
roles?: string[]; // User roles for authorization
};
type JwtTokenType = 'access' | 'refresh';type TokenPair = {
accessToken: string;
refreshToken: string;
expiresIn: number; // Access token TTL in seconds
};| Constant | Value | Description |
|---|---|---|
JWT_CONFIG |
Symbol.for('JWT_CONFIG') |
DI injection token |
JWT_ACCESS_TOKEN_TYPE |
'access' |
Access token type identifier |
JWT_REFRESH_TOKEN_TYPE |
'refresh' |
Refresh token type identifier |
JWT_DEFAULT_ACCESS_TTL |
'15m' |
15 minutes |
JWT_DEFAULT_REFRESH_TTL |
'7d' |
7 days |
JWT_DEFAULT_ISSUER |
'noviq-api' |
Issuer claim |
JWT_DEFAULT_AUDIENCE |
'noviq-client' |
Audience claim |
JWT_DEV_ACCESS_SECRET |
'noviq-dev-access-token-secret-change-me' |
Dev-only fallback |
JWT_DEV_REFRESH_SECRET |
'noviq-dev-refresh-token-secret-change-me' |
Dev-only fallback |
BullMQ-based job queue system backed by Redis (separate DB from cache).
Three named queues registered at startup:
| Queue Name | Purpose |
|---|---|
default |
General-purpose background jobs |
email |
Email sending jobs |
notification |
Push/in-app notification jobs |
Uses a separate Redis DB (DB 1 by default) from the cache (DB 0):
| Env Variable | Default | Description |
|---|---|---|
QUEUE_REDIS_HOST |
localhost |
Redis host |
QUEUE_REDIS_PORT |
6379 |
Redis port |
QUEUE_REDIS_PASSWORD |
(none) | Redis password |
QUEUE_REDIS_DB |
1 |
Redis database number |
QUEUE_PREFIX |
noviq |
BullMQ key prefix |
QUEUE_DEFAULT_ATTEMPTS |
3 |
Job retry attempts |
QUEUE_BACKOFF_DELAY_MS |
5000 |
Backoff delay (exponential) |
QUEUE_REMOVE_ON_COMPLETE |
1000 |
Keep last N completed jobs |
QUEUE_REMOVE_ON_FAIL |
5000 |
Keep last N failed jobs |
Key: maxRetriesPerRequest: null β required for BullMQ workers.
| Method | Description |
|---|---|
add(queueName, jobName, data, options?) |
Enqueue a single job |
addDelayed(queueName, jobName, data, delayMs, options?) |
Schedule a job for future execution |
addBulk(queueName, jobs[]) |
Enqueue multiple jobs atomically |
getQueue(queueName) |
Get a Queue instance (throws for unknown names) |
type QueueJob<Data> = {
name: string;
data: Data;
opts?: JobsOptions;
};AWS S3-compatible file storage with support for alternative endpoints (MinIO, LocalStack).
Required environment variables (refuses to start if missing):
| Variable | Description |
|---|---|
AWS_REGION |
AWS region (e.g., us-east-1) |
AWS_ACCESS_KEY_ID |
AWS access key |
AWS_SECRET_ACCESS_KEY |
AWS secret key |
S3_BUCKET_NAME |
Target bucket name |
Optional variables:
| Variable | Default | Description |
|---|---|---|
S3_ENDPOINT |
(none) | Custom endpoint for MinIO/LocalStack |
S3_FORCE_PATH_STYLE |
false |
Path-style addressing |
S3_PUBLIC_BASE_URL |
(none) | CDN / public URL base |
S3_SIGNED_URL_EXPIRES_SECONDS |
900 |
Signed URL lifetime (15 min) |
S3_KEY_PREFIX |
uploads |
Object key prefix |
Value Normalization: Trailing slashes stripped from endpoints and base URLs. Key prefix normalized (no leading/trailing slashes).
Two DI providers:
s3StorageConfigProviderβ createsS3StorageConfigfrom environment.s3ClientProviderβ createsS3Clientinstance using the config.
Comprehensive S3 operation service:
| Operation | Method | Description |
|---|---|---|
| Upload (multipart) | uploadBuffer() |
Multipart upload via @aws-sdk/lib-storage |
| Upload (stream) | uploadStream() |
Same as above (delegates to uploadManaged) |
| Upload (direct) | putObject() |
Single PutObjectCommand for small objects |
| Retrieve | getObject() |
Fetch object from S3 |
| Metadata | headObject() |
Get object metadata without body |
| Exists | objectExists() |
Check if object exists (returns boolean) |
| Delete one | deleteObject() |
Single object deletion |
| Delete many | deleteObjects() |
Batch deletion (accepts string or string[]) |
| Copy | copyObject() |
Server-side copy between keys |
| Signed upload URL | getSignedUploadUrl() |
Pre-signed URL for client-side upload |
| Signed download URL | getSignedDownloadUrl() |
Pre-signed URL for temporary access |
| Public URL | getPublicUrl() |
Construct public CDN URL |
| Key building | buildKey() |
Prefix-aware key construction |
Key Prefix Logic: All keys are automatically prefixed with S3_KEY_PREFIX (default: uploads/). Passing "avatar.png" results in key "uploads/avatar.png". Already-prefixed keys are not double-prefixed.
Storage Object Shape:
type StorageObject = {
bucket: string;
key: string;
url?: string; // Only if publicBaseUrl is configured
};Upload Options:
type UploadOptions = {
key: string;
body: Buffer | Uint8Array | string | Readable;
acl?: ObjectCannedACL;
contentType?: string;
contentLength?: number;
contentDisposition?: string;
contentEncoding?: string;
cacheControl?: string;
metadata?: Record<string, string>;
};Nodemailer-based email service with BullMQ-backed reliable delivery and an HTML template engine.
Caller (e.g., AuthService)
β
β queueService.add('email', EmailName.WELCOME, { to, subject, emailName, templateData })
β
βΌ
BullMQ 'email' queue ββββ retries (3 attempts, exponential backoff)
β
βΌ
EmailProcessor.process(job)
β
βββ html set? β use as-is
β else β MailTemplateService.render(emailName, templateData)
β
βΌ
EmailSenderService.send({ ...job.data, html })
β
βΌ
Nodemailer β SMTP
Builds a nodemailer Transporter from environment variables:
| Env Variable | Default | Description |
|---|---|---|
EMAIL_HOST |
localhost |
SMTP server host |
EMAIL_PORT |
587 |
SMTP server port |
EMAIL_USER |
β | SMTP auth username |
EMAIL_PASSWORD |
β | SMTP auth password |
EMAIL_FROM |
noreply@noviq.com |
Default sender address |
EMAIL_SECURE |
false |
Use TLS (set true for 465) |
Supports both authenticated and unauthenticated SMTP connections.
| Method | Description |
|---|---|
send(options) |
Send a single email via nodemailer |
sendMany(emails) |
Send multiple emails in sequence |
SendEmailOptions:
| Field | Type | Description |
|---|---|---|
to |
string | string[] |
Recipient(s) |
subject |
string |
Email subject line |
html |
string (optional) |
Pre-rendered HTML body |
text |
string (optional) |
Plain-text fallback |
from |
string (optional) |
Override default sender |
cc |
string | string[] (opt) |
Carbon copy |
bcc |
string | string[] (opt) |
Blind carbon copy |
attachments |
array (optional) |
File attachments |
emailName |
EmailName |
Which email template to render |
templateData |
TemplateDataMap[EmailName] |
Data injected into the template |
BullMQ worker consumer bound to the email queue via @Processor(EMAIL_QUEUE_NAME). On each job:
- Checks if
htmlis already set β uses it directly - Otherwise calls
MailTemplateService.render()withemailName+templateData - Passes the resolved HTML to
EmailSenderService.send() - If sending fails, BullMQ retries automatically (3 attempts, exponential backoff)
Renders email templates by name. Template names are typed via the EmailName enum:
EmailName |
Template file | Data interface |
|---|---|---|
WELCOME |
welcome.template.ts |
WelcomeTemplateData |
PASSWORD_RESET |
password-reset.template.ts |
PasswordResetTemplateData |
EMAIL_VERIFICATION |
email-verification.template.ts |
EmailVerificationTemplateData |
To add a new template: add to EmailName enum β create *.template.ts file β register in templates/index.ts.
Each template is a pure function that takes typed data and returns an HTML string. All templates share a common baseTemplate() layout with:
- Gradient brand accent bar (
#6366f1β#a855f7) - Responsive card layout (560px max-width)
- Inter font family with system fallbacks
- Mobile breakpoint at 480px
- App name and copyright year derived automatically from constants
| Constant | Value | Description |
|---|---|---|
MAIL_APP_NAME |
'Noviq' |
Brand name in emails |
MAIL_DEFAULT_HOST |
'localhost' |
Default SMTP host |
MAIL_DEFAULT_PORT |
587 |
Default SMTP port |
MAIL_DEFAULT_FROM |
'noreply@noviq.com' |
Default sender address |
MAIL_TRANSPORTER |
'MAIL_TRANSPORTER' |
DI injection token |
| Directory | Intended Purpose |
|---|---|
telemetry/ |
OpenTelemetry / metrics / tracing |
Purpose: Handle user authentication via JWT and OAuth2 providers.
| Component | File | Status |
|---|---|---|
| Module | auth.module.ts |
Imports PassportModule, provides JwtStrategy |
| Guard | guards/jwt-auth.guard.ts |
AuthGuard('jwt') wrapper |
| Strategy | strategies/jwt.strategy.ts |
Passport JWT strategy |
- Extends
PassportStrategy(Strategy)(passport-jwt). - Extracts JWT from
Authorization: Bearer <token>header. - Uses the same
JwtInfrastructureConfigas the infrastructure layer. validate()extractsuserId(fromsub) androlesfrom the decoded JWT payload.- The returned object is attached to
request.user.
All four layers (application/, domain/, infrastructure/, presentation/) are scaffolded with .gitkeep files, ready for implementation:
- Presentation: Auth controller (login, register, refresh, logout endpoints).
- Application: Use cases (register user, authenticate, refresh session).
- Domain: Auth-related entities (session, credentials).
- Infrastructure: OAuth adapters (Google, Microsoft, Facebook).
Purpose: User profile management and CRUD operations.
The core user model mapped to the users table:
| Column | Type | Constraints | Notes |
|---|---|---|---|
id |
uuid |
PRIMARY KEY |
Auto-generated via gen_random_uuid() |
name |
varchar(100) |
NOT NULL |
Display name |
email |
varchar(255) |
UNIQUE, NOT NULL |
Login identifier |
passwordHash |
varchar(255) |
NOT NULL, SELECT: false |
Hidden from queries by default |
isActive |
boolean |
DEFAULT true, INDEXED |
Soft disable |
createdAt |
timestamp |
NOT NULL |
Auto-set on insert |
updatedAt |
timestamp |
NOT NULL |
Auto-set on insert (needs update trigger) |
termsAcceptedAt |
timestamp |
NULLABLE |
Tracks TOS acceptance timestamp |
termsVersion |
varchar(20) |
NULLABLE |
Tracks accepted TOS version |
Key Design Decisions:
passwordHashusesselect: falseβ never returned in queries unless explicitly selected.- UUID primary key with database-level generation (no application-level UUID dependency).
isActiveis indexed for fast filtering on active users.
Currently stub endpoints returning placeholder strings:
| Method | Route | Status |
|---|---|---|
GET |
/users |
Returns "List of users" |
GET |
/users/:id |
Returns "User details" |
POST |
/users |
Returns "User created successfully" |
- Uses
TypeOrmModule.forFeature([User])to register the entity. - Exports
TypeOrmModuleso other modules can use the User repository.
Purpose: Kubernetes/load-balancer compatible health check endpoint.
GET /health
Response (200):
{
"status": "ok",
"info": {
"memory_heap": { "status": "up" }
},
"error": {},
"details": {
"memory_heap": { "status": "up" }
}
}| Check | Indicator | Threshold |
|---|---|---|
| Memory Heap | MemoryHealthIndicator |
300 MB (314,572,800 bytes) |
Fully documented with @ApiProperty decorators for the OpenAPI schema.
The Shared Layer is a stub directory intended for cross-module providers that are stable and intentionally shared (e.g., a DateTimeService, IdGenerator, or EventBus).
Currently contains only a .gitkeep file β reserved for future use.
1. NestFactory.create(AppModule, { bufferLogs: true })
β
2. Replace logger with Winston (app.useLogger)
β
3. configureApplication(app)
βββ Helmet (security headers)
βββ Cookie Parser
βββ CORS (credentialed, all origins)
βββ Global ValidationPipe (whitelist + transform)
βββ Shutdown Hooks
β
4. Conditional Swagger Setup
βββ Check ENABLE_SWAGGER or NODE_ENV !== 'production'
βββ setupSwagger(app) β /docs
β
5. app.listen(PORT || 3000)
@Module({
imports: [
RouterModule.register([
{ path: 'users', module: UsersModule },
{ path: 'auth', module: AuthModule },
]),
CoreModule, // Config, rate limiting, scheduling
InfrastructureModule, // DB, cache, JWT, queue, storage, logging
HealthModule, // /health endpoint
AuthModule, // Authentication
UsersModule, // User management
],
})
export class AppModule {}Routing: RouterModule.register() sets up path prefixes:
- Requests to
/users/*βUsersModule - Requests to
/auth/*βAuthModule - Requests to
/healthβ directly handled byHealthModule(no prefix)
| Method | Path | Module | Controller | Status |
|---|---|---|---|---|
GET |
/health |
Health | HealthController |
β Implemented |
GET |
/users |
Users | UsersController |
π§ Stub |
GET |
/users/:id |
Users | UsersController |
π§ Stub |
POST |
/users |
Users | UsersController |
π§ Stub |
GET |
/docs |
(Swagger) | β | β Implemented |
| Method | Path | Purpose |
|---|---|---|
POST |
/auth/register |
User registration |
POST |
/auth/login |
Email/password login |
POST |
/auth/refresh |
Refresh access token |
POST |
/auth/logout |
Invalidate session |
POST |
/auth/google |
Google OAuth login |
POST |
/auth/microsoft |
Microsoft OAuth login |
POST |
/auth/facebook |
Facebook OAuth login |
1. .env.{NODE_ENV} (e.g., .env.development)
2. .env (fallback)
Later files override earlier ones. For production (NODE_ENV=production), the app loads:
1. .env.production
2. .env
See Section 25 for the complete reference table.
The .env file provides sensible defaults for local development:
- Database:
localhost:5432, databasenoviq_dev, usermyuser - Redis:
localhost:6379, DB 0 - Queue Redis:
localhost:6379, DB 1 - JWT: Dev-only secrets with 15m access / 7d refresh TTLs
- Logging:
debuglevel,./logsdirectory - Swagger: Enabled by default
- S3: Points to
fanousebucketinus-east-1
1. User logs in β receives Access Token (15m) + Refresh Token (7d)
β
2. Client stores tokens (httpOnly cookie recommended)
β
3. Each API request β Authorization: Bearer <access_token>
β
4. JwtStrategy validates β request.user = { userId, roles }
β
5. (Future) RolesGuard checks @Roles() decorator
β
6. When access token expires β POST /auth/refresh with refresh token
β
7. New access token issued (refresh token rotation supported via jti)
Access Token Payload:
{
"sub": "user-uuid",
"roles": ["student"],
"type": "access",
"iat": 1740000000,
"exp": 1740000900,
"iss": "noviq-api",
"aud": "noviq-client"
}Refresh Token Payload:
{
"sub": "user-uuid",
"roles": ["student"],
"type": "refresh",
"jti": "unique-token-id",
"iat": 1740000000,
"exp": 1740604800,
"iss": "noviq-api",
"aud": "noviq-client"
}| Feature | Implementation |
|---|---|
| Token type validation | Verifier checks type claim β refresh tokens can't be used as access tokens |
| Separate secrets | Access and refresh tokens use independent signing keys |
| Token ID (jti) | Each refresh token has a unique ID for revocation tracking |
| Short-lived access | 15-minute access tokens limit exposure window |
| Helmet | Security headers (CSP disabled for Swagger compatibility) |
| CORS | Credentialed requests from any origin |
| Rate limiting | 100 req/min globally |
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
"passwordHash" VARCHAR(255) NOT NULL,
"isActive" BOOLEAN DEFAULT true,
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
"termsAcceptedAt" TIMESTAMP,
"termsVersion" VARCHAR(20)
);
CREATE INDEX IDX_users_isActive ON users ("isActive");- Type: PostgreSQL
- Synchronize: Auto in development (not safe for production).
- Auto-load entities: Yes β entities from
TypeOrmModule.forFeature()are discovered. - Logging: SQL queries logged in development.
Application Code
β
CacheService (cache-aside pattern)
β
Redis (ioredis client)
β
Key prefix: "noviq:" (namespace isolation)
// Instead of:
let data = await cache.get(key);
if (!data) {
data = await fetchFromDB();
await cache.set(key, data, ttl);
}
// Use:
const data = await cache.remember(key, ttl, () => fetchFromDB());| Pattern | Method | Use Case |
|---|---|---|
| Single key | del(key) |
Update a specific entity |
| Multiple keys | delMany(keys) |
Bulk operations |
| Pattern-based | invalidatePattern("users:*") |
Invalidate all user caches |
All keys follow the pattern: {domain}:{resource}:{identifier}
noviq:users:profile:<userId>
noviq:users:permissions:<userId>
noviq:courses:details:<courseId>
noviq:courses:list:<filtersHash>
noviq:auth:sessions:<sessionId>
- All cache operations catch errors β Redis being down never crashes the app.
get()returnsnullon failure (triggers DB fetch in cache-aside pattern).set()anddel()log warnings and continue silently.
Application
β
QueueService.add(queueName, jobName, data)
β
BullMQ (Redis DB 1)
β
Worker processes
βββ EmailProcessor (MailModule) β consumes 'email' queue
βββ Notification (future)
β
Job handlers (email, notifications, etc.)
| Queue | BullMQ Name | Env Reference |
|---|---|---|
| Default | default |
General background work |
email |
Outbound email delivery | |
| Notification | notification |
Push & in-app notifications |
Enqueue β Wait β Active β Completed
β
Failed β Retry (exponential backoff) β Active
β (after N attempts)
Failed β Dead Letter
| Option | Value | Description |
|---|---|---|
attempts |
3 |
Max retry attempts |
backoff.type |
exponential |
Exponential backoff strategy |
backoff.delay |
5000 ms |
Initial backoff delay |
removeOnComplete |
1000 |
Retain last 1000 completed jobs |
removeOnFail |
5000 |
Retain last 5000 failed jobs |
Client App S3StorageService
β β
ββ GET /files/:key ββββββββββββββββββββ getSignedDownloadUrl() β S3
β β
ββ POST /upload βββ presigned URL ββββ getSignedUploadUrl() β S3
β β
ββ (server upload) ββββββββββββββββββββ uploadBuffer()/putObject() β S3
β β
ββ DELETE /files/:key βββββββββββββββββ deleteObject() β S3
β β
ββ GET /files/:key/public βββββββββββββ getPublicUrl() β CDN
| Category | Operation | Method |
|---|---|---|
| Upload | Small object | putObject() |
| Upload | Large/multipart | uploadBuffer() / uploadStream() |
| Download | Via pre-signed URL | getSignedDownloadUrl() |
| Upload | Pre-signed URL for client | getSignedUploadUrl() |
| Retrieve | Get object | getObject() |
| Metadata | Head object | headObject() |
| Check | Object exists | objectExists() |
| Delete | Single object | deleteObject() |
| Delete | Multiple objects | deleteObjects(keys) |
| Copy | Server-side | copyObject(source, target) |
| URL | Public CDN URL | getPublicUrl() |
All keys are automatically prefixed:
Input key: "avatar.png"
Built key: "uploads/avatar.png" (with keyPrefix = "uploads")
Input key: "uploads/avatar.png" (already prefixed)
Built key: "uploads/avatar.png" (no double-prefix)
Input key: " reports /2024 /summary .pdf"
Built key: "uploads/reports/2024/summary.pdf" (normalized)
ββββββββββββββββ
β Application β
ββββββββ¬ββββββββ
β
ββββββββββββββΌβββββββββββββ
β β β
ββββββΌβββββ ββββββΌβββββ βββββββΌβββββββ
β Console β β App β β Error β
β stdout β β File β β File β
βββββββββββ βββββββββββ ββββββββββββββ
Nest-like JSON JSON
(dev) Daily Daily
JSON Rotate Rotate
(prod)
logs/
βββ application-2026-07-15.log β All log levels
βββ application-2026-07-14.log
βββ error-2026-07-15.log β Error level only
βββ error-2026-07-14.log
βββ .*-audit.json β Security audit trail
error: 0 β System errors, exceptions
warn: 1 β Degraded operation (Redis down, cache miss)
info: 2 β Normal operations (server start, DB connected)
debug: 3 β Detailed diagnostic info
// Inject Winston logger
constructor(@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService) {}
// Use it
this.logger.log('User registered', { userId: '...' });
this.logger.error('Database connection failed', error);Exception thrown
β
βΌ
AllExceptionsFilter.catch()
β
βββ Extract HTTP status (or 500 for unknown)
βββ Generate correlationId (UUID)
βββ Extract message (from HttpException, Error, or fallback)
βββ Log via Winston with correlationId + stack trace
β
βΌ
HTTP Response
β
βββ JSON body with:
βββ success: false
βββ statusCode
βββ timestamp (ISO 8601)
βββ path (request URL)
βββ correlationId (for log tracing)
βββ message (user-facing)
βββ stack (development only)
Development:
{
"success": false,
"statusCode": 400,
"timestamp": "2026-07-15T14:30:00.000Z",
"path": "/users/123",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": ["email must be a valid email", "name must be a string"],
"stack": "ValidationError: ...\n at ..."
}Production:
{
"success": false,
"statusCode": 500,
"timestamp": "2026-07-15T14:30:00.000Z",
"path": "/users",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "Internal server error"
}The ValidationPipe automatically returns structured validation errors through the exception filter. The message field will contain an array of validation failure strings from class-validator.
| Parameter | Default | Env Variable |
|---|---|---|
| Time window | 60,000 ms (1 minute) | THROTTLE_TTL |
| Max requests | 100 | THROTTLE_LIMIT |
| Guard scope | Global (all routes) | APP_GUARD |
All standard security headers are applied. Content Security Policy is disabled to allow Swagger UI to function correctly.
{
credentials: true, // Allow cookies/auth headers
origin: true // Reflect request origin
}Note:
origin: truereflects all origins. In production, this should be restricted to specific domains.
| Setting | Effect |
|---|---|
whitelist: true |
Strips properties not defined in DTO |
forbidNonWhitelisted: true |
Returns 400 if unknown properties are sent |
transform: true |
Auto-converts types (string β number, etc.) |
- URL:
http://localhost:3000/docs - Title: "Noviq API Docs"
- Authentication: Bearer JWT (enter token via "Authorize" button)
A custom dark theme is applied via customCss:
- Dark background (
#0b1120/#111827/#030712) - Light text (
#e5e7eb/#f9fafb) - Dark form inputs
- Styled buttons and code blocks
- Try it out: Execute API calls directly from the browser.
- Request duration: Shows how long each request took.
- Persistent auth: Token saved in browser β no need to re-enter after page reload.
- Sorted: Tags alphabetically, operations by HTTP method.
| Aspect | Unit Tests | E2E Tests |
|---|---|---|
| Config file | jest (in package.json) |
test/jest-e2e.json |
| Root directory | src/ |
. |
| File pattern | *.spec.ts |
*.e2e-spec.ts |
| Transform | ts-jest |
ts-jest |
| Environment | node |
node |
Coverage collected from all .ts and .js files in src/, output to coverage/ directory.
| Tool | Purpose |
|---|---|
jest-mock-extended |
Type-safe mocks for DI |
nock |
HTTP request interception |
@testcontainers/postgresql |
Real PostgreSQL for integration tests |
@testcontainers/redis |
Real Redis for integration tests |
supertest |
HTTP assertion library for e2e |
@faker-js/faker |
Realistic test data generation |
| Module | Test File | Coverage |
|---|---|---|
| Logging Config | logging.config.spec.ts |
Transport creation, directory/filename |
| Queue Config | queue.config.spec.ts |
Redis connection, job options |
| Queue Service | queue.service.spec.ts |
Add, delay, bulk, unknown queue |
| S3 Config | s3.config.spec.ts |
Config creation, missing values |
| S3 Storage Service | s3-storage.service.spec.ts |
Upload, put, signed URLs, commands |
| Token Service | token.service.spec.ts |
Issue, verify, type protection, jti |
| Health (E2E) | app.e2e-spec.ts |
GET /health returns 200 with status |
npm test # Unit tests
npm run test:watch # Watch mode
npm run test:cov # With coverage
npm run test:e2e # End-to-end testsnpm run build # nest build (with SWC)Compiled output goes to dist/. TypeScript is compiled with:
- CommonJS modules (Node.js compatible)
- ES2023 target
- Decorator metadata enabled
- Source maps generated
- Declarations generated
Dockerfile and docker-compose.yml exist but are currently empty β containerization is planned but not yet implemented.
| Script | Command | Description |
|---|---|---|
start |
nest start |
Start the app (dev) |
start:dev |
nest start --watch |
Start with hot reload |
start:debug |
nest start --debug --watch |
Start with debugger |
start:prod |
node dist/main |
Start compiled production build |
build |
nest build |
Compile TypeScript |
lint |
eslint ... --fix |
Lint and auto-fix |
format |
prettier --write ... |
Format code |
From todo.md:
| Priority | Feature | Status |
|---|---|---|
| π΄ High | Authentication system (register, login, refresh, logout) | π§ In Progress |
| π΄ High | User CRUD operations | π§ Stub only |
| π‘ Medium | Email & SMS via Twilio | β¬ Not started |
| π‘ Medium | Stripe payment integration | β¬ Not started |
| π‘ Medium | OAuth: Google, Microsoft, Facebook | β¬ Not started |
| π’ Low | Two-factor authentication | β¬ Not started |
| Variable | Default | Description |
|---|---|---|
NODE_ENV |
development |
Environment (development, production, test) |
PORT |
3000 |
HTTP server port |
ENABLE_SWAGGER |
true (non-prod) |
Enable Swagger at /docs |
| Variable | Default | Description |
|---|---|---|
DATABASE_HOST |
β | PostgreSQL host |
DATABASE_PORT |
β | PostgreSQL port |
DATABASE_NAME |
β | Database name |
DATABASE_USER |
β | Database username |
DATABASE_PASSWORD |
β | Database password |
| Variable | Default | Description |
|---|---|---|
REDIS_HOST |
localhost |
Redis host |
REDIS_PORT |
6379 |
Redis port |
REDIS_PASSWORD |
β | Redis password |
REDIS_DB |
0 |
Redis database number |
REDIS_KEY_PREFIX |
noviq: |
Key prefix for namespacing |
| Variable | Default | Description |
|---|---|---|
QUEUE_REDIS_HOST |
localhost |
Queue Redis host |
QUEUE_REDIS_PORT |
6379 |
Queue Redis port |
QUEUE_REDIS_PASSWORD |
β | Queue Redis password |
QUEUE_REDIS_DB |
1 |
Queue Redis database |
QUEUE_PREFIX |
noviq |
BullMQ key prefix |
QUEUE_DEFAULT_ATTEMPTS |
3 |
Job retry attempts |
QUEUE_BACKOFF_DELAY_MS |
5000 |
Backoff delay (ms) |
QUEUE_REMOVE_ON_COMPLETE |
1000 |
Retain completed jobs |
QUEUE_REMOVE_ON_FAIL |
5000 |
Retain failed jobs |
| Variable | Default | Description |
|---|---|---|
JWT_ACCESS_SECRET |
Dev secret | Access token signing key |
JWT_REFRESH_SECRET |
Dev secret | Refresh token signing key |
JWT_ACCESS_TTL |
15m |
Access token lifetime |
JWT_REFRESH_TTL |
7d |
Refresh token lifetime |
JWT_ISSUER |
noviq-api |
JWT issuer claim |
JWT_AUDIENCE |
noviq-client |
JWT audience claim |
| Variable | Default | Description |
|---|---|---|
EMAIL_HOST |
localhost |
SMTP server host |
EMAIL_PORT |
587 |
SMTP server port |
EMAIL_USER |
β | SMTP auth username |
EMAIL_PASSWORD |
β | SMTP auth password |
EMAIL_FROM |
noreply@noviq.com |
Default sender address |
EMAIL_SECURE |
false |
Use TLS (true for 465) |
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
info |
Winston log level |
LOG_DIR |
logs |
Log output directory |
LOG_MAX_SIZE |
20m |
Max log file size |
LOG_MAX_FILES |
14d |
Log retention period |
| Variable | Default | Description |
|---|---|---|
THROTTLE_TTL |
60000 |
Rate limit window (ms) |
THROTTLE_LIMIT |
100 |
Max requests per window |
| Variable | Default | Description |
|---|---|---|
AWS_REGION |
β | AWS region |
AWS_ACCESS_KEY_ID |
β | AWS access key |
AWS_SECRET_ACCESS_KEY |
β | AWS secret key |
S3_BUCKET_NAME |
β | S3 bucket name |
S3_ENDPOINT |
β | Custom S3 endpoint |
S3_FORCE_PATH_STYLE |
false |
Use path-style URLs |
S3_PUBLIC_BASE_URL |
β | CDN base URL |
S3_SIGNED_URL_EXPIRES_SECONDS |
900 |
Signed URL lifetime |
S3_KEY_PREFIX |
uploads |
Object key prefix |
| Token | Value | Module |
|---|---|---|
REDIS_CLIENT |
Symbol('REDIS_CLIENT') |
Cache |
JWT_CONFIG |
Symbol.for('JWT_CONFIG') |
Security/JWT |
S3_CLIENT |
Symbol.for('S3_CLIENT') |
Storage |
S3_STORAGE_CONFIG |
Symbol.for('S3_STORAGE_CONFIG') |
Storage |
MAIL_TRANSPORTER |
'MAIL_TRANSPORTER' |
- Main Branch:
master - Current Branch:
auth(authentication feature development) - Commit Convention: Informal (short messages like "updated.", "Update.")
| Tool | Config | Description |
|---|---|---|
| ESLint | eslint.config.mjs (flat config) |
TypeScript-aware linting |
| Prettier | .prettierrc |
Single quotes, trailing commas |
ESLint Rules:
@typescript-eslint/no-explicit-any: off (allowed)@typescript-eslint/no-floating-promises: warn@typescript-eslint/no-unsafe-argument: warn
Document Version: 1.0
Generated: 2026-07-15
Repository:noviq-apiβ The backend powering the Noviq learning platform.