Skip to content

Middleware & API Infrastructure - Deep Dive Documentation

Generated: 2026-02-16 Scope: saas-boilerplate/src/app/middlewares/ (13 files) + modules/v1/{Swagger,Routes,Public}/ (6 files) Files Analyzed: 19 in-scope + 15 dependency files (34 total) Lines of Code: ~2,184 (in-scope files) Workflow Mode: Exhaustive Deep-Dive

Overview

The middleware layer and API infrastructure form the request-processing backbone of the SaaS boilerplate backend. Every HTTP request passes through a middleware pipeline before reaching route handlers, and infrastructure modules provide cross-cutting concerns like documentation, route introspection, and public content serving.

Purpose: Process, validate, authenticate, authorize, and log every incoming HTTP request before it reaches business logic.

Key Responsibilities: - Security headers (Helmet), CORS, compression, cookie parsing - Session management (express-session) - JWT authentication with refresh token support - Role-based permission checking (RBAC) - Request validation (Zod schemas) and query sanitization - File upload handling (Multer) - Rate limiting - Stripe availability gating - Request context (AsyncLocalStorage) with permission caching - Global error handling (Zod, Stripe, ApiError) - HTTP request/response logging (Winston + PostgreSQL) - Swagger API documentation with auth - Route registry endpoint - Public (unauthenticated) content API

Integration Points: User/Role/Permission entities, JWT helpers, Stripe config, Winston logger, PostgreSQL transport, event system


Middleware Pipeline Order (Actual — from app.ts)

The actual middleware registration order in app.ts (lines 20-98) is:

1. cookieParser()                          — Parse cookies from headers
2. Request logger (console, dev only)      — Log method + path (uses config.nod_env typo)
3. initializeCronJobs()                    — Start cron jobs (side effect, not middleware)
4. compression()                           — Gzip response compression
5. trust proxy = true                      — Trust X-Forwarded-For headers
6. cors({ origin, credentials })           — CORS with dynamic origins
7. express-session                         — Session with 30-day cookie
8. passport.initialize()                   — Passport auth init
9. passport.session()                      — Passport session deserialization
10. webhookRouters (at "/")                — Stripe/LemonSqueezy webhooks (BEFORE body parser)
11. express.json()                         — JSON body parser
12. express.urlencoded({ extended: true }) — URL-encoded body parser
13. _startAt = process.hrtime()            — Request timing
14. contextMiddleware                      — AsyncLocalStorage context + permission cache
15. Static files ('/uploads')              — Serve uploaded files
16. router ("/api/v1")                     — All API routes (auth, hasPermission, etc. applied per-route)
17. globalErrorHandler                     — Catch-all error handler
18. 404 handler                            — Fallback for unmatched routes

CRITICAL: The securityMiddleware (Helmet) is imported but commented out at line 22. No security headers are applied in production.

NOTE: middleware.ts exports registerMiddlewares() and registerErrorHandlers() that duplicate this pipeline, but they are never imported or called. Status: unknown (dead code or planned refactor).


Complete File Inventory

1. src/app/middlewares/middleware.ts

Purpose: Exports two functions (registerMiddlewares, registerErrorHandlers) that set up the Express middleware pipeline and error handlers in a modular fashion. Lines of Code: 92 File Type: Middleware orchestrator

What Future Contributors Must Know: This file is dead code. Neither function is imported or called anywhere. The actual middleware setup lives inline in app.ts. If you refactor middleware registration, either wire this file up or delete it. Do NOT modify it expecting changes to take effect.

Exports: - registerMiddlewares(app: Application): void — Registers security, cookies, CORS, sessions, passport, body parsers, timing, context middleware - registerErrorHandlers(app: Application): void — Registers global error handler and 404 fallback

Dependencies: - express, cors, cookie-parser, express-session, compression — npm packages - ./contextMiddleware — Request context setup - ./globalErrorHandler — Error handler - ./sequrity — Helmet security headers (imported as securityMiddleware) - ../../config — For sesseion_secret (typo for session_secret) - ../../shared/getCorsOrigins — Dynamic CORS origins

Used By: Nothing (dead code)

Key Implementation Details: - Pipeline order differs slightly from app.ts: includes securityMiddleware as first middleware (not commented out), missing trust proxy setting, missing webhook route placement before body parser - The 404 handler returns { success: false, message: "API NOT FOUND!", error: { path, message } }

Patterns Used: - Express middleware pipeline: Sequential app.use() calls - Factory function: Returns void, mutates Application parameter

Side Effects: - Sets up session store, passport initialization

Error Handling: Delegates to globalErrorHandler

Testing: No test files

Comments/TODOs: None


2. src/app/middlewares/globalErrorHandler.ts

Purpose: Catches all errors thrown or passed via next(err) in the Express pipeline and returns standardized JSON error responses. Lines of Code: 66 File Type: Error handling middleware

What Future Contributors Must Know: This is the final safety net for all errors. It handles ZodError (validation), Stripe errors, and custom ApiError. The if blocks are not mutually exclusive — a ZodError could theoretically fall through to the StripeError check (though this can't happen in practice). Always use ApiError for custom errors to get proper status codes.

Exports: - default: (err, req, res, next) => void — Express 4-param error handler

Dependencies: - zodZodError class - stripeStripe.errors.StripeError class - ../errors/handleZodError — Zod error formatter - ../errors/ApiError — Custom error class - ../interfaces/errorTErrorSources type

Used By: - app.ts:86app.use(globalErrorHandler) (registered after routes) - middleware.ts:79app.use(globalErrorHandler) (dead code)

Key Implementation Details:

// Error classification cascade (NOT else-if for Zod→Stripe):
if (err instanceof ZodError) { ... }       // Handles validation errors → 400
if (err instanceof Stripe.errors.StripeError) { ... }  // Stripe errors → various codes
else if (err instanceof ApiError) { ... }  // Custom errors → dynamic status
// Default: 500 Internal Server Error
  • Sets res.locals.errorMessage = message before processing — used by httpLogger for logging
  • Response shape: { success: false, message: string, error: any }
  • Does NOT include errorSources in response despite computing them for Zod errors
  • Does NOT log errors (relies on Winston transport to capture from response)

Patterns Used: - Error type discrimination: instanceof checks - Express error middleware: 4-parameter signature (err, req, res, next)

Side Effects: None (pure response formatter)

Error Handling: Is the error handler

Testing: No test files

Comments/TODOs: None


3. src/app/middlewares/contextMiddleware.ts

Purpose: Initializes per-request context using Node.js AsyncLocalStorage. For authenticated users, loads complete user data with roles/permissions from the database and sets up a permission cache. Lines of Code: 93 File Type: Request context middleware

What Future Contributors Must Know: This runs for EVERY request (globally via app.use) but only activates when req.user exists (after Passport auth). It creates a new DB query per request to load user + roles + permissions. The runWithContext() call wraps the entire remaining request pipeline in an AsyncLocalStorage context. If the context middleware fails, the request continues WITHOUT context — hasPermission checks will fall back to DB queries.

Exports: - contextMiddleware (default + named export) — Express middleware wrapped in catchAsync

Dependencies: - ../../shared/requestContextrunWithContext, RequestContext, RequestUser - ../../shared/permissionCacheinitializePermissionCache - ../../shared/getDbRepository — DB access - ../../entity/Role, ../../entity/User — TypeORM entities - ../../shared/catchAsync — Async error wrapper

Used By: - app.ts:74app.use(contextMiddleware) (global, runs for all requests) - middleware.ts:74 — Same (dead code)

Key Implementation Details:

// DB query on every authenticated request:
const fullUser = await userRepo.findOne({
  where: { id: req.user.id },
  relations: ['role', 'userRoles', 'userRoles.permissions', 'role.permissions']
});
  • Request ID: Date.now().toString() + Math.random().toString(36).substr(2, 9) — not a proper UUID
  • Merges primary role (user.role) and extra roles (user.userRoles) with deduplication
  • contextUser.role is set from req.user.role (JWT string like "admin"), NOT from the Role entity
  • contextUser.userRoles is an array of Role entity objects (asymmetric types)
  • Wraps remaining middleware/routes in runWithContext() (AsyncLocalStorage)
  • Initializes permission cache with all role permissions

State Management: AsyncLocalStorage (per-request context, not shared state)

Side Effects: - Database: Reads User with role/permission relations on every authenticated request - Memory: Creates permission cache Set per request

Error Handling: Triple try-catch: outer catchAsync, inner context creation, inner cache initialization. All failures fall through to next() — request continues without context.

Testing: No test files

Comments/TODOs: None


4. src/app/middlewares/auth.ts

Purpose: JWT authentication middleware. Provides two implementations: authGuard (named export, legacy) and auth (default export, current) with refresh token support. Lines of Code: 236 (including 53 lines of commented-out code) File Type: Authentication middleware

What Future Contributors Must Know: 1. Two implementations existauthGuard (named export) and auth (default export). The default auth is used by 25+ route files. authGuard is used in only 2 active routes (Purchase /me, Subscription /me). 2. CRITICAL BUG: The default auth middleware at line 174 checks user.refreshToken !== refreshToken, but the User entity has no refreshToken column. This means refresh token validation always fails — undefined !== <string> is always true. Users with expired access tokens cannot use refresh tokens through this middleware. 3. The default auth sets httpOnly: true on the new access token cookie during refresh (line 189-194), which is correct security practice.

Exports: - authGuard(...roles: string[]) — Named export, used by Purchase/Subscription /me routes - auth(...roles: string[]) — Default export, used by 25+ route files

Dependencies: - ../../helper/jwtHelpersverifyToken, verifyRefreshToken, generateToken - ../../config — JWT secrets, expiry config - jsonwebtokenJwtPayload, Secret types - ../errors/ApiError — Custom error class - ../../shared/getDbRepository — DB access - ../../shared/catchAsync — Async error wrapper - ../../entity/User — User entity

Used By: (25+ route files import default auth, 8 also import authGuard) - Every authenticated route in the application

Key Implementation Details:

Feature authGuard (named) auth (default)
Error handling catchAsync + throw try/catch + next(err)
User lookup By email By id
Role name check Yes (line 59) No
Refresh token No Yes (broken)
Status check Checks inactive (line 52) No status check
Sets req.user.name Yes (line 73) No
Debug messages "You are not authorized!33" (line 63) Clean messages

Token extraction order: Cookie req.cookies.token first, then Authorization: Bearer <token> header.

Refresh token flow (default auth, lines 162-208): 1. Access token expired → check for refresh_token cookie 2. Verify refresh token with verifyRefreshToken() 3. Load user from DB by decoded ID 4. BUG: Check user.refreshToken !== refreshToken (always fails — column doesn't exist) 5. Generate new access token 6. Set new access token in httpOnly cookie (24h maxAge) 7. Continue with refreshed credentials

Patterns Used: - Higher-order function: Returns middleware closure with role params - Token extraction: Cookie-first, header-fallback

Side Effects: - Database: Reads User + Role relation per request - Cookies: May set new token cookie during refresh

Error Handling: - authGuard: Throws ApiError (caught by catchAsync) - auth: Catches all errors and passes via next(err) to global handler

Testing: No test files

Comments/TODOs: - Lines 80-132: Entire commented-out third implementation of auth - Line 63: Debug message "You are not authorized!33" — leftover debug suffix


5. src/app/middlewares/hasPermission.ts

Purpose: Route-level authorization middleware that checks if the authenticated user's role has permission to access the requested endpoint. Lines of Code: 80 File Type: Authorization middleware

What Future Contributors Must Know: This middleware queries the DB for the user's role + permissions on every request it's applied to, even though contextMiddleware.ts already loads and caches all permissions in permissionCache.ts using AsyncLocalStorage. The permission cache is never consulted here. Super admins bypass all checks. Permission is determined by matching the normalized URL path + HTTP method against Permission.route values in the DB.

Exports: - default: hasPermission() — Factory function returning Express middleware

Dependencies: - ../../shared/getDbRepository — DB access - ../../entity/Role — Role entity with permissions relation - ../../entity/Permission — Permission entity (type used) - ../errors/ApiError — Custom error class

Used By: - Payment/payment.route.ts:27router.get("/", auth(), hasPermission(), PaymentController.getAllPayments) — Only usage in entire codebase

Key Implementation Details:

Path normalization:
  /api/v1/payments         → /payments          (strip prefix)
  /api/v1/payments/create  → /payments/create   (keep action suffix)
  /api/v1/payments/123     → /payments          (strip dynamic params)

Method → Action mapping:
  GET    → "read"     → permission: "/payments"
  POST   → "create"   → permission: "/payments/create"
  PATCH  → "edit"     → permission: "/payments/edit"
  DELETE → "delete"   → permission: "/payments/delete"

  URL suffix override: /payments/edit, /payments/delete, /payments/create
  → Uses the suffix directly instead of HTTP method mapping
  • Super admin check: user.role === "super_admin" → bypass all permission checks
  • DB query: roleRepo.findOne({ where: { id: user.roleId }, relations: ["permissions"] })
  • Match: role.permissions.some(perm => perm.route === requiredPermission)

Patterns Used: - Factory function: hasPermission() returns middleware closure - URL-based RBAC: Permissions are route strings, not feature flags

Side Effects: - Database: Reads Role + Permission relation per request

Error Handling: Returns ApiError(UNAUTHORIZED) or ApiError(FORBIDDEN) via next()

Testing: No test files

Comments/TODOs: None


6. src/app/middlewares/rateLimiter.ts

Purpose: Creates configurable rate limiting middleware using express-rate-limit. Lines of Code: 31 File Type: Rate limiting middleware factory

What Future Contributors Must Know: Uses in-memory store by default — rate limits are NOT shared across serverless instances or multiple processes. Each deployment creates its own limit counter. The factory returns a new limiter instance per call, which is correct for different limits on different routes.

Exports: - rateLimiter(options?: RateLimiterOptions) — Factory function returning configured rate limiter - RateLimiterOptions type — { windowMs?, max?, message?, statusCode? }

Dependencies: - express-rate-limit — Rate limiting package

Used By: - Auth/auth.route.ts — Applied to auth endpoints (login, register, etc.)

Key Implementation Details: - Default: 100 requests per IP per minute - Uses standardHeaders: true (RateLimit-* headers), legacyHeaders: false (no X-RateLimit-*) - Custom handler returns { success: false, error: "Too Many Requests", message } with configurable status code (default 429)

Patterns Used: - Factory pattern: Configurable middleware creation

Side Effects: None (stateful via express-rate-limit's internal store)

Error Handling: Custom handler — does not call next() on limit exceeded

Testing: No test files


7. src/app/middlewares/requireStripe.ts

Purpose: Guards routes that require Stripe to be configured. Checks both env-based config and DB-stored encrypted keys. Lines of Code: 24 File Type: Feature gate middleware

What Future Contributors Must Know: This middleware checks for Stripe availability but does NOT pass the Stripe instance to downstream handlers. After this middleware, route handlers must call getStripe() again. The getStripe() function caches the result, so the second call is free.

Exports: - requireStripe — Express middleware (named export)

Dependencies: - ../../config/stripe — Module-level Stripe instance (from env) - ../../config/getStripe — Async Stripe loader (from env or DB) - ../errors/ApiError — Custom error class

Used By: - Subscription/subscription.routes.ts — Guards subscription endpoints

Key Implementation Details: - First checks module-level stripe (sync, from env at import time) - If null, falls back to getStripe() (async, checks DB for encrypted key) - If still null, returns 500 Internal Server Error: "Stripe secret is not defined"

Patterns Used: - Feature gate: Block requests when feature is unavailable

Side Effects: May trigger DB query via getStripe()

Testing: No test files


8. src/app/middlewares/sanitizeQuery.ts

Purpose: Cleans and type-casts query string parameters — converts numeric strings to numbers, "true"/"false" to booleans, removes empty strings. Lines of Code: 29 File Type: Query sanitization middleware

What Future Contributors Must Know: This is NOT applied globally — only blog.routes.ts imports it. All other routes receive raw string query params. Adding this globally would be a breaking change if any route relies on string-typed query values.

Exports: - sanitizeQuery — Express middleware (named export)

Dependencies: None (pure function on req.query)

Used By: - Blog/blog.routes.ts — Applied to blog listing endpoints

Key Implementation Details:

// Casting rules:
""         undefined (removed from query)
"123"      123 (number)
"-1.5"     -1.5 (number)
"true"     true (boolean)
"false"    false (boolean)
"hello"    "hello" (unchanged)
["a","b"]  recursively cast each element

  • Replaces req.query entirely with sanitized object: (req as any).query = out
  • Uses any cast to bypass Express's ReadonlyDict typing

Patterns Used: - Query transformation: Mutates request object in-place

Side Effects: Mutates req.query

Testing: No test files


9. src/app/middlewares/upload.ts

Purpose: Configures Multer for file upload handling with image-only file filtering. Lines of Code: 31 File Type: File upload middleware

What Future Contributors Must Know: Despite creating an uploads/ directory on disk at module load time, this uses memoryStorage() — files are stored in memory as Buffer, NOT written to disk by Multer. The uploads/ directory creation is dead code overhead. On serverless (Vercel), fs.mkdirSync() will fail or create a transient directory.

Exports: - default: upload — Multer instance

Dependencies: - multer — File upload middleware - fs, path — Node.js built-ins

Used By: - Blog/blog.routes.ts — Blog image uploads - BlogCategory/blog_category.routes.ts — Category images - GenericPage/generic-page.routes.ts — Page images - Image/image.route.ts — Standalone image upload - Product/product.routes.ts — Product images - Setting/setting.routes.ts — Site settings images - User/user.routes.ts — User avatar uploads

Key Implementation Details: - Allowed types: jpeg|jpg|png|gif|webp|avif (checked by file extension, not MIME type) - Storage: Memory buffer (multer.memoryStorage()) - No file size limit configured - File filter uses basic regex on extension — no MIME type validation

Patterns Used: - Multer instance: Used as upload.single('fieldName') or upload.fields([...]) in routes

Side Effects: - Creates uploads/ directory at import time (module load side effect) - Holds file data in memory until request completes

Testing: No test files


10. src/app/middlewares/validateRequest.ts

Purpose: Validates request body against Zod schemas, with special handling for multipart/form-data where form fields arrive as strings. Lines of Code: 64 File Type: Request validation middleware

What Future Contributors Must Know: For multipart requests, this middleware parses JSON strings in form fields EXCEPT for a hardcoded skip list: password, token, permissionIds, userRoles, oldPassword, newPassword, phone. These fields are kept as raw strings. Adding new sensitive fields requires editing this skip list. Only validates { body } — does NOT validate req.params or req.query.

Exports: - default: validateRequest(schema: AnyZodObject) — Factory function returning validation middleware

Dependencies: - zodAnyZodObject type

Used By: 25+ route files (most widely used middleware)

Key Implementation Details:

// Multipart form-data handling:
// 1. For each field in req.body:
//    - If field is in skip list → keep as-is
//    - If field value is a JSON string → parse it
//    - Otherwise → try JSON.parse, fallback to raw value
// 2. Validate { body: parsedBody } against Zod schema
// 3. Replace req.body with parsed version
  • Skip list (line 34-39): password, token, permissionIds, userRoles, oldPassword, newPassword, phone
  • Uses != (loose equality) for oldPassword, newPassword, phone checks (line 37-39) vs !== (strict) for others — inconsistent but functionally identical for string comparison
  • Validation wraps body in { body: parsedBody } — Zod schemas must expect z.object({ body: z.object({...}) })
  • On validation failure, passes ZodError to next(err) → caught by globalErrorHandler

Patterns Used: - Higher-order function: validateRequest(schema) returns middleware - Schema validation: Zod parse with async support

Side Effects: Mutates req.body with parsed values

Testing: No test files


11. src/app/middlewares/sequrity.ts (filename typo: should be security.ts)

Purpose: Configures Helmet security headers including CSP, HSTS, frame protection, CORS headers, and referrer policy. Lines of Code: 38 File Type: Security headers middleware

What Future Contributors Must Know: This middleware is currently disabledapp.use(securityMiddleware) is commented out in app.ts line 22. No Helmet security headers are applied to the production API. The filename has a typo (sequrity instead of security). If re-enabled, the CSP rules are quite restrictive (connectSrc: ["'self'"]) and may break external API calls from browsers.

Exports: - default: securityMiddleware — Express Router with Helmet configured

Dependencies: - helmet — Security headers package

Used By: - app.ts:22 — Imported but commented out - middleware.ts:21 — Used in dead code

Key Implementation Details:

CSP Directives:
  defaultSrc:  'self'
  scriptSrc:   'self', https://apis.google.com
  styleSrc:    'self', 'unsafe-inline', fonts.googleapis.com, cdnjs.cloudflare.com
  fontSrc:     'self', fonts.gstatic.com
  imgSrc:      'self', data:, https://res.cloudinary.com/
  connectSrc:  'self'
  objectSrc:   'none'

Other Headers:
  HSTS: maxAge=31536000, includeSubDomains, preload
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: no-referrer-when-downgrade
  Cross-Origin-Embedder-Policy: require-corp
  Cross-Origin-Resource-Policy: same-origin

Testing: No test files


12. src/app/middlewares/swaggerAuthMiddleware.ts

Purpose: Protects Swagger API documentation behind Basic Auth with session persistence. Serves a custom HTML login page for unauthenticated users. Lines of Code: 279 (143 lines of HTML/CSS/JS) File Type: Documentation auth middleware

What Future Contributors Must Know: Default credentials [email protected] / password123 are used when DOCS_EMAIL / DOCS_PASSWORD env vars are missing. Uses session-based auth (req.session.docsAuthenticated) so docs access persists for 30 days (session cookie maxAge). The inline HTML login page is rendered server-side with embedded JavaScript.

Exports: - swaggerAuthMiddleware (default + named export) — Express middleware

Dependencies: - express-session — Session typing (via req.session)

Used By: - Swagger/swagger.route.ts:22 — Protects Swagger UI - Swagger/swagger.route.ts:38 — Protects swagger.json endpoint

Key Implementation Details:

Auth flow:
1. Static assets (.css, .js, .png, .svg, swagger-ui*) → pass through
2. Session check (req.session.docsAuthenticated) → pass through if true
3. POST with Basic Auth header → validate credentials, set session
4. GET with Basic Auth header → validate credentials, set session
5. No auth → return HTML login page
  • Credentials stored in DOCS_CREDENTIALS const at module level
  • Basic Auth: Authorization: Basic <base64(email:password)>
  • Login form uses fetch() with Basic Auth header (not form POST)
  • XSS risk: ${req.originalUrl} is injected into HTML template without sanitization (line 208, 244)

Patterns Used: - Session-based auth: One-time login, session persistence - Progressive auth: Static assets bypass, session check, credential check, fallback to login page

Side Effects: - Modifies session (req.session.docsAuthenticated = true) - Renders HTML response (breaks JSON API convention)

Testing: No test files


13. src/app/middlewares/logger.ts

Purpose: Configures Winston logger with console output and custom PostgreSQL transport for database logging. Lines of Code: 63 File Type: Logger configuration

What Future Contributors Must Know: The file rotation transport (DailyRotateFile) is instantiated but not added to the transports array (commented out at line 57). The PostgreSQL transport only writes in production (config.production check in PostgresTransport). The logger expects metadata with req/res objects — it's designed to be used with express-winston, not standalone. The httpLogger in helper/httpLogger.ts is the middleware wrapper that feeds this logger.

Exports: - logger — Winston logger instance

Dependencies: - winston — Logging framework - winston-daily-rotate-file — File rotation (imported but transport commented out) - ../transporter/PostgresTransport — Custom DB transport

Used By: - helper/httpLogger.ts:3expressWinston.logger({ winstonInstance: logger }) (imported but httpLogger itself is commented out in app.ts)

Key Implementation Details: - Console transport: Always active - PostgresTransport: Active, but only writes to DB in production - File rotation: Instantiated (14-day retention, 20MB max, gzip) but NOT in transports array - Log format: JSON with timestamp, level, method, URL, status, responseTime, errorMessage - Log data flow: httpLoggerloggerPostgresTransportLoggerService.createLogger() → Logger entity

Data loss issue: PostgresTransport passes path and method fields to LoggerService.createLogger(), but: - ICreateLoggerPayload interface does not include path or method - Logger entity does not have path or method columns - Data is silently dropped by TypeORM

Side Effects: - Database: Writes log records in production - Console: Always logs

Testing: No test files


14. src/app/modules/v1/Swagger/swagger.route.ts

Purpose: Sets up Swagger UI documentation endpoint at /api/v1/docs with YAML spec loading and auth protection. Lines of Code: 45 File Type: API documentation route

What Future Contributors Must Know: Reads src/public/swagger.yaml synchronously at module load time using fs.readFileSync. If the YAML file doesn't exist, the server will crash on startup. The external CSS URL points to Swagger UI 4.1.0 on cdnjs — hardcoded version.

Exports: - default: swaggerRoute — Express Router

Dependencies: - swagger-ui-express — Swagger UI middleware - yaml — YAML parser - ../../../middlewares/swaggerAuthMiddleware — Auth protection - fs, path — File reading

Used By: - routes/v1/index.ts:159 — Mounted at /api/v1/docs

Key Implementation Details: - GET /api/v1/docs — Swagger UI (protected by swaggerAuthMiddleware) - GET /api/v1/docs/swagger.json — Raw JSON spec (also protected) - Reads YAML from process.cwd() + '/src/public/swagger.yaml' - Custom CSS applied to Swagger UI layout

Side Effects: - File I/O: Synchronous file read at module load

Testing: No test files


15. src/app/modules/v1/Routes/routerList.routes.ts

Purpose: Single-endpoint router that serves the route registry. Lines of Code: 9 File Type: Route definition

Exports: - default: routerList — Express Router

Used By: - routes/v1/index.ts:76 — Mounted at /api/v1/routes


16. src/app/modules/v1/Routes/routes.controller.ts

Purpose: Reads and serves a routes.json file containing the API route registry. Lines of Code: 19 File Type: Controller

What Future Contributors Must Know: The routes.json file does NOT exist and is in .gitignore. This endpoint will throw ENOENT at runtime. The file path uses __dirname + "/routes.json" which resolves to the compiled JS output directory, not the source. Origin/generation mechanism is unknown.

Exports: - default: RoutesController — Object with getRoutes method

Dependencies: - ../../shared/catchAsync — Async error wrapper - fs, path — File reading

Used By: - Routes/routerList.routes.ts:7

Key Implementation Details: - Reads routes.json synchronously via fs.readFileSync - Returns { success: true, data: routes } with status 200 - No authentication required (mounted without auth() middleware)

Side Effects: - File I/O: Synchronous file read per request (expensive, no caching)


17. src/app/modules/v1/Public/public.routes.ts

Purpose: Defines public (unauthenticated) API routes for blog content and menus. Lines of Code: 22 File Type: Route definition

Exports: - default: publicRoutes — Express Router

Used By: - routes/v1/index.ts:128 — Mounted at /api/v1/public

Key Implementation Details:

GET /api/v1/public/blogs                          — Paginated blog list
GET /api/v1/public/blogs/:id                      — Single blog by ID or slug
GET /api/v1/public/blogs/:categoryNameOrSlug/featured — Featured blogs by category
GET /api/v1/public/blogs/featured-categories      — Categories with blog counts
GET /api/v1/public/top-blogs                      — Top blogs (today/week/month)
GET /api/v1/public/home-page-blogs                — Full homepage blog data
GET /api/v1/public/menus                          — Menu list by type
GET /api/v1/public/menu-items                     — Menu items by type (legacy)

  • No auth() middleware — all endpoints are public
  • No rate limiting — vulnerable to scraping
  • Route order matters: /blogs/featured-categories must come before /blogs/:id to avoid param capture

18. src/app/modules/v1/Public/public.controller.ts

Purpose: Handles HTTP requests for public content endpoints, delegates to PublicService and MenuItemService. Lines of Code: 110 File Type: Controller

Exports: - default: PublicController — Object with 8 handler methods

Dependencies: - ./public.service — Blog and menu queries - ../MenuBuilder/MenuItem/menuItem.service — Menu list service - ../../../../shared/catchAsync — Async wrapper - ../../../../shared/sendResponse — Standardized responses

Used By: - Public/public.routes.ts — All 8 routes


19. src/app/modules/v1/Public/public.service.ts

Purpose: Implements all public content queries — blog listings, categories, trending, featured, homepage aggregation, menu items with nested hierarchy. Lines of Code: 853 File Type: Service (business logic)

What Future Contributors Must Know: This is a large service file with 11 methods and complex TypeORM query builder usage. It accesses 5 entities (Blog, BlogCategory, BlogViewCount, MenuItem, Menu). The getHomePageBlogs() method fires 7 parallel DB queries via Promise.all(). Several methods use raw queries and manual object mapping. The getMenuItemByType() builds a nested tree structure from flat DB results.

Exports: - default: PublicService — Object with 8 public methods (3 more are private helpers)

Dependencies: - ../../../../entity/Blog, BlogCategory, BlogView, MenuItem, Menu — TypeORM entities - ../../../../helper/transformImageUrl, parsePaginationQuery, parseBoolean — Helpers - ../../../../shared/getSortQuery, getDbRepository — Shared utilities - ../../../builder/getQueryBuilder, ../../../builder/QueryBuilder — Custom query builder - ../Blog/blog.service — Blog service (imported but not directly called in visible code)

Used By: - Public/public.controller.ts — All 8 endpoints

Database Entities Accessed: - Blog — Main content entity (all methods) - BlogCategory — Category filtering and counting - BlogViewCount — View-based ranking (top blogs) - MenuItem — Navigation menu items - Menu — Menu containers with location types

Key Implementation Details: - getAllBlogs: Uses custom BaseQueryBuilder with pagination, search, filters - getTopBlogs: Today (by publishedAt), Week/Month (by view count via raw JOIN) - getHomePageBlogs: 7 parallel queries — slider, todaysNews, trending, featured, featuredCategories, latestCategory, monthlyArticles - getMenuItemByType: Flat-to-tree conversion using Map, recursive sort by order - Typo in query: query.tranding (line 54) — should be trending - getBlogByIdOrSlug: Uses SEO-friendly slug lookup with similar blog suggestions - categoriesByFeatured: Uses loadRelationCountAndMap for view counts


Contributor Checklist

Risks & Gotchas

  • auth.ts default export has broken refresh token validation — user.refreshToken references non-existent column
  • middleware.ts is dead code — changes there have NO effect on the application
  • securityMiddleware is disabled — no Helmet headers in production
  • routes.json doesn't exist — /api/v1/routes endpoint crashes at runtime
  • hasPermission() middleware queries DB despite permission cache being available
  • upload.ts creates uploads/ directory but uses memory storage — directory is unused
  • swaggerAuthMiddleware.ts has XSS risk from unescaped req.originalUrl in HTML template
  • validateRequest.ts has hardcoded field skip list — new sensitive fields need code changes
  • logger.ts loses path and method data — Logger entity missing columns
  • sanitizeQuery is only used by blog routes, not globally
  • Rate limiter uses in-memory store — not shared across serverless instances

Pre-change Verification Steps

  1. Check app.ts for actual middleware registration (not middleware.ts)
  2. Verify which auth export (auth vs authGuard) your route uses
  3. Test refresh token flow end-to-end (currently broken)
  4. Check Permission.route values in DB match the URL patterns hasPermission() constructs
  5. Test file upload endpoints — Multer uses memory storage, files are Buffers
  6. Verify Zod schemas expect { body: { ... } } wrapping

Suggested Tests Before PR

  • Auth: Test expired access token + valid refresh token scenario
  • Rate limiting: Verify limits work per-IP in single-instance mode
  • Validation: Test multipart form-data with JSON fields and skip-list fields
  • Permission: Verify super_admin bypass and normal role permission matching
  • Upload: Test with allowed and disallowed file extensions
  • Error handling: Test ZodError, StripeError, ApiError response shapes
  • Public routes: Test pagination, slug lookup, and menu tree building

Architecture & Design Patterns

Code Organization

Middleware files follow a flat structure in src/app/middlewares/ with no subdirectories. Each file exports either a single middleware function or a factory function. API infrastructure modules follow the standard module pattern (controller.ts, routes.ts, service.ts).

Design Patterns

  • Factory pattern (higher-order functions): auth(...roles), hasPermission(), rateLimiter(options), validateRequest(schema) — All return configured middleware closures
  • AsyncLocalStorage context: contextMiddleware wraps requests in runWithContext() providing request-scoped state without passing context through every function
  • Per-request permission cache: permissionCache.ts builds a Set<string> of all user permissions at context creation time
  • URL-based RBAC: Permissions are route strings (/users, /users/create) matched against normalized request paths
  • Cookie-first token extraction: JWT checked in cookies before Authorization header
  • Graceful degradation: Context middleware, permission cache, and logger all fail silently — request continues

State Management Strategy

  • Request-scoped state: AsyncLocalStorage via requestContext.ts — stores user, roles, permissions
  • Session state: express-session with server-side storage — used for Swagger docs auth
  • In-memory state: express-rate-limit counters — per-process, not shared

Error Handling Philosophy

Errors follow a three-tier approach: 1. Middleware-level: catchAsync wraps async handlers, forwards errors via next(err) 2. Application-level: Specific error types (ApiError, ZodError, Stripe.errors.StripeError) 3. Global-level: globalErrorHandler catches all, returns { success: false, message, error }

Testing Strategy

No test files exist for any middleware or infrastructure module. Testing coverage is 0%.


Data Flow

Request Lifecycle (Authenticated API Call)

Client Request
  ├─ 1. cookieParser()     → Parse cookies
  ├─ 2. compression()      → Setup response compression
  ├─ 3. cors()             → Validate origin, set CORS headers
  ├─ 4. session()          → Load/create session
  ├─ 5. passport.init()    → Initialize Passport
  ├─ 6. express.json()     → Parse JSON body
  ├─ 7. urlencoded()       → Parse form body
  ├─ 8. _startAt           → Record request start time
  ├─ 9. contextMiddleware  → Load user + roles + permissions from DB
  │                           → Create AsyncLocalStorage context
  │                           → Initialize permission cache
  ├─ Route Match (/api/v1/...)
  │   ├─ auth()            → Verify JWT, load user from DB again
  │   ├─ hasPermission()   → Load role+perms from DB again (3rd DB call for same user)
  │   ├─ validateRequest() → Validate body against Zod schema
  │   └─ Controller        → Business logic
  ├─ Response
  │   └─ (httpLogger)      → Log to Winston → PostgresTransport → Logger table (if enabled)
  └─ Error Path
      └─ globalErrorHandler → Classify error → JSON response

Data Entry Points

  • HTTP cookies: req.cookies.token, req.cookies.refresh_token — JWT tokens
  • Authorization header: Bearer <token> — JWT access token fallback
  • Request body: JSON or multipart form-data — validated by Zod
  • Query params: URL query string — sanitized by sanitizeQuery (blog routes only)
  • URL path: req.originalUrl — used for permission matching

Data Transformations

  • Query sanitization: String → Number/Boolean/undefined (sanitizeQuery)
  • Multipart body parsing: JSON strings → objects (validateRequest)
  • JWT decoding: Token string → { id, role, email } payload (auth)
  • Permission normalization: URL path → permission string (hasPermission)
  • User enrichment: JWT payload + DB user → req.user with id, name, roleId

Data Exit Points

  • Response JSON: { success, message, data?, meta?, error? }
  • Cookies: New access token set during refresh (res.cookie("token", ...))
  • Session: Swagger auth state (req.session.docsAuthenticated)
  • Database: Logger records (production only)
  • Console: Request logs (development only)

Integration Points

APIs Exposed (Public/Infrastructure)

  • GET /api/v1/public/blogs — Paginated public blogs
  • Auth: None | Response: { blogs[], meta, categories[] }
  • GET /api/v1/public/blogs/:id — Blog by ID or slug
  • Auth: None | Response: Blog with related blogs
  • GET /api/v1/public/top-blogs — Top blogs by period
  • Auth: None | Response: { today[], week[], month[] }
  • GET /api/v1/public/home-page-blogs — Full homepage data
  • Auth: None | Response: { slider[], todaysNews[], trending[], featured[], featuredCategories[], latestCategory[], newsArchive[] }
  • GET /api/v1/public/menus — Nested menu tree
  • Auth: None | Query: ?type=navbar|footer
  • GET /api/v1/public/menu-items — Menu items (legacy)
  • Auth: None | Query: ?type=navbar
  • GET /api/v1/routes — Route registry (BROKEN — missing routes.json)
  • Auth: None
  • GET /api/v1/docs — Swagger UI
  • Auth: Basic Auth via session
  • GET /api/v1/docs/swagger.json — OpenAPI spec
  • Auth: Basic Auth via session

Database Access

Entity Accessed By Operations Indexes Used
User auth.ts, contextMiddleware.ts findOne by id/email + role relation email, roleId
Role contextMiddleware.ts, hasPermission.ts findOne by id + permissions relation IDX_ROLE_NAME_ACTIVE
Permission hasPermission.ts (via Role) Read via relation route (unique)
Logger PostgresTransport → LoggerService create + save None
Blog public.service.ts Complex queries, queryBuilder status, publishedAt
BlogCategory public.service.ts find, queryBuilder None
BlogViewCount public.service.ts JOIN for view counts blogId
Menu public.service.ts JOIN with MenuItem None
MenuItem public.service.ts queryBuilder with tree building parentId, order

Shared State

  • AsyncLocalStorage context: requestContext.ts — per-request user + permissions
  • Permission cache: permissionCache.ts — per-request Set of permission strings
  • Express session store: In-memory (default) — Swagger auth persistence

Events

No events are published or subscribed to by middleware or infrastructure modules.


Dependency Graph

middleware.ts (DEAD CODE)
  ├── contextMiddleware.ts
  ├── globalErrorHandler.ts
  ├── sequrity.ts (Helmet)
  ├── config/index.ts
  └── shared/getCorsOrigins.ts

app.ts (ACTUAL ENTRY)
  ├── contextMiddleware.ts
  │   ├── shared/requestContext.ts (AsyncLocalStorage)
  │   ├── shared/permissionCache.ts
  │   ├── shared/getDbRepository.ts
  │   ├── entity/User.ts
  │   ├── entity/Role.ts
  │   └── shared/catchAsync.ts
  ├── globalErrorHandler.ts
  │   ├── errors/handleZodError.ts
  │   ├── errors/ApiError.ts
  │   └── interfaces/error.ts
  ├── sequrity.ts → helmet
  ├── shared/getCorsOrigins.ts → config
  ├── routes/v1/index.ts (ROUTE TREE)
  │   ├── Swagger/swagger.route.ts
  │   │   └── swaggerAuthMiddleware.ts
  │   ├── Routes/routerList.routes.ts
  │   │   └── Routes/routes.controller.ts
  │   └── Public/public.routes.ts
  │       └── Public/public.controller.ts
  │           └── Public/public.service.ts
  │               ├── entity/Blog.ts
  │               ├── entity/BlogCategory.ts
  │               ├── entity/BlogView.ts
  │               ├── entity/MenuItem.ts
  │               └── entity/Menu.ts
  └── logger.ts
      └── transporter/PostgresTransport.ts
          └── modules/v1/Logger/logger.service.ts
              └── entity/Logger.ts

Per-route middleware (applied in route files):
  auth.ts → helper/jwtHelpers.ts, entity/User.ts, config
  hasPermission.ts → entity/Role.ts, entity/Permission.ts
  validateRequest.ts → zod
  rateLimiter.ts → express-rate-limit
  requireStripe.ts → config/stripe.ts, config/getStripe.ts
  sanitizeQuery.ts → (no deps)
  upload.ts → multer

Entry Points (Not Imported by Others in Scope)

  • app.ts — Application factory (true entry)
  • middleware.ts — Dead code entry

Leaf Nodes (Don't Import Others in Scope)

  • sanitizeQuery.ts — Pure function, no dependencies
  • rateLimiter.ts — Only depends on npm package
  • upload.ts — Only depends on npm package

Circular Dependencies

No circular dependencies detected within scope.


Testing Analysis

Test Coverage Summary

  • Statements: 0%
  • Branches: 0%
  • Functions: 0%
  • Lines: 0%

Test Files

None exist for any file in scope.

Testing Gaps

  • No unit tests for any middleware
  • No integration tests for the middleware pipeline order
  • No tests for error classification in globalErrorHandler
  • No tests for permission path normalization in hasPermission
  • No tests for multipart body parsing in validateRequest
  • No tests for query sanitization edge cases
  • No tests for token refresh flow (which is broken anyway)
  • No tests for Swagger auth flow
  • No tests for public service queries

Similar Features Elsewhere

  • helper/hasPermission.ts — A separate permission check helper exists alongside the middleware version. Uses permissionCache from context. Not used by the middleware.
  • shared/permissionNormalizer.ts — Permission normalization logic. Not used by hasPermission middleware.

Reusable Utilities Available

  • shared/permissionCache.tshasPermissionInCache(permission) returns boolean | null. Could replace the DB query in hasPermission middleware.
  • shared/catchAsync.ts — Used by contextMiddleware and authGuard but not by auth (default export).
  • shared/sendResponse.ts — Standardized response format used by controllers. Error handler uses direct res.json() instead.

Patterns to Follow

  • Module pattern: See Blog/ for the standard controller/service/routes/validation structure
  • catchAsync usage: See contextMiddleware.ts for proper async middleware wrapping
  • Factory middleware: See rateLimiter.ts for a clean factory pattern

Known Issues

Severity Scale: CRITICAL > HIGH > MEDIUM > LOW

# Severity File Line(s) Issue
1 CRITICAL auth.ts 174 user.refreshToken references non-existent User column — refresh token validation always fails
2 CRITICAL routes.controller.ts 6 Reads routes.json that doesn't exist — endpoint crashes with ENOENT
3 HIGH middleware.ts all Entire file is dead code — never imported or called
4 HIGH auth.ts 63 Debug message "You are not authorized!33" in production
5 HIGH auth.ts 16-78, 134-233 Two parallel auth implementations with different behaviors, no documentation
6 HIGH hasPermission.ts 55-58 Queries DB for permissions despite permission cache being available
7 HIGH sequrity.ts / app.ts 22 Security middleware disabled — no Helmet headers in production
8 HIGH swaggerAuthMiddleware.ts 4-7 Default credentials [email protected]/password123 as fallback
9 HIGH swaggerAuthMiddleware.ts 208, 244 XSS risk — req.originalUrl injected into HTML without escaping
10 MEDIUM logger.ts + Logger entity path and method data silently dropped — entity missing columns
11 MEDIUM contextMiddleware.ts 19 Request ID uses Date.now() + Math.random() — not UUID, collision risk
12 MEDIUM contextMiddleware.ts 37 contextUser.role is JWT string vs userRoles is entity array — asymmetric types
13 MEDIUM upload.ts 8-10 Creates uploads/ directory at module load but uses memoryStorage — unused
14 MEDIUM validateRequest.ts 33-39 Hardcoded field skip list — new sensitive fields need code changes
15 MEDIUM sanitizeQuery.ts Only applied to blog routes, not globally
16 MEDIUM rateLimiter.ts In-memory store — not shared across serverless instances
17 MEDIUM requireStripe.ts Checks Stripe availability but doesn't pass instance downstream
18 MEDIUM public.routes.ts No rate limiting on public endpoints — scraping vulnerability
19 MEDIUM routes/routerList.routes.ts No auth required — exposes API structure to anyone
20 LOW sequrity.ts filename Filename typo: sequrity.ts should be security.ts
21 LOW logger.ts 6-13 DailyRotateFile transport instantiated but not used (commented out)
22 LOW config/index.ts 78 nod_env config key typo (should be node_env)
23 LOW auth.ts 80-132 53 lines of commented-out third auth implementation
24 LOW public.service.ts 54 Typo: query.tranding should be query.trending
25 LOW auth.ts 189-194 httpOnly: true on refresh vs potentially false on login — inconsistency
26 LOW app.ts + middleware.ts Redundant auth DB query: auth() loads user, contextMiddleware loads user again, hasPermission() loads role again (up to 3 DB calls for same user per request)

Issue Count: 26 total (2 Critical, 7 High, 9 Medium, 8 Low)


TODOs and Future Work

File Line TODO
middleware.ts Decide: wire up as middleware orchestrator or delete dead code
sequrity.ts Decide: re-enable Helmet or document why it's disabled
routes.controller.ts Implement routes.json generation or remove broken endpoint
auth.ts Fix refreshToken validation (check Token entity instead of User)
auth.ts Consolidate dual auth implementations into one
hasPermission.ts Use permission cache from contextMiddleware instead of DB query
logger.ts Add path and method columns to Logger entity
upload.ts Remove unused uploads/ directory creation
swaggerAuthMiddleware.ts Sanitize req.originalUrl in HTML template
public.routes.ts Add rate limiting to public endpoints

Optimization Opportunities

  1. Eliminate redundant DB queries: auth()contextMiddlewarehasPermission() each query the DB for the same user/role data. Could share via req.user or AsyncLocalStorage context.
  2. Use permission cache in hasPermission(): The cache is already built by contextMiddleware. Replace the DB query with hasPermissionInCache().
  3. Global query sanitization: Apply sanitizeQuery globally instead of per-route.
  4. Lazy Swagger YAML loading: Load spec once at startup, not re-read per request (already done correctly — readFileSync at module load).
  5. Redis-backed rate limiting: Replace in-memory store for multi-instance deployments.

Technical Debt

  1. Dead code: middleware.ts (92 LOC), commented-out auth implementation (53 LOC), unused DailyRotateFile import
  2. Filename typo: sequrity.ts — creates confusion and grep issues
  3. Dual auth implementations: authGuard and auth with subtle behavioral differences
  4. Inconsistent error handling: authGuard uses catchAsync/throw, auth uses try/catch/next(err)
  5. Hardcoded values: Swagger CSS URL version, validateRequest skip list, default Swagger credentials
  6. Missing types: req.user typed as any, req._startAt as any
  7. public.service.ts in API infrastructure scope: 853-line service better suited for Blog/CMS deep-dive

Modification Guidance

To Add New Middleware

  1. Create file in src/app/middlewares/
  2. If global: add app.use() in app.ts at appropriate position in pipeline
  3. If per-route: import and add in specific route files
  4. Follow factory pattern if middleware needs configuration: export const myMiddleware = (options) => (req, res, next) => { ... }
  5. Wrap async middleware with catchAsync

To Modify Existing Middleware

  1. ALWAYS check app.ts for actual registration — ignore middleware.ts
  2. Verify which auth export (auth vs authGuard) the affected routes use
  3. Test with both authenticated and unauthenticated requests
  4. Check if changes affect the permission cache or context flow
  5. For validation changes, verify Zod schema structure ({ body: { ... } })

To Remove/Deprecate

  1. Grep for all imports of the middleware file
  2. Check both app.ts (global) and route files (per-route)
  3. Remove from both import and app.use() / route chain
  4. Consider if removal affects error handling flow (e.g., removing catchAsync wrapper)

Testing Checklist for Changes

  • Auth middleware correctly extracts JWT from cookies and Authorization header
  • Expired access token + valid refresh token works (currently broken)
  • Super admin bypasses permission checks
  • Non-super-admin with matching permission can access endpoint
  • Non-super-admin without permission gets 403
  • Invalid/missing token returns 401
  • Zod validation errors return 400 with readable messages
  • Stripe errors return appropriate status codes
  • File upload rejects non-image files
  • Rate limiter blocks after threshold
  • Public endpoints work without authentication
  • CORS allows configured origins
  • Error handler doesn't leak stack traces in production

Generated by document-project workflow (deep-dive mode) Base Documentation: docs/index.md Scan Date: 2026-02-16 Analysis Mode: Exhaustive