Skip to content

Backend Architecture — Express.js TypeScript API

Part: saas-boilerplate/ | Type: Backend API Generated: 2026-02-12 | Last Modified: 2026-03-26 | Scan Level: Exhaustive | Workflow: document-project v1.2.0

Executive Summary

Production-ready Express.js 4.18 REST API built with TypeScript 5.4 and PostgreSQL via TypeORM 0.3. Implements multi-tenant SaaS patterns including RBAC, subscription management with Stripe/LemonSqueezy, CMS, and admin operations. Designed for serverless deployment (Vercel) with traditional server fallback.

Codebase at a glance: 289 TypeScript files, 30 modules, 29 entities, 212 API endpoints, 14 middleware files, 10 auth policies, 4 auth handlers, 7 event handlers, 2 cron jobs, 4 service factories.

Technology Stack

Category Technology Version Purpose
Runtime Node.js 18.17+ Server runtime (not enforced via engines in package.json)
Language TypeScript 5.4 Type safety
Framework Express.js 4.18 HTTP server
Database PostgreSQL 14+ Primary datastore
ORM TypeORM 0.3.25 Database abstraction
Validation Zod 3.22 Request validation
Auth JWT (jsonwebtoken) 9.0 Token-based auth
Auth Passport + passport-google-oauth20 0.7/2.0 OAuth strategies
Passwords bcrypt 5.1 Password hashing (async, non-blocking)
Payments Stripe 18.3 Subscription billing
Payments LemonSqueezy 4.0 Alternative billing (webhooks only — payment factory not implemented)
File Storage Cloudinary 2.7 Image uploads
File Storage AWS S3 3.x Alternative storage
File Upload multer 2.0 Multipart form handling
Email Nodemailer 6.9 SMTP email
Email Resend 6.1 Transactional email
Email Brevo (GetBrevo) 3.0 Marketing email
Logging Winston + express-winston 3.17/4.2 Structured logging with daily rotate
Security Helmet 8.1 HTTP headers
Sanitization DOMPurify (isomorphic-dompurify) Backend HTML sanitization (via sanitizer factory)
Sanitization sanitize-html 2.17 XSS prevention (legacy)
Compression compression 1.8 Response gzip
Rate Limiting express-rate-limit 8.0 Request throttling
Scheduling node-cron 4.2 Cron jobs
2FA speakeasy + qrcode 2.0/1.5 TOTP authentication
HTTP Client axios 1.11 External API calls
Dates date-fns + luxon 3.6/3.4 Date manipulation (date-fns minimally used in coupon service; luxon installed but unused — consider removing both or consolidating)
Slugs slugify 1.6 URL slug generation
Caching node-cache 5.1 In-memory cache
Docs swagger-jsdoc + swagger-ui-express 6.2/4.6 API documentation
IDs uuid 9.0 UUID generation
DB Driver pg 8.16 PostgreSQL driver
Env dotenv 16.4 Environment variable loading
HTTP Status http-status 1.7 Status code constants

Architecture Pattern

Modular MVC with Service Layer — Each business domain is isolated into a self-contained module under src/app/modules/v1/:

src/app/modules/v1/{Domain}/
├── {domain}.controller.ts     # HTTP request/response handling
├── {domain}.service.ts        # Business logic
├── {domain}.routes.ts         # Express route definitions (some modules use .route.ts — no consistent convention)
├── {domain}.validation.ts     # Zod validation schemas (missing in Image, Public, Purchase; empty in Setting)
└── {domain}.utils.ts          # Domain-specific utilities (optional; some modules use .helpers.ts or .repository.ts)

Compliance: 18/30 modules follow this pattern exactly. 10 have partial deviations (missing files or naming inconsistencies). 2 are non-standard: Logger (service-only, no controller/routes) and Swagger (route-only, no controller/service). File naming conventions vary: hyphenated (generic-page.*), underscored (app_config.*), camelCase (menuItem.*), and plain (blog.*).

Directory Structure

saas-boilerplate/
├── src/
│   ├── server.ts                    # Entry point — HTTP server + DB init (Vercel handler export)
│   ├── app.ts                       # Express app factory — middleware pipeline
│   ├── config/                      # 8 config files
│   │   ├── index.ts                 # Centralized env config (30+ variable groups)
│   │   ├── AppConfig.ts             # Singleton config class with required env validation at startup
│   │   ├── dbConfig.ts              # TypeORM DataSource configuration (contains nod_env typo)
│   │   ├── passport.ts              # Google OAuth strategy (handles missing email)
│   │   ├── getStripe.ts             # Stripe instance factory
│   │   ├── stripe.ts                # Direct Stripe instance singleton
│   │   ├── cloudinary.ts            # Cloudinary v2 configuration
│   │   └── getLemonsqueezy.ts       # LemonSqueezy SDK setup + checkout
│   ├── db/
│   │   └── connection.ts            # Serverless-safe DataSource singleton
│   ├── entity/                      # 29 TypeORM entities
│   ├── app/
│   │   ├── modules/v1/              # 30 feature modules (see API Modules)
│   │   ├── middlewares/             # 14 middleware files
│   │   │   ├── auth.ts              # JWT authentication guard
│   │   │   ├── hasPermission.ts     # RBAC permission check (legacy — completely unused, zero imports)
│   │   │   ├── permissionMiddleware.ts # Active RBAC: permission() and hasAnyPermission()
│   │   │   ├── contextMiddleware.ts # AsyncLocalStorage request context + permission cache init
│   │   │   ├── globalErrorHandler.ts# Centralized error handler (Zod, Stripe, ApiError)
│   │   │   ├── rateLimiter.ts       # Rate limiting (configurable window/max/message)
│   │   │   ├── requireStripe.ts     # Stripe availability guard
│   │   │   ├── upload.ts            # Multer file upload (memory storage, image types only)
│   │   │   ├── validateRequest.ts   # Zod schema validation (replaces req.body with validated data)
│   │   │   ├── sanitizeQuery.ts     # Query parameter type casting (string→number/boolean)
│   │   │   ├── sequrity.ts          # [sic] Helmet security headers (CSP, HSTS, Frameguard)
│   │   │   ├── swaggerAuthMiddleware.ts # Swagger docs auth (Basic auth + session)
│   │   │   ├── logger.ts            # Winston logging (JSON + PostgreSQL transport)
│   │   │   └── middleware.ts        # Middleware registration + error handler setup
│   │   ├── routes/v1/
│   │   │   ├── index.ts             # Route aggregator (30 route registrations, all under /api/v1/ prefix)
│   │   │   └── webhook.route.ts     # Webhook routes (root-level, before JSON parser)
│   │   ├── events/
│   │   │   ├── eventEmitter.ts      # Node.js EventEmitter singleton
│   │   │   ├── eventTypes.ts        # Event type constants (10 defined, 7 with handlers)
│   │   │   ├── index.ts             # Handler registration
│   │   │   └── handlers/            # 7 event handlers
│   │   ├── cron/
│   │   │   ├── index.ts             # Cron initialization
│   │   │   ├── scheduleResetLoginAttempts.ts
│   │   │   └── subscribtionExpire.ts # [sic] should be subscriptionExpire
│   │   ├── errors/                  # ApiError class + handleZodError utility
│   │   ├── interfaces/              # 8 TypeScript interface files + sanitizer/ subdirectory
│   │   │   └── sanitizer/
│   │   │       └── sanitizer.interface.ts  # ISanitizer interface
│   │   ├── factories/               # Sanitizer factory (DI pattern)
│   │   │   └── sanitizer/
│   │   │       └── sanitizer.factory.ts    # Creates ISanitizer instances
│   │   ├── infrastructure/          # DOMPurify sanitizer implementation
│   │   │   └── sanitizer/
│   │   │       └── dompurify.sanitizer.ts  # DOMPurify backend sanitizer
│   │   ├── transporter/             # PostgresTransport (Winston DB transport)
│   │   ├── policy/                  # Auth validation policies (10 files)
│   │   │   ├── Policy.ts            # Base policy class
│   │   │   └── policies/            # 9 concrete policies
│   │   │       ├── LoginAttemptPolicy.ts   # Login attempt limit validation
│   │   │       ├── LoginPolicy.ts          # Login flow orchestration
│   │   │       ├── OtpPolicy.ts            # OTP verification validation
│   │   │       ├── PasswordAuthPolicy.ts   # Password authentication
│   │   │       ├── RegistrationPolicy.ts   # Registration validation
│   │   │       ├── TokenValidityPolicy.ts  # Password reset token validation
│   │   │       ├── TwoFactorPolicy.ts      # 2FA validation
│   │   │       ├── UserExistencePolicy.ts  # User existence verification
│   │   │       └── UserStatusPolicy.ts     # Account status verification
│   │   └── builder/                 # Query builders (getQueryBuilder.ts + QueryBuilder.ts)
│   ├── lib/                         # Service factories (added alongside legacy shared/ utilities)
│   │   ├── cache/                   # Cache abstraction layer
│   │   │   ├── cache.interface.ts   # ICache interface
│   │   │   └── Node-cache.ts        # NodeCache implementation
│   │   ├── email/                   # Email provider factory — 10 files including template/ subdirectory
│   │   │   ├── EmailFactory.ts      # Provider selection via MAIL_TYPE config
│   │   │   ├── IEmailService.ts     # Email service interface
│   │   │   ├── NodeMailerService.ts  # Nodemailer provider
│   │   │   ├── ResendService.ts     # Resend provider
│   │   │   ├── BrevoService.ts      # Brevo provider
│   │   │   └── ...                  # + type definitions, transporter, template/
│   │   ├── payment/                 # Payment provider factory — 4 files
│   │   │   ├── PaymentFactory.ts    # Provider selection (Stripe only — LemonSqueezy declared but NOT implemented)
│   │   │   ├── StripeService.ts     # Stripe provider
│   │   │   └── ...                  # + type definitions, service
│   │   └── storage/                 # Storage provider factory — 5 files
│   │       ├── StorageFactory.ts    # Provider selection (Cloudinary/S3 only — Local file exists but NOT integrated)
│   │       ├── CloudinaryStorage.ts # Cloudinary provider
│   │       ├── AwsStorage.ts        # AWS S3 provider
│   │       ├── LocalSotrage.ts      # [sic] Local storage (exists but unused by factory)
│   │       └── IStorageService.ts   # Storage interface
│   ├── shared/                      # 15 shared utility files (legacy cache.ts still here despite lib/cache/)
│   │   ├── catchAsync.ts            # Async error wrapper for RequestHandler
│   │   ├── sendResponse.ts          # Standardized JSON response format
│   │   ├── cache.ts                 # Legacy node-cache wrapper (1hr TTL, prefix-based invalidation)
│   │   ├── permissionCache.ts       # Request-scoped permission cache (Set<string>) with ownership validation
│   │   ├── permissionNormalizer.ts  # Bidirectional format conversion: /route/action ↔ resource.action
│   │   ├── requestContext.ts        # AsyncLocalStorage: RequestContext {user, permissionCache, requestId}
│   │   ├── getCorsOrigins.ts        # Parses ALLOWED_ORIGINS env var
│   │   ├── getDataSource.ts         # TypeORM DataSource accessor
│   │   ├── getDbRepository.ts       # Generic repository factory (by entity class or table name)
│   │   ├── getSortQuery.ts          # Dynamic sort builder for queries
│   │   ├── generateUniqueSlug.ts    # Slug generator with uniqueness check
│   │   ├── pick.ts                  # Object property picker utility
│   │   ├── sanitizeImageName.ts     # Image filename sanitizer
│   │   ├── normalizeImageSrc.ts     # Image URL normalizer
│   │   └── validatePostTypeAndId.ts # Menu post type validator
│   ├── helper/                        # 24+ utility files (top-level + cloud/ and email/ subdirectories)
│   │   ├── uploadToCloudinary.ts     # Cloudinary upload (auto-converts to WebP)
│   │   └── httpLogger.ts            # Winston HTTP logger
│   ├── seed/                        # Database seeders (6 files: seed.ts, permission.seed.ts, role.seed.ts, setting.seed.ts + data/permission.ts, data/setting.ts)
│   ├── template/                    # 5 Email HTML templates
│   └── public/                      # Static files (swagger.yaml)
├── solution docs/                   # Applied fix documentation (AUTH, JWT, OTP, HASHED_PASSWORD, CONFIG, PASSPORT)
├── package.json
├── tsconfig.json                    # ES2016 target, CommonJS modules, strict mode
├── .env.example                     # 30+ env variable groups
└── CLAUDE.md                        # AI assistant context file

Middleware Pipeline (Execution Order)

Global middleware (registered in middleware.tsregisterMiddlewares()):

Request
 ├─ 1. securityMiddleware (sequrity.ts)  # Helmet: CSP, HSTS, Frameguard, noSniff
 ├─ 2. cookieParser()                    # Parse cookies
 ├─ 3. Request logger                    # Console log method + path (dev)
 ├─ 4. compression()                     # Gzip responses
 ├─ 5. cors({ credentials: true })       # Dynamic CORS origins from ALLOWED_ORIGINS
 ├─ 6. express-session                   # Sessions for OAuth (httpOnly, 30-day max)
 ├─ 7. passport.initialize/session       # OAuth middleware
 ├─ 8. express.json() + urlencoded()     # Body parsers
 ├─ 9. Request start time tracker        # For response time metrics
 ├─ 10. contextMiddleware               # AsyncLocalStorage + permission cache init (global for all routes; also listed per-route because it has dual-path behavior: OAuth requests get full context loading, JWT-authenticated requests may short-circuit)

Route registration (in app.ts):

 ├─ 11. /webhooks/* (RAW body)           # Stripe/LemonSqueezy webhooks (before JSON parse)
 ├─ 12. GET / (root welcome)             # Health check / welcome
 ├─ 13. express.static('/uploads')       # Static file serving
 ├─ 14. /api/v1/* → router              # Main API routes (30 modules)
 ├─ 15. globalErrorHandler              # Zod, Stripe, ApiError error handling
 └─ 16. 404 handler                      # Not found JSON response

Per-route middleware (applied in individual route files): - auth() — JWT authentication with access token refresh (not rotation — only a new access token is issued, the refresh token is not replaced) - permission(perm) — Check specific permission from cache - validateRequest(schema) — Zod validation (handles multipart JSON) - rateLimiter(opts) — Rate limiting (auth routes only) - upload.single/array() — Multer file upload (memory storage) - sanitizeQuery — Query parameter type casting (blog list only) - requireStripe — Stripe key availability check (subscription routes) - swaggerAuthMiddleware — Basic auth + session for Swagger docs

API Modules (30 Modules + Webhook) — 212 Endpoints

Path Module Endpoints Description
/api/v1/auth Auth 16 Login, register, refresh, OTP, OAuth, 2FA, password reset
/api/v1/users User 13 User CRUD, profile, bulk delete/restore
/api/v1/analytics Analytic 1 Dashboard metrics and statistics
/api/v1/package-categories PackageCategory 5 Subscription tier categories
/api/v1/packages Package 9 Subscription plans CRUD
/api/v1/roles Role 10 Role CRUD with permission assignment
/api/v1/subscriptions Subscription 3 User subscription lifecycle
/api/v1/payments Payment 2 Payment processing records
/api/v1/purchases Purchase 2 Purchase history and records
/api/v1/permissions Permission 15 Permission CRUD
/api/v1/coupons Cupon [sic] 5 Discount coupon management
/api/v1/app-configs AppConfig 5 Encrypted app configurations
/api/v1/user-settings UserSetting 8 User preferences, 2FA settings
/api/v1/blogs Blog 11 Blog post CRUD with SEO, bulk ops
/api/v1/blog-categories BlogCategory 11 Hierarchical blog categories
/api/v1/blog-tags BlogTag 10 Blog tag management
/api/v1/products Product 5 Product catalog CRUD
/api/v1/product-categories ProductCategory 6 Product category management
/api/v1/pages GenericPage 11 CMS static/dynamic pages
/api/v1/contacts Contact 10 Contact form submissions (entity: ContactMessage)
/api/v1/uploads Image 1 File upload (Cloudinary/S3 — no entity, operates on files)
/api/v1/public Public 8 Public endpoints (no auth — aggregates public data from other entities)
/api/v1/slugs Slug 1 URL slug validation/generation (utility — no own entity)
/api/v1/settings Setting 7 Site settings (key-value store)
/api/v1/menus MenuBuilder/Menu 10 Menu CRUD
/api/v1/menu-items MenuBuilder/MenuItem 11 Hierarchical menu items
/api/v1/invite-users InviteUser 11 User invitation management
/api/v1/user-invites InviteUser Alias route (same module as invite-users)
/api/v1/email-templates EmailTemplate 11 Email template CRUD (JSONB elements)
/api/v1/docs Swagger 2 API documentation UI
/webhooks/* Webhook 2 Stripe/LemonSqueezy webhooks (root level)

Total: 212 unique endpoints across 30 modules + webhook.

Note: Logger module exists under modules/v1/ with only a logger.service.ts (no controller or routes, 0 endpoints) — violates the MVC pattern. A loose stripe.service.ts also exists directly in modules/v1/ outside any module directory — this is an architectural violation of the modular pattern. The MenuBuilder module uses a hierarchical structure with Menu/ and MenuItem/ subdirectories (each containing a full MVC file set), deviating from the flat module convention. The package module directory uses lowercase instead of PascalCase like all other modules.

Common CRUD Pattern

Many data modules implement (Auth, Public, Slug, Analytic, Swagger, and Logger do not follow this pattern): - GET / — List with pagination, search, filters, sorting - GET /:id — Get by ID or slug - POST / — Create with Zod validation - PATCH /:id — Update - DELETE /:id — Soft delete - PATCH /bulk-delete — Bulk soft delete - PATCH /bulk-restore — Bulk restore - PATCH /:id/restore — Single restore - DELETE /:id/permanent — Hard delete

Database Schema (29 Entities)

Note on table names: Entities using explicit @Entity('table_name') have the documented table name. Entities using bare @Entity() default to the lowercase class name with no underscores (e.g., PackageCategorypackagecategory, not package_category).

User Management

Entity Table Key Fields Relations
User user id, name, email, password, status (active/inactive/suspend), provider (credential/google/facebook/github/otp), avatar, phone, address, stripeCustomerId, lemonSqueezyCustomerId, inviteOnly ManyToOne→Role (primary), ManyToMany→Role (extra), OneToMany→Purchase, OneToOne→Subscription, OneToOne→UserSetting, OneToMany→Blog
Role role id, name, displayName OneToMany→User, ManyToMany→Permission (via role_permission), ManyToMany→User (via user_roles)
Permission permission id, route (unique), displayName, readonly
UserSetting user_setting id, userId, isTwoFactorEnabled, twoFactorProvider (email/google_authenticator/sms), twoFactorSecret, tempTwoFactorSecret, backupCodes, language OneToOne→User
InviteUser user_invites id, email (unique), status (PENDING/ACCEPTED)

Authentication & Security

Entity Table Key Fields
Token token id, userId, token (SHA-256 hash), expiresAt — stores password reset tokens; created on forgot-password, consumed on reset
OtpVerification otpverification (bare @Entity) id, email, optHash [sic], isVerified, expiresAt — file: Otp.ts, class: OtpVerification (filename/class name mismatch)
LoginAttempt loginattempt (bare @Entity) id, userId, failedAttemptsCount, lastFailedAt, lockUntil, isBlocked
Logger logger id, status, responseTime, success, errorMessage, userId, ip, requestedAt

Subscription & Payments

Entity Table Key Fields Relations
Package package id, name, slug, price, durationInDays, type, isFree, features (JSON array), billingCycle (trial/monthly/yearly), stripePriceId, lemonVariantId OneToMany→Subscription
PackageCategory packagecategory (bare @Entity) id, name (unique), slug
Subscription subscription id, userId, packageId, startDate, endDate, status (active/expired/pending/cancelled), stripeSubscriptionId OneToOne→User, ManyToOne→Package
Payment payment id, amount, currency, status (pending/completed/failed/refunded), method (stripe/paypal/lemonsqueezy), referenceId, paymentDate, metadata (JSON)
Purchase purchase id, packageId, paymentId, couponId, originalPrice, discountAmount, amountPaid, userId ManyToOne→User, Package, Payment
Coupon coupon id, code (unique), discountType (percent/fixed), amountOff, duration (once/repeating/forever), durationInMonths, stripeCouponId, isActive — file: Cupon.ts [sic]

Content Management

Entity Table Key Fields Relations
Blog blogs id, title, slug (unique), content, excerpt, imageUrl, imageAlt, status (draft/published/archived), publishedAt, authorName, authorId, isFeatured, showOnSlider, isTrending, SEO fields (metaTitle, metaDescription, metaKeywords, robotsIndex, robotsFollow) ManyToOne→User (author), ManyToMany→BlogCategory (via blog_categories_pivot), OneToMany→BlogTag
BlogCategory blogcategory (bare @Entity) id, name, slug, description, serial, parentId Self-referencing hierarchy, ManyToMany→Blog
BlogTag blog_tags id, name, slug, blogId ManyToOne→Blog
BlogViewCount blog_view_counts id, blogId, count ManyToOne→Blog
GenericPage generic_pages id, title, slug, description, image, status, SEO fields

E-Commerce

Entity Table Key Fields Relations
Product products id, name, images (text array), description, categoryId, price, discountPrice, currency ManyToOne→ProductCategory
ProductCategory productcategory (bare @Entity) id, name, description OneToMany→Product
Entity Table Key Fields Relations
Menu menus id, name, slug (unique), locations (JSON array) OneToMany→MenuItem
MenuItem menu_items id, menuId, title, link, linkType (dynamic/static), target, iconClass, color, parentId, order, postType (blogs/pages/categories/tags/packages), postId, parameters, permissions Self-referencing hierarchy, ManyToOne→Menu
Setting settings id, key, display_name, value, details, backupCodes, type, order, group
AppConfig app_configs id, key (unique), displayName, value, isEncrypted
EmailTemplate email_templates id, title, type (unique), subject, elements (JSONB), html
ContactMessage contact_messages id, name, email, details
UserRole userrole (bare @Entity) userId, roleId, createdAt, updatedAt, deletedAt ManyToOne→User, ManyToOne→Role — explicit entity for the user_roles junction (file: UserRoles.ts)

Junction Tables

  • user_roles (userId, roleId) — User ↔ Role many-to-many (also has explicit UserRole entity above)
  • role_permission (roleId, permissionId) — Role ↔ Permission many-to-many
  • blog_categories_pivot (blogId, categoryId) — Blog ↔ BlogCategory many-to-many

Database Design Patterns

  • Soft delete: All 29 entities use @DeleteDateColumn — recoverable deletion
  • Compound indexes: Performance-optimized (e.g., [status, deletedAt], [email, deletedAt])
  • Partial indexes: PostgreSQL WHERE clauses on active records (e.g., WHERE "deletedAt" IS NULL)
  • Enum columns: TypeORM enum types for status/type fields (note: capitalization varies — InviteUser uses UPPER_CASE enums while Subscription/Payment/Blog use lowercase)
  • Hierarchical data: Self-referencing parentId for BlogCategory and MenuItem

Authentication Architecture

Login Flows

  1. Email/Password → Validate → Check LoginAttempt lockout → Generate JWT + Refresh
  2. Email/Password + 2FA → Validate → Require OTP → Verify via Email/TOTP → JWT
  3. OTP Login → Send OTP (crypto.randomInt CSPRNG) → Verify code → Auto-create user if needed → JWT
  4. Google OAuth → Passport strategy → Express session stores OAuth profile → google-oauth.controller.ts retrieves session data → generates JWT + Refresh token → redirects to frontend with tokens

Permission System (RBAC)

Active system: permissionMiddleware.ts — used for all route protection: - permission(perm) — checks if req.user.permissions Set contains the required permission string - hasAnyPermission(perms[]) — checks if user has ANY of multiple permissions - Relies on contextMiddleware having already initialized the permission cache

Legacy system: hasPermission.ts — completely unused (zero imports anywhere in codebase). Retained in the codebase but applied to no routes: - Normalizes request path (removes /api/v1 prefix) - Maps HTTP methods: GET→read, POST→create, PUT/PATCH→edit, DELETE→delete - Constructs permission string: /{resource}/{action} (e.g., /users/create) - Queries Role with permissions from database on each request (no caching)

Permission flow: 1. auth() middleware authenticates user and attaches req.user 2. contextMiddleware loads full user with all roles and permissions, initializes PermissionCache (Set) via AsyncLocalStorage 3. permission() or hasAnyPermission() checks against the cached Set

Permission format: Supports both route format (/users/create) and dot notation (users.create) via permissionNormalizer.ts

  • Super admin bypasses all permission checks
  • Users can have primary role (roleId) + additional roles via user_roles junction table

Sanitizer Architecture (DI Pattern)

A clean dependency-injection pattern for HTML sanitization:

src/app/interfaces/sanitizer/sanitizer.interface.ts   # ISanitizer interface
src/app/infrastructure/sanitizer/dompurify.sanitizer.ts # DOMPurify implementation
src/app/factories/sanitizer/sanitizer.factory.ts       # Factory creating ISanitizer instances

This replaces ad-hoc sanitize-html usage with a testable, swappable backend sanitizer using isomorphic-dompurify. Consumers call the factory to get an ISanitizer instance without knowing the concrete implementation.

Event System

Event-driven via Node.js EventEmitter (eventEmitter.ts) for decoupled side effects:

Event Type Constant Handler File Action
SEND_LOGIN_OTP sendLoginOtp.handler.ts Send OTP email
FORGOT_PASSWORD sendForgotPass.handler.ts Send reset email
SEND_REGISTRATION_EMAIL registerEmailHandler.ts Send verification email
INVITE_USER_CREATED inviteUserCreate.handler.ts Send invitation email
INVITEONLY_USER_LOGIN inviteOnlyLoginSendEmail.ts Handle invite-only login
RESET_LOGIN_ATTEMPTS resetLoginAttempts.handler.ts Clear failed attempts (>7 days old)
RESET_SUBSCRIPTION_STATUS resetSubscribtionStatus.handler.ts [sic] Mark ACTIVE subs with past endDate as EXPIRED

Dead event types (defined in eventTypes.ts but no handlers registered): - OTP_VERIFIED — no handler - RESET_PASSWORD — no handler - VERIFY_EMAIL_REG — no handler

Cron Jobs

Job Purpose
scheduleResetLoginAttempts Reset failed login attempt counters
subscribtionExpire [sic] Check and expire overdue subscriptions

Webhook Integration

Registered at root level (before JSON parser for raw body signature verification):

Stripe Events: checkout.session.completed, invoice.payment_succeeded, payment_intent.payment_failed, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted

LemonSqueezy Events: order_created, order_updated, subscription_created, subscription_updated, subscription_cancelled

Service Factories (src/lib/)

Service factories were added in src/lib/ alongside (not replacing) legacy utilities in src/shared/:

Factory Location Providers Config Key
Cache lib/cache/ NodeCache (ICache interface)
Email lib/email/ Nodemailer, Resend, Brevo MAIL_TYPE
Payment lib/payment/ Stripe only (LemonSqueezy declared in type signature but NOT implemented — will throw at runtime)
Storage lib/storage/ Cloudinary, AWS S3 only (LocalSotrage.ts [sic] exists but NOT wired into StorageFactory) IMAGE_STORAGE

Auth Policy System (src/app/policy/)

The auth module uses a policy-based validation pattern with a base Policy class and 9 concrete policies:

Policy Purpose
LoginAttemptPolicy Validates login attempt limits and lockout
LoginPolicy Orchestrates login flow validation
OtpPolicy Validates OTP verification attempts
PasswordAuthPolicy Handles password authentication checks
RegistrationPolicy Validates registration requirements
TokenValidityPolicy Validates password reset tokens
TwoFactorPolicy Validates 2FA authentication flow
UserExistencePolicy Verifies user existence with configurable settings
UserStatusPolicy Verifies user account status for authentication

Auth Module Structure (Refactored)

The Auth module (src/app/modules/v1/Auth/) was refactored with handler extraction and cleanup completed:

Auth/
├── auth.controller.ts          # Main controller (typo fixed: was auth.contoroller.ts)
├── auth.service.ts             # Main service with policy-based validation
├── auth.route.ts               # Route definitions (createAccount renamed to requestRegistration)
├── auth.utils.ts               # Auth utilities (setTokenCookie with corrected maxAge)
├── auth.validation.ts          # Zod validation schemas (password min-length updated)
├── google-oauth.controller.ts  # OAuth-specific controller (extra controller outside standard pattern)
└── handlers/                   # Extracted auth handlers
    ├── LoginAttemptHandler.ts   # Login attempt tracking/lockout
    ├── OtpHandler.ts            # OTP generation (crypto.randomInt CSPRNG) and verification
    ├── TokenHandler.ts          # JWT generation and refresh token expiration
    └── UserHandler.ts           # User account operations + OTP password handling (async bcrypt)

Note: Cleanup completed Mar 8-11. All backup/refactored files and auth.helpers.ts have been removed. The controller typo (auth.contoroller.ts) has been fixed.

Environment Configuration (30+ Properties)

Database, JWT (4 secrets with expiration), Email (SMTP/Resend/Brevo), Payments (Stripe + LemonSqueezy + legacy SSL), Storage (Cloudinary + AWS S3), OAuth (Google + Facebook placeholders), Admin seed credentials, CORS origins, Master key, Session secret. See .env.example for full reference.

Known config typos: nod_env (should be node_env) in dbConfig.ts, sesseion_secret (should be session_secret).

Startup validation (via AppConfig.ts singleton): Required env vars (JWT_SECRET, REFRESH_TOKEN_SECRET, DB_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET) are validated at startup — missing vars throw immediately. The singleton pattern ensures validation runs exactly once.

Resolved typo: auth.contoroller.ts was renamed to auth.controller.ts on 2026-03-08.

Recent Changes (2026-03-12 through 2026-03-26)

Security and architecture improvements across 8 commits:

Change Impact
OTP generation uses crypto.randomInt (CSPRNG) Replaces insecure Math.random — OTPs are now cryptographically random
Password hashing is async (non-blocking bcrypt) Prevents event loop blocking during hash computation
JWT decodeToken validates signature before decoding Prevents accepting tampered tokens
AppConfig.ts singleton with required env validation Fails fast on missing config; singleton ensures one-time validation
Google OAuth handles missing email gracefully Prevents crash when Google profile lacks email
Sanitizer factory + DOMPurify infrastructure added Clean DI pattern for backend HTML sanitization
Auth handler extraction (4 handlers) Login, OTP, Token, User logic extracted from monolithic controller
6 solution docs added Documented fixes for AUTH, JWT, OTP, HASHED_PASSWORD, CONFIG, PASSPORT

Known Filename Typos

File Issue
sequrity.ts Should be security.ts
subscribtionExpire.ts Should be subscriptionExpire.ts
resetSubscribtionStatus.handler.ts Should be resetSubscriptionStatus.handler.ts
LocalSotrage.ts Should be LocalStorage.ts
Cupon.ts (entity) / Cupon/ (module) Should be Coupon
Otp.ts (entity file) Class inside is OtpVerification — filename/class name mismatch

Known Architectural Issues

Issue Severity Details
hasPermission.ts completely unused Low Zero imports across entire codebase; permissionMiddleware.ts is the active RBAC system
sequrity.ts filename typo Low Should be security.ts; functional but confusing
subscribtionExpire.ts filename typo Low Should be subscriptionExpire.ts
nod_env typo in dbConfig.ts Medium May cause incorrect environment detection
Logger module has no routes Low Only logger.service.ts — violates MVC pattern
Loose stripe.service.ts in modules/v1/ Low File exists outside any module directory
LemonSqueezy payment factory not implemented Medium Declared in type signature but throws at runtime
LocalSotrage.ts not wired into StorageFactory Low File exists but factory ignores it
Legacy shared/cache.ts coexists with lib/cache/ Low Duplicate cache abstraction

Development Commands

yarn dev          # Hot reload via ts-node-dev (port 5500)
yarn seed         # Run database seeders
yarn build        # Compile TypeScript to dist/
yarn start        # Run compiled production server
yarn lint         # ESLint

Generated by BMAD Document Project workflow v1.2.0 — Exhaustive scan, 2026-02-12 | Last modified: 2026-03-26