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.tsexportsregisterMiddlewares()andregisterErrorHandlers()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:
- zod — ZodError class
- stripe — Stripe.errors.StripeError class
- ../errors/handleZodError — Zod error formatter
- ../errors/ApiError — Custom error class
- ../interfaces/error — TErrorSources type
Used By:
- app.ts:86 — app.use(globalErrorHandler) (registered after routes)
- middleware.ts:79 — app.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 = messagebefore processing — used byhttpLoggerfor logging - Response shape:
{ success: false, message: string, error: any } - Does NOT include
errorSourcesin 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/requestContext — runWithContext, RequestContext, RequestUser
- ../../shared/permissionCache — initializePermissionCache
- ../../shared/getDbRepository — DB access
- ../../entity/Role, ../../entity/User — TypeORM entities
- ../../shared/catchAsync — Async error wrapper
Used By:
- app.ts:74 — app.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.roleis set fromreq.user.role(JWT string like "admin"), NOT from the Role entitycontextUser.userRolesis 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 exist — authGuard (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/jwtHelpers — verifyToken, verifyRefreshToken, generateToken
- ../../config — JWT secrets, expiry config
- jsonwebtoken — JwtPayload, 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:27 — router.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.queryentirely with sanitized object:(req as any).query = out - Uses
anycast 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:
- zod — AnyZodObject 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) foroldPassword,newPassword,phonechecks (line 37-39) vs!==(strict) for others — inconsistent but functionally identical for string comparison - Validation wraps body in
{ body: parsedBody }— Zod schemas must expectz.object({ body: z.object({...}) }) - On validation failure, passes ZodError to
next(err)→ caught byglobalErrorHandler
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 disabled — app.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_CREDENTIALSconst 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:3 — expressWinston.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: httpLogger → logger → PostgresTransport → LoggerService.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-categoriesmust come before/blogs/:idto 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.tsdefault export has broken refresh token validation —user.refreshTokenreferences non-existent columnmiddleware.tsis dead code — changes there have NO effect on the applicationsecurityMiddlewareis disabled — no Helmet headers in productionroutes.jsondoesn't exist —/api/v1/routesendpoint crashes at runtimehasPermission()middleware queries DB despite permission cache being availableupload.tscreatesuploads/directory but uses memory storage — directory is unusedswaggerAuthMiddleware.tshas XSS risk from unescapedreq.originalUrlin HTML templatevalidateRequest.tshas hardcoded field skip list — new sensitive fields need code changeslogger.tslosespathandmethoddata — Logger entity missing columnssanitizeQueryis only used by blog routes, not globally- Rate limiter uses in-memory store — not shared across serverless instances
Pre-change Verification Steps¶
- Check
app.tsfor actual middleware registration (notmiddleware.ts) - Verify which auth export (
authvsauthGuard) your route uses - Test refresh token flow end-to-end (currently broken)
- Check
Permission.routevalues in DB match the URL patternshasPermission()constructs - Test file upload endpoints — Multer uses memory storage, files are Buffers
- 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_adminbypass 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:
contextMiddlewarewraps requests inrunWithContext()providing request-scoped state without passing context through every function - Per-request permission cache:
permissionCache.tsbuilds aSet<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:
AsyncLocalStorageviarequestContext.ts— stores user, roles, permissions - Session state:
express-sessionwith server-side storage — used for Swagger docs auth - In-memory state:
express-rate-limitcounters — 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.userwithid,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 dependenciesrateLimiter.ts— Only depends on npm packageupload.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
Related Code & Reuse Opportunities¶
Similar Features Elsewhere¶
helper/hasPermission.ts— A separate permission check helper exists alongside the middleware version. UsespermissionCachefrom context. Not used by the middleware.shared/permissionNormalizer.ts— Permission normalization logic. Not used byhasPermissionmiddleware.
Reusable Utilities Available¶
shared/permissionCache.ts—hasPermissionInCache(permission)returnsboolean | null. Could replace the DB query inhasPermissionmiddleware.shared/catchAsync.ts— Used bycontextMiddlewareandauthGuardbut not byauth(default export).shared/sendResponse.ts— Standardized response format used by controllers. Error handler uses directres.json()instead.
Patterns to Follow¶
- Module pattern: See
Blog/for the standard controller/service/routes/validation structure - catchAsync usage: See
contextMiddleware.tsfor proper async middleware wrapping - Factory middleware: See
rateLimiter.tsfor 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¶
- Eliminate redundant DB queries:
auth()→contextMiddleware→hasPermission()each query the DB for the same user/role data. Could share viareq.useror AsyncLocalStorage context. - Use permission cache in
hasPermission(): The cache is already built bycontextMiddleware. Replace the DB query withhasPermissionInCache(). - Global query sanitization: Apply
sanitizeQueryglobally instead of per-route. - Lazy Swagger YAML loading: Load spec once at startup, not re-read per request (already done correctly —
readFileSyncat module load). - Redis-backed rate limiting: Replace in-memory store for multi-instance deployments.
Technical Debt¶
- Dead code:
middleware.ts(92 LOC), commented-out auth implementation (53 LOC), unused DailyRotateFile import - Filename typo:
sequrity.ts— creates confusion and grep issues - Dual auth implementations:
authGuardandauthwith subtle behavioral differences - Inconsistent error handling:
authGuardusescatchAsync/throw,authuses try/catch/next(err) - Hardcoded values: Swagger CSS URL version,
validateRequestskip list, default Swagger credentials - Missing types:
req.usertyped asany,req._startAtasany public.service.tsin API infrastructure scope: 853-line service better suited for Blog/CMS deep-dive
Modification Guidance¶
To Add New Middleware¶
- Create file in
src/app/middlewares/ - If global: add
app.use()inapp.tsat appropriate position in pipeline - If per-route: import and add in specific route files
- Follow factory pattern if middleware needs configuration:
export const myMiddleware = (options) => (req, res, next) => { ... } - Wrap async middleware with
catchAsync
To Modify Existing Middleware¶
- ALWAYS check
app.tsfor actual registration — ignoremiddleware.ts - Verify which auth export (
authvsauthGuard) the affected routes use - Test with both authenticated and unauthenticated requests
- Check if changes affect the permission cache or context flow
- For validation changes, verify Zod schema structure (
{ body: { ... } })
To Remove/Deprecate¶
- Grep for all imports of the middleware file
- Check both
app.ts(global) and route files (per-route) - Remove from both import and
app.use()/ route chain - Consider if removal affects error handling flow (e.g., removing
catchAsyncwrapper)
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