@synergyerp/backend-standards 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -44,21 +44,21 @@
44
44
 
45
45
  ### 2.1 Framework & Runtime
46
46
 
47
- | Layer | Recommended | Alternative | Notes |
48
- |-------|------------|-------------|-------|
49
- | **Runtime** | Node.js 20+ (LTS) | -- | Standard runtime. |
50
- | **Framework** | Express.js | NestJS | Use Express for lightweight services, NestJS for enterprise-grade apps. |
51
- | **Language** | TypeScript 5.5+ | -- | Non-negotiable. Strict mode required. |
52
- | **Package Manager**| pnpm 9+ | npm / yarn | Developers may use any manager locally, but **pnpm** is preferred for CI/CD. |
47
+ | Layer | Recommended | Alternative | Notes |
48
+ | ------------------- | ----------------- | ----------- | ---------------------------------------------------------------------------- |
49
+ | **Runtime** | Node.js 20+ (LTS) | -- | Standard runtime. |
50
+ | **Framework** | Express.js | NestJS | Use Express for lightweight services, NestJS for enterprise-grade apps. |
51
+ | **Language** | TypeScript 5.5+ | -- | Non-negotiable. Strict mode required. |
52
+ | **Package Manager** | pnpm 9+ | npm / yarn | Developers may use any manager locally, but **pnpm** is preferred for CI/CD. |
53
53
 
54
54
  ### 2.2 Database & Storage
55
55
 
56
- | Layer | Recommendation | Notes |
57
- |-------|---------------|-------|
58
- | **Database** | PostgreSQL | Primary relational database. |
59
- | **ORM / Query Builder** | Prisma or TypeORM | Schema-driven or Decorator-based. |
60
- | **Caching** | Redis | For session management and high-performance caching. |
61
- | **Migrations** | Built-in ORM Migrations | Version-controlled database changes. |
56
+ | Layer | Recommendation | Notes |
57
+ | ----------------------- | ----------------------- | ---------------------------------------------------- |
58
+ | **Database** | PostgreSQL | Primary relational database. |
59
+ | **ORM / Query Builder** | Prisma or TypeORM | Schema-driven or Decorator-based. |
60
+ | **Caching** | Redis | For session management and high-performance caching. |
61
+ | **Migrations** | Built-in ORM Migrations | Version-controlled database changes. |
62
62
 
63
63
  ---
64
64
 
@@ -121,13 +121,13 @@ src/modules/
121
121
 
122
122
  **Module Communication Rules**:
123
123
 
124
- | Rule | Enforcement |
125
- |------|-------------|
126
- | Module A may import from Module B's barrel (`index.ts`) only | ESLint `import/no-internal-modules` |
127
- | Module A must NOT import Module B's repository, controller, or validation files | ESLint `boundaries/element-types` |
128
- | Module A must NOT import flat files from `src/modules/` root | ESLint `boundaries/entry-point` |
129
- | Every module subdirectory must contain an `index.ts` barrel | `check-modularization` script (CI) |
130
- | `index.ts` must only re-export the module's public service(s) | Code review |
124
+ | Rule | Enforcement |
125
+ | ------------------------------------------------------------------------------- | ----------------------------------- |
126
+ | Module A may import from Module B's barrel (`index.ts`) only | ESLint `import/no-internal-modules` |
127
+ | Module A must NOT import Module B's repository, controller, or validation files | ESLint `boundaries/element-types` |
128
+ | Module A must NOT import flat files from `src/modules/` root | ESLint `boundaries/entry-point` |
129
+ | Every module subdirectory must contain an `index.ts` barrel | `check-modularization` script (CI) |
130
+ | `index.ts` must only re-export the module's public service(s) | Code review |
131
131
 
132
132
  **Violation examples**:
133
133
 
@@ -172,18 +172,18 @@ server.listen(PORT, () => {
172
172
 
173
173
  const shutdown = async (signal: string) => {
174
174
  logger.info({ signal }, 'Shutdown signal received. Draining connections...');
175
-
175
+
176
176
  server.close(async (err) => {
177
177
  if (err) {
178
178
  logger.error({ err }, 'Error during graceful shutdown');
179
179
  process.exit(1);
180
180
  }
181
-
181
+
182
182
  // Close database connections
183
183
  // await prisma.$disconnect();
184
184
  // Close Redis connections
185
185
  // await redis.quit();
186
-
186
+
187
187
  logger.info('Server shut down gracefully');
188
188
  process.exit(0);
189
189
  });
@@ -232,8 +232,8 @@ These options ensure a modern, strict, and performant TypeScript environment for
232
232
  "emitDecoratorMetadata": true,
233
233
  "experimentalDecorators": true,
234
234
  "baseUrl": ".",
235
- "paths": { "@/*": ["src/*"] }
236
- }
235
+ "paths": { "@/*": ["src/*"] },
236
+ },
237
237
  }
238
238
  ```
239
239
 
@@ -247,8 +247,8 @@ These options ensure a modern, strict, and performant TypeScript environment for
247
247
  "noUncheckedIndexedAccess": true,
248
248
  "exactOptionalPropertyTypes": true,
249
249
  "noPropertyAccessFromIndexSignature": true,
250
- "allowJs": false
251
- }
250
+ "allowJs": false,
251
+ },
252
252
  }
253
253
  ```
254
254
 
@@ -258,13 +258,13 @@ These options ensure a modern, strict, and performant TypeScript environment for
258
258
 
259
259
  ### 5.0 Required ESLint Plugins for Backend
260
260
 
261
- | Plugin | Purpose |
262
- |--------|---------|
263
- | @eslint/js | ESLint recommended rules |
264
- | typescript-eslint | TypeScript-specific rules |
265
- | eslint-plugin-import | Import ordering + internal module restrictions |
266
- | eslint-plugin-unicorn | Modern JS best practices |
267
- | eslint-plugin-prettier | Prettier as ESLint rule |
261
+ | Plugin | Purpose |
262
+ | ------------------------ | ---------------------------------------------------- |
263
+ | @eslint/js | ESLint recommended rules |
264
+ | typescript-eslint | TypeScript-specific rules |
265
+ | eslint-plugin-import | Import ordering + internal module restrictions |
266
+ | eslint-plugin-unicorn | Modern JS best practices |
267
+ | eslint-plugin-prettier | Prettier as ESLint rule |
268
268
  | eslint-plugin-boundaries | Enforce module isolation and folder dependency rules |
269
269
 
270
270
  Install:
@@ -279,30 +279,30 @@ pnpm add -D eslint @eslint/js typescript-eslint eslint-plugin-import eslint-plug
279
279
 
280
280
  **Single-letter variables are strictly prohibited** except loop indices `i, j, k` and unused parameters `_`.
281
281
 
282
- | Entity | Convention | Good Example | Bad Example |
283
- |--------|-----------|-------------|-------------|
284
- | Files (General) | kebab-case.dot-role.ts | user.controller.ts | UserService.ts |
285
- | Directories | kebab-case | user-profile/ | UserProfile/ |
286
- | Variables | camelCase | userCount, isLoading | uc, data |
287
- | Booleans | camelCase + prefix (is/has/can/should/was) | isActive, hasPermission, canSubmit | active, permission |
288
- | Functions | camelCase (verb+noun) | createUser(), getById() | create(), handle() |
289
- | Classes | PascalCase | UserRepository, UserService | userRepository |
290
- | Interfaces/Types| PascalCase | CreateUserDto, UserProfile | user_profile |
291
- | Enums | PascalCase name, UPPER_SNAKE_CASE members | Role.ADMIN | role.admin |
292
- | Constants | UPPER_SNAKE_CASE | MAX_RETRY_COUNT | maxRetryCount |
293
- | Database Tables | snake_case (singular) | user, role, permission<br/>*(Prisma: PascalCase model with `@@map()`)* | users, RoleTable |
294
- | Database Columns| snake_case | user_id, first_name | userId, firstName |
295
- | Env variables | UPPER_SNAKE + prefix | DATABASE_URL | db_url |
282
+ | Entity | Convention | Good Example | Bad Example |
283
+ | ---------------- | ------------------------------------------ | ---------------------------------------------------------------------- | ------------------ |
284
+ | Files (General) | kebab-case.dot-role.ts | user.controller.ts | UserService.ts |
285
+ | Directories | kebab-case | user-profile/ | UserProfile/ |
286
+ | Variables | camelCase | userCount, isLoading | uc, data |
287
+ | Booleans | camelCase + prefix (is/has/can/should/was) | isActive, hasPermission, canSubmit | active, permission |
288
+ | Functions | camelCase (verb+noun) | createUser(), getById() | create(), handle() |
289
+ | Classes | PascalCase | UserRepository, UserService | userRepository |
290
+ | Interfaces/Types | PascalCase | CreateUserDto, UserProfile | user_profile |
291
+ | Enums | PascalCase name, UPPER_SNAKE_CASE members | Role.ADMIN | role.admin |
292
+ | Constants | UPPER_SNAKE_CASE | MAX_RETRY_COUNT | maxRetryCount |
293
+ | Database Tables | snake_case (singular) | user, role, permission<br/>_(Prisma: PascalCase model with `@@map()`)_ | users, RoleTable |
294
+ | Database Columns | snake_case | user_id, first_name | userId, firstName |
295
+ | Env variables | UPPER_SNAKE + prefix | DATABASE_URL | db_url |
296
296
 
297
297
  #### Banned Variable Names
298
298
 
299
- | Banned | Why | Use Instead |
300
- |--------|-----|-------------|
301
- | const data | Too vague | const users, const response |
302
- | const temp | Temporary to whom? | const formattedDate |
303
- | const arr | Hungarian notation | const permissions |
304
- | const obj | Everything is an object | const config |
305
- | const result | Everything is a result | const validationResult |
299
+ | Banned | Why | Use Instead |
300
+ | ------------ | ----------------------- | --------------------------- |
301
+ | const data | Too vague | const users, const response |
302
+ | const temp | Temporary to whom? | const formattedDate |
303
+ | const arr | Hungarian notation | const permissions |
304
+ | const obj | Everything is an object | const config |
305
+ | const result | Everything is a result | const validationResult |
306
306
 
307
307
  #### Boolean Naming
308
308
 
@@ -314,77 +314,77 @@ Booleans must use a prefix that makes true/false obvious:
314
314
 
315
315
  ```javascript
316
316
  // eslint.config.js (flat config -- backend)
317
- import boundaries from "eslint-plugin-boundaries";
318
- import importPlugin from "eslint-plugin-import";
317
+ import boundaries from 'eslint-plugin-boundaries';
318
+ import importPlugin from 'eslint-plugin-import';
319
319
 
320
320
  export default [
321
321
  // ... other configs
322
322
  {
323
323
  plugins: { boundaries, import: importPlugin },
324
324
  settings: {
325
- "boundaries/include": ["src/**/*"],
326
- "boundaries/elements": [
325
+ 'boundaries/include': ['src/**/*'],
326
+ 'boundaries/elements': [
327
327
  {
328
- mode: "full",
329
- type: "modules",
330
- pattern: "src/modules/*/**",
328
+ mode: 'full',
329
+ type: 'modules',
330
+ pattern: 'src/modules/*/**',
331
331
  },
332
332
  {
333
- mode: "full",
334
- type: "shared",
333
+ mode: 'full',
334
+ type: 'shared',
335
335
  pattern: [
336
- "src/shared/**",
337
- "src/config/**",
338
- "src/routes/**",
339
- "src/app.ts",
340
- "src/server.ts",
336
+ 'src/shared/**',
337
+ 'src/config/**',
338
+ 'src/routes/**',
339
+ 'src/app.ts',
340
+ 'src/server.ts',
341
341
  ],
342
342
  },
343
343
  ],
344
344
  },
345
345
  rules: {
346
346
  // BLOCK: flat files at modules/ root (all modules must be in subdirectories)
347
- "boundaries/entry-point": [
348
- "error",
347
+ 'boundaries/entry-point': [
348
+ 'error',
349
349
  {
350
- default: "disallow",
350
+ default: 'disallow',
351
351
  rules: [
352
352
  {
353
- target: ["src/modules/**/*.ts"],
354
- allow: "src/modules/**/index.ts",
353
+ target: ['src/modules/**/*.ts'],
354
+ allow: 'src/modules/**/index.ts',
355
355
  },
356
356
  ],
357
357
  },
358
358
  ],
359
359
 
360
360
  // BLOCK: deep imports bypassing barrel files (imports must go through index.ts)
361
- "import/no-internal-modules": [
362
- "error",
361
+ 'import/no-internal-modules': [
362
+ 'error',
363
363
  {
364
364
  allow: [
365
365
  // Only allow importing the barrel (index.ts) from each module
366
- "src/modules/*/index",
366
+ 'src/modules/*/index',
367
367
  // Allow shared utilities
368
- "src/shared/**",
369
- "src/config/**",
370
- "src/routes/**",
371
- "src/app",
372
- "src/server",
368
+ 'src/shared/**',
369
+ 'src/config/**',
370
+ 'src/routes/**',
371
+ 'src/app',
372
+ 'src/server',
373
373
  ],
374
374
  },
375
375
  ],
376
376
 
377
377
  // BLOCK: importing repositories or controllers from other modules
378
- "import/no-restricted-paths": [
379
- "error",
378
+ 'import/no-restricted-paths': [
379
+ 'error',
380
380
  {
381
381
  zones: [
382
382
  {
383
- target: "./src/modules/*/",
384
- from: "./src/modules/*/",
385
- except: ["./index.ts"],
383
+ target: './src/modules/*/',
384
+ from: './src/modules/*/',
385
+ except: ['./index.ts'],
386
386
  message:
387
- "Cross-module imports must go through the barrel (index.ts). " +
387
+ 'Cross-module imports must go through the barrel (index.ts). ' +
388
388
  "Do not import another module's repository, controller, or validation files directly.",
389
389
  },
390
390
  ],
@@ -397,13 +397,14 @@ export default [
397
397
 
398
398
  **Enforcement Summary**:
399
399
 
400
- | Rule | What It Catches | Severity |
401
- |------|----------------|----------|
402
- | `boundaries/entry-point` | Flat files at `modules/` root | `error` |
403
- | `import/no-internal-modules` | Deep imports bypassing barrel files (e.g., importing `user.repository.ts` directly) | `error` |
404
- | `import/no-restricted-paths` | Cross-module imports of non-barrel files (repositories, controllers, etc.) | `error` |
400
+ | Rule | What It Catches | Severity |
401
+ | ---------------------------- | ----------------------------------------------------------------------------------- | -------- |
402
+ | `boundaries/entry-point` | Flat files at `modules/` root | `error` |
403
+ | `import/no-internal-modules` | Deep imports bypassing barrel files (e.g., importing `user.repository.ts` directly) | `error` |
404
+ | `import/no-restricted-paths` | Cross-module imports of non-barrel files (repositories, controllers, etc.) | `error` |
405
405
 
406
406
  **Adding a New Module**:
407
+
407
408
  1. Create `src/modules/<module-name>/`
408
409
  2. Create the standard files: `*.controller.ts`, `*.service.ts`, `*.repository.ts`, `*.validation.ts`, `*.routes.ts`
409
410
  3. Create `index.ts` barrel that exports **only** the public service
@@ -445,6 +446,7 @@ export default {
445
446
  ### 7.2 Standard Response Envelopes
446
447
 
447
448
  **Success (200/201)**:
449
+
448
450
  ```json
449
451
  {
450
452
  "statusCode": 200,
@@ -456,23 +458,24 @@ export default {
456
458
  ```
457
459
 
458
460
  **Error (400/500)**:
461
+
459
462
  ```json
460
463
  {
461
464
  "statusCode": 400,
462
465
  "success": false,
463
466
  "message": "Validation failed",
464
- "errors": [ { "field": "email", "message": "Email is required" } ]
467
+ "errors": [{ "field": "email", "message": "Email is required" }]
465
468
  }
466
469
  ```
467
470
 
468
471
  ### 7.2.1 Pagination Standard
469
472
 
470
- | Parameter | Type | Default | Max | Description |
471
- |-----------|------|---------|-----|-------------|
472
- | `page` | number | 1 | — | Current page number |
473
- | `limit` | number | 10 | 100 | Items per page |
474
- | `sortBy` | string | `createdAt` | — | Field to sort by |
475
- | `order` | `asc` \| `desc` | `desc` | — | Sort direction |
473
+ | Parameter | Type | Default | Max | Description |
474
+ | --------- | --------------- | ----------- | --- | ------------------- |
475
+ | `page` | number | 1 | — | Current page number |
476
+ | `limit` | number | 10 | 100 | Items per page |
477
+ | `sortBy` | string | `createdAt` | — | Field to sort by |
478
+ | `order` | `asc` \| `desc` | `desc` | — | Sort direction |
476
479
 
477
480
  Example: `GET /api/v1.0/users?page=1&limit=20&sortBy=name&order=asc`
478
481
 
@@ -508,23 +511,26 @@ CI validates that all PRs follow the template structure. Module isolation compli
508
511
  ## 10. Security Standards
509
512
 
510
513
  ### 10.1 Authentication & Authorization
514
+
511
515
  - **JWT**: Stateless authentication with short-lived access tokens and refresh tokens.
512
516
  - **Password Hashing**: Use `bcrypt` with 12 salt rounds.
513
517
  - **RBAC (Role-Based Access Control)**: Assign users to roles (Admin, Manager, User).
514
518
  - **ABAC (Attribute-Based Access Control)**: Fine-grained permissions based on attributes (Tenant ID, Owner ID).
515
519
 
516
520
  ### 10.2 API Response Masking (Critical)
521
+
517
522
  Sensitive fields must NEVER be returned to the client. Use public DTOs in the service layer to ensure only safe fields are returned.
518
523
 
519
524
  **Global Response Interceptor Example**:
525
+
520
526
  ```typescript
521
527
  class ResponseInterceptor {
522
528
  private SENSITIVE_FIELDS = ['password', 'password_hash', 'salt', 'ssn', 'api_key', 'token'];
523
529
 
524
530
  public mask(data: any): any {
525
531
  if (typeof data !== 'object' || data === null) return data;
526
- if (Array.isArray(data)) return data.map(item => this.mask(item));
527
-
532
+ if (Array.isArray(data)) return data.map((item) => this.mask(item));
533
+
528
534
  const masked: Record<string, any> = {};
529
535
  for (const [key, value] of Object.entries(data)) {
530
536
  if (this.SENSITIVE_FIELDS.includes(key.toLowerCase())) {
@@ -539,6 +545,7 @@ class ResponseInterceptor {
539
545
  ```
540
546
 
541
547
  ### 10.3 Input Validation & Sanitization
548
+
542
549
  - Validate all incoming data using Zod or Class-Validator.
543
550
  - Sanitize HTML to prevent XSS if your application processes user-generated content.
544
551
  - Whitelist allowed request fields to prevent mass-assignment vulnerabilities.
@@ -547,21 +554,25 @@ class ResponseInterceptor {
547
554
 
548
555
  All API endpoints must be protected by rate limiting to prevent abuse and ensure fair resource usage.
549
556
 
550
- | Endpoint Type | Window | Max Requests | Notes |
551
- |---------------|--------|-------------|-------|
552
- | General API | 15 min | 100 per IP | Default for all endpoints |
553
- | Auth endpoints (login, register, MFA) | 15 min | 5 per IP | Prevents brute-force |
554
- | Password reset / email verification | 1 hour | 3 per IP | Prevents spam |
557
+ | Endpoint Type | Window | Max Requests | Notes |
558
+ | ------------------------------------- | ------ | ------------ | ------------------------- |
559
+ | General API | 15 min | 100 per IP | Default for all endpoints |
560
+ | Auth endpoints (login, register, MFA) | 15 min | 5 per IP | Prevents brute-force |
561
+ | Password reset / email verification | 1 hour | 3 per IP | Prevents spam |
555
562
 
556
563
  ```typescript
557
564
  import rateLimit from 'express-rate-limit';
558
565
 
559
566
  export const generalLimiter = rateLimit({
560
- windowMs: 15 * 60 * 1000, // 15 minutes
567
+ windowMs: 15 * 60 * 1000, // 15 minutes
561
568
  max: 100,
562
569
  standardHeaders: true,
563
570
  legacyHeaders: false,
564
- message: { statusCode: 429, success: false, message: 'Too many requests. Please try again later.' },
571
+ message: {
572
+ statusCode: 429,
573
+ success: false,
574
+ message: 'Too many requests. Please try again later.',
575
+ },
565
576
  });
566
577
 
567
578
  export const authLimiter = rateLimit({
@@ -604,17 +615,19 @@ Use `helmet` middleware to set standard security headers on all responses.
604
615
  ```typescript
605
616
  import helmet from 'helmet';
606
617
 
607
- app.use(helmet({
608
- contentSecurityPolicy: false, // Configured separately per application
609
- crossOriginEmbedderPolicy: true,
610
- crossOriginOpenerPolicy: { policy: 'same-origin' },
611
- crossOriginResourcePolicy: { policy: 'same-origin' },
612
- dnsPrefetchControl: { allow: false },
613
- frameguard: { action: 'deny' },
614
- hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
615
- referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
616
- xssFilter: true,
617
- }));
618
+ app.use(
619
+ helmet({
620
+ contentSecurityPolicy: false, // Configured separately per application
621
+ crossOriginEmbedderPolicy: true,
622
+ crossOriginOpenerPolicy: { policy: 'same-origin' },
623
+ crossOriginResourcePolicy: { policy: 'same-origin' },
624
+ dnsPrefetchControl: { allow: false },
625
+ frameguard: { action: 'deny' },
626
+ hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
627
+ referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
628
+ xssFilter: true,
629
+ })
630
+ );
618
631
  ```
619
632
 
620
633
  ---
@@ -625,12 +638,12 @@ app.use(helmet({
625
638
 
626
639
  Every standard must be machine-enforced to be effective.
627
640
 
628
- | Layer | Tool | When | Purpose |
629
- |-------|------|------|---------|
630
- | L1: Editor | ESLint + Prettier | Every save | Immediate developer feedback |
631
- | L2: Pre-commit | Husky + lint-staged | Every commit | Block bad code locally |
632
- | L3: CI Pipeline | GitHub Actions | Every PR | Enforce standards before merge |
633
- | L4: CD Pipeline | Smoke Tests | Every deploy | Verify runtime integrity |
641
+ | Layer | Tool | When | Purpose |
642
+ | --------------- | ------------------- | ------------ | ------------------------------ |
643
+ | L1: Editor | ESLint + Prettier | Every save | Immediate developer feedback |
644
+ | L2: Pre-commit | Husky + lint-staged | Every commit | Block bad code locally |
645
+ | L3: CI Pipeline | GitHub Actions | Every PR | Enforce standards before merge |
646
+ | L4: CD Pipeline | Smoke Tests | Every deploy | Verify runtime integrity |
634
647
 
635
648
  ### 11.2 Required Git Hooks (Husky)
636
649
 
@@ -682,7 +695,13 @@ const MODULES_DIR = join(ROOT, 'src', 'modules');
682
695
  const BARREL_FILE = 'index.ts';
683
696
  let errors = 0;
684
697
 
685
- function isDir(p) { try { return statSync(p).isDirectory(); } catch { return false; } }
698
+ function isDir(p) {
699
+ try {
700
+ return statSync(p).isDirectory();
701
+ } catch {
702
+ return false;
703
+ }
704
+ }
686
705
 
687
706
  console.log('\n🔍 Checking backend module modularization...\n');
688
707
 
@@ -706,21 +725,34 @@ if (!existsSync(MODULES_DIR)) {
706
725
 
707
726
  // Verify barrel only exports service (not repository, controller, etc.)
708
727
  const barrelContent = readdirSync(entryPath);
709
- const hasRepo = barrelContent.some(f => f.includes('repository'));
710
- const hasCtrl = barrelContent.some(f => f.includes('controller'));
711
- const hasValidation = barrelContent.some(f => f.includes('validation'));
712
-
713
- if (hasRepo) console.log(` ⚠️ WARNING: Repository file detected -- ensure it is NOT exported from ${BARREL_FILE}`);
714
- if (hasCtrl) console.log(` ⚠️ WARNING: Controller file detected -- ensure it is NOT exported from ${BARREL_FILE}`);
715
- if (hasValidation) console.log(` ⚠️ WARNING: Validation file detected -- ensure it is NOT exported from ${BARREL_FILE}`);
728
+ const hasRepo = barrelContent.some((f) => f.includes('repository'));
729
+ const hasCtrl = barrelContent.some((f) => f.includes('controller'));
730
+ const hasValidation = barrelContent.some((f) => f.includes('validation'));
731
+
732
+ if (hasRepo)
733
+ console.log(
734
+ ` ⚠️ WARNING: Repository file detected -- ensure it is NOT exported from ${BARREL_FILE}`
735
+ );
736
+ if (hasCtrl)
737
+ console.log(
738
+ ` ⚠️ WARNING: Controller file detected -- ensure it is NOT exported from ${BARREL_FILE}`
739
+ );
740
+ if (hasValidation)
741
+ console.log(
742
+ ` ⚠️ WARNING: Validation file detected -- ensure it is NOT exported from ${BARREL_FILE}`
743
+ );
716
744
  } else if (entry.endsWith('.ts')) {
717
- console.error(` ❌ ERROR: Flat file "modules/${entry}" at root -- move into a module subdirectory`);
745
+ console.error(
746
+ ` ❌ ERROR: Flat file "modules/${entry}" at root -- move into a module subdirectory`
747
+ );
718
748
  errors++;
719
749
  }
720
750
  }
721
751
  }
722
752
 
723
- console.log(`\n${errors === 0 ? '✅' : '❌'} Backend modularization check complete. Errors: ${errors}\n`);
753
+ console.log(
754
+ `\n${errors === 0 ? '✅' : '❌'} Backend modularization check complete. Errors: ${errors}\n`
755
+ );
724
756
  if (errors > 0) process.exit(1);
725
757
  ```
726
758
 
@@ -744,11 +776,11 @@ The `git commit --no-verify` flag skips local hooks. Since CI runs the same chec
744
776
 
745
777
  **The `--no-verify` policy**:
746
778
 
747
- | Action | Consequence |
748
- |--------|-------------|
749
- | `git commit --no-verify` | Commit lands locally; CI blocks PR if checks fail |
750
- | Tampering with `.husky/` | Caught in code review; `.husky/` is committed |
751
- | Disabling husky in `package.json` | CI re-installs hooks via `pnpm prepare` |
779
+ | Action | Consequence |
780
+ | --------------------------------- | ------------------------------------------------- |
781
+ | `git commit --no-verify` | Commit lands locally; CI blocks PR if checks fail |
782
+ | Tampering with `.husky/` | Caught in code review; `.husky/` is committed |
783
+ | Disabling husky in `package.json` | CI re-installs hooks via `pnpm prepare` |
752
784
 
753
785
  > **There is no merge path that bypasses these standards.** Even if hooks are skipped locally, CI enforces the same checks before merge.
754
786
 
@@ -757,6 +789,7 @@ The `git commit --no-verify` flag skips local hooks. Since CI runs the same chec
757
789
  ## 12. CI/CD & Deployment Standards
758
790
 
759
791
  Refer to the **CI/CD Standardization & DevOps Guide** for full details.
792
+
760
793
  - **Docker**: Every project must have a multi-stage Dockerfile.
761
794
  - **Migrations**: Migrations must run automatically in dev and staging CD pipelines. Production migrations require manual approval per the **CI/CD Standardization & DevOps Guide** (Section 10).
762
795
  - **Health Checks**: Implement `/health` endpoint for K8s liveness/readiness probes.
@@ -778,6 +811,7 @@ Refer to the **CI/CD Standardization & DevOps Guide** for full details.
778
811
  All applications must use structured JSON logging to stdout. This allows for easy parsing by log aggregators (ELK, Loki, Datadog).
779
812
 
780
813
  **Recommended Logger (Pino)**:
814
+
781
815
  ```typescript
782
816
  import pino from 'pino';
783
817
  export const logger = pino({
@@ -844,6 +878,7 @@ export const errorHandler = (err: Error, req: Request, res: Response, next: Next
844
878
  ## 16. Appendix: Quick References
845
879
 
846
880
  Standard `package.json` scripts:
881
+
847
882
  ```json
848
883
  {
849
884
  "scripts": {
@@ -861,7 +896,9 @@ Standard `package.json` scripts:
861
896
  ---
862
897
 
863
898
  > **This is a living document.** It should be reviewed and updated quarterly by the line manager and architecture team.
899
+
864
900
  ---
901
+
865
902
  ## Appendix A. Backend Cost & Pricing Reference (June 2026)
866
903
 
867
904
  > Use these estimates for staffing, infrastructure budgeting, and vendor selection. Amounts are mid-2026 commercial market approximations; actual costs vary by geography, vendor, and contract terms.
@@ -880,40 +917,40 @@ Use this table to compare partner-inclusive pricing vs. standard list pricing wh
880
917
 
881
918
  ### A.1 Estimated Annual Costs by Project Size
882
919
 
883
- | Project Size | Backend Devs | Frontend Devs | DevOps | QA / Security | Tools & Licenses | Infrastructure | Total Est. Annual |
884
- |---|---|---|---|---|---|---|---|
885
- | Small (MVP / closed beta) | 1 | 1 | 0.25 | 0.25 | ~USD 2,000 | ~USD 2,000 | ~USD 170,000 |
886
- | Medium (production SaaS) | 2 | 2 | 0.5 | 0.5 | ~USD 5,000 | ~USD 4,000 | ~USD 380,000 |
887
- | Large (enterprise / multi-tenant) | 4 | 4 | 1 | 1 | ~USD 12,000 | ~USD 10,000 | ~USD 900,000 |
888
- | Enterprise (regulated / high scale) | 6+ | 6+ | 2+ | 2+ | ~USD 25,000+ | ~USD 25,000+ | ~USD 1,800,000+ |
920
+ | Project Size | Backend Devs | Frontend Devs | DevOps | QA / Security | Tools & Licenses | Infrastructure | Total Est. Annual |
921
+ | ----------------------------------- | ------------ | ------------- | ------ | ------------- | ---------------- | -------------- | ----------------- |
922
+ | Small (MVP / closed beta) | 1 | 1 | 0.25 | 0.25 | ~USD 2,000 | ~USD 2,000 | ~USD 170,000 |
923
+ | Medium (production SaaS) | 2 | 2 | 0.5 | 0.5 | ~USD 5,000 | ~USD 4,000 | ~USD 380,000 |
924
+ | Large (enterprise / multi-tenant) | 4 | 4 | 1 | 1 | ~USD 12,000 | ~USD 10,000 | ~USD 900,000 |
925
+ | Enterprise (regulated / high scale) | 6+ | 6+ | 2+ | 2+ | ~USD 25,000+ | ~USD 25,000+ | ~USD 1,800,000+ |
889
926
 
890
927
  ### A.2 Tooling & License Costs (Annual)
891
928
 
892
- | Tool / Service | Purpose | Typical Tier | Est. Cost | Notes |
893
- |---|---|---|---|---|
894
- | GitHub Enterprise / Teams | Repo hosting, Actions, GHCR, Environments | Team: USD 4/user/mo; Enterprise: USD 21/user/mo | USD 2,400 – USD 25,000/yr | Private repo Actions minutes are billed past the free tier (2,000 min/mo). |
895
- | Sentry (Error Tracking) | Frontend & backend error monitoring | Team: ~USD 29/app/mo | USD 350+/app/yr | Volume-based; self-hosted option at scale. |
896
- | Datadog / New Relic / Azure Monitor | APM, logs, traces | Host/ingestion based | USD 1,500 – USD 10,000+/yr | Strong for microservice backends. |
897
- | SonarCloud / SonarQube | Static analysis, quality gate | Developer: ~USD 25/dev/mo; Enterprise: annually | USD 2,000 – USD 15,000/yr | SonarCloud SaaS vs self-hosted with infra cost. |
898
- | Snyk / Trivy / Dependabot | Dependency & container vulnerability scanning | Snyk Team: ~USD 19/dev/mo; Trivy: free | USD 0 – USD 5,000/yr | Trivy + GitHub Dependabot often cover needs at zero extra cost. |
899
- | Auth0 / WorkOS / Clerk | Managed auth (optional if custom NestJS auth) | Free to USD 23+/mo | USD 0 – USD 2,000/yr | Custom auth saves license cost but needs more engineering time. |
900
- | Redis Cloud (optional) | Managed Redis / Valkey | 1 GB – 50 GB | USD 100 – USD 1,000/yr | Useful for cache-heavy workloads; not strictly required. |
901
- | Managed Postgres (Azure/RDS/Cloud SQL) | 2 vCPU / 8 GB + storage | USD 150 – USD 500/mo | USD 1,800 – USD 6,000/yr | Preferred for regulated backends due to backups and HA options. |
902
- | Azure Key Vault / AWS Secrets Manager | Secret storage & rotation | Per-secret / per-operation pricing | USD 200 – USD 1,500/yr | Prevents expensive breach remediation. |
903
- | Object Storage (S3 / Blob) | File uploads, media, exports | 1 TB + egress | USD 240 – USD 960/yr | Backend-controlled uploads/downloads. |
929
+ | Tool / Service | Purpose | Typical Tier | Est. Cost | Notes |
930
+ | -------------------------------------- | --------------------------------------------- | ----------------------------------------------- | -------------------------- | -------------------------------------------------------------------------- |
931
+ | GitHub Enterprise / Teams | Repo hosting, Actions, GHCR, Environments | Team: USD 4/user/mo; Enterprise: USD 21/user/mo | USD 2,400 – USD 25,000/yr | Private repo Actions minutes are billed past the free tier (2,000 min/mo). |
932
+ | Sentry (Error Tracking) | Frontend & backend error monitoring | Team: ~USD 29/app/mo | USD 350+/app/yr | Volume-based; self-hosted option at scale. |
933
+ | Datadog / New Relic / Azure Monitor | APM, logs, traces | Host/ingestion based | USD 1,500 – USD 10,000+/yr | Strong for microservice backends. |
934
+ | SonarCloud / SonarQube | Static analysis, quality gate | Developer: ~USD 25/dev/mo; Enterprise: annually | USD 2,000 – USD 15,000/yr | SonarCloud SaaS vs self-hosted with infra cost. |
935
+ | Snyk / Trivy / Dependabot | Dependency & container vulnerability scanning | Snyk Team: ~USD 19/dev/mo; Trivy: free | USD 0 – USD 5,000/yr | Trivy + GitHub Dependabot often cover needs at zero extra cost. |
936
+ | Auth0 / WorkOS / Clerk | Managed auth (optional if custom NestJS auth) | Free to USD 23+/mo | USD 0 – USD 2,000/yr | Custom auth saves license cost but needs more engineering time. |
937
+ | Redis Cloud (optional) | Managed Redis / Valkey | 1 GB – 50 GB | USD 100 – USD 1,000/yr | Useful for cache-heavy workloads; not strictly required. |
938
+ | Managed Postgres (Azure/RDS/Cloud SQL) | 2 vCPU / 8 GB + storage | USD 150 – USD 500/mo | USD 1,800 – USD 6,000/yr | Preferred for regulated backends due to backups and HA options. |
939
+ | Azure Key Vault / AWS Secrets Manager | Secret storage & rotation | Per-secret / per-operation pricing | USD 200 – USD 1,500/yr | Prevents expensive breach remediation. |
940
+ | Object Storage (S3 / Blob) | File uploads, media, exports | 1 TB + egress | USD 240 – USD 960/yr | Backend-controlled uploads/downloads. |
904
941
 
905
942
  ### A.3 Infrastructure Costs (Monthly)
906
943
 
907
- | Environment | Typical Specs | Est. Monthly | Annual Est. |
908
- |---|---|---|---|
909
- | Dev (1 replica, no HA) | 1 vCPU / 2 GB RAM | USD 40 – USD 120 | USD 500 – USD 1,500 |
910
- | Staging (2 replicas) | 2 vCPU / 4 GB RAM each | USD 120 – USD 300 | USD 1,500 – USD 3,600 |
911
- | Production (min 3 replicas, autoscale) | 2 vCPU / 4 GB RAM + LB | USD 300 – USD 900 | USD 3,600 – USD 10,800 |
912
- | Managed PostgreSQL | 2 vCPU / 8 GB + storage | USD 150 – USD 500 | USD 1,800 – USD 6,000 |
913
- | Managed Redis / Valkey | 1 vCPU / 2 GB | USD 15 – USD 50 | USD 180 – USD 600 |
914
- | Object Storage | 1 TB + egress | USD 20 – USD 80 | USD 240 – USD 960 |
915
- | SSL / ingress certs | Managed | Often free | USD 0 |
916
- | DNS / domain | -- | USD 10 – USD 30 | USD 120 – USD 360 |
944
+ | Environment | Typical Specs | Est. Monthly | Annual Est. |
945
+ | -------------------------------------- | ----------------------- | ----------------- | ---------------------- |
946
+ | Dev (1 replica, no HA) | 1 vCPU / 2 GB RAM | USD 40 – USD 120 | USD 500 – USD 1,500 |
947
+ | Staging (2 replicas) | 2 vCPU / 4 GB RAM each | USD 120 – USD 300 | USD 1,500 – USD 3,600 |
948
+ | Production (min 3 replicas, autoscale) | 2 vCPU / 4 GB RAM + LB | USD 300 – USD 900 | USD 3,600 – USD 10,800 |
949
+ | Managed PostgreSQL | 2 vCPU / 8 GB + storage | USD 150 – USD 500 | USD 1,800 – USD 6,000 |
950
+ | Managed Redis / Valkey | 1 vCPU / 2 GB | USD 15 – USD 50 | USD 180 – USD 600 |
951
+ | Object Storage | 1 TB + egress | USD 20 – USD 80 | USD 240 – USD 960 |
952
+ | SSL / ingress certs | Managed | Often free | USD 0 |
953
+ | DNS / domain | -- | USD 10 – USD 30 | USD 120 – USD 360 |
917
954
 
918
955
  > These are mid-2026 reference prices. Reserved/commitment terms generally reduce compute cost by 20%–40%.
919
956
 
@@ -921,18 +958,18 @@ Use this table to compare partner-inclusive pricing vs. standard list pricing wh
921
958
 
922
959
  ### A.4 Open-Source vs Commercial Alternatives (2026)
923
960
 
924
- | Need | Commercial Option | OSS Alternative | Est. Savings | Trade-offs / Notes |
925
- |---|---|---|---|---|
926
- | CI/CD Platform | GitHub Actions / Azure DevOps / GitLab.com | **Woodpecker**, **Gitea Actions**, **Jenkins**, **Drone CI** | Up to ~USD 10,000+/yr | OSS needs self-hosted runners/infra and more ops. Best when private-minute costs dominate. |
927
- | Auth (optional) | Auth0 / WorkOS / Clerk | **Keycloak**, **Authentik**, **SuperTokens** | ~USD 0 – USD 2,000/yr | Keycloak is the most mature. SuperTokens is lighter and DX-friendly. Custom NestJS auth has highest eng cost. |
928
- | Managed Redis | Redis Cloud | **Valkey** (Linux Foundation fork) / self-hosted Redis | ~USD 100 – USD 1,000/yr | Valkey is API/protocol compatible with Redis and avoids license changes. Self-hosted shifts infra cost to ops. |
929
- | API Gateway / BFF | Kong Enterprise / Zuplo | **Kong OSS**, **APISIX**, **Traefik** | ~USD 1,000 – USD 10,000/yr | OSS APIs/gateways are production-grade; commercial adds support, RBAC, and analytics. |
930
- | Logging | Datadog / Splunk / New Relic | **Loki + Promtail**, **OpenSearch**, **Vector + ClickHouse** | ~USD 1,000 – USD 8,000/yr | Loki is the easiest OSS drop-in for K8s logging. OpenSearch is heavier but more capable. |
931
- | Monitoring/APM | Datadog / New Relic / Azure Monitor | **Prometheus + Grafana**, **SigNoz**, **Grafana Cloud** | ~USD 1,500 – USD 10,000+/yr | Grafana Cloud Pro is cheaper than Datadog. Self-hosted Prometheus saves most, but needs SRE skill. |
932
- | Static Analysis | SonarCloud | **SonarQube Community**, **CodeQL**, **ESLint + TS strict + Coverage** | ~USD 2,000 – USD 15,000/yr | SonarQube CE is capable. CodeQL is free on GitHub. Pre-commit linting is free regardless. |
933
- | Dependency Scanning | Snyk | **Trivy**, **OWASP Dependency-Check**, **Dependabot**, **Renovate** | ~USD 0 – USD 5,000/yr | Dependabot + Trivy usually cover needs at zero marginal license cost. Renovate is best for automated updates. |
934
- | Secret Storage | Azure Key Vault premium | **HashiCorp Vault OSS**, **SOPS + age/GPG**, **Doppler CLI (free tier)** | ~USD 200 – USD 1,500/yr | Vault OSS is mature. SOPS is best if secrets are already committed encrypted in Git. |
935
- | Object / File Storage | Blob / S3 managed | **MinIO**, **SeaweedFS** | ~USD 240 – USD 960/yr | Useful on-prem or when egress is expensive. Managed cloud is simpler for small teams. |
961
+ | Need | Commercial Option | OSS Alternative | Est. Savings | Trade-offs / Notes |
962
+ | --------------------- | ------------------------------------------ | ------------------------------------------------------------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------- |
963
+ | CI/CD Platform | GitHub Actions / Azure DevOps / GitLab.com | **Woodpecker**, **Gitea Actions**, **Jenkins**, **Drone CI** | Up to ~USD 10,000+/yr | OSS needs self-hosted runners/infra and more ops. Best when private-minute costs dominate. |
964
+ | Auth (optional) | Auth0 / WorkOS / Clerk | **Keycloak**, **Authentik**, **SuperTokens** | ~USD 0 – USD 2,000/yr | Keycloak is the most mature. SuperTokens is lighter and DX-friendly. Custom NestJS auth has highest eng cost. |
965
+ | Managed Redis | Redis Cloud | **Valkey** (Linux Foundation fork) / self-hosted Redis | ~USD 100 – USD 1,000/yr | Valkey is API/protocol compatible with Redis and avoids license changes. Self-hosted shifts infra cost to ops. |
966
+ | API Gateway / BFF | Kong Enterprise / Zuplo | **Kong OSS**, **APISIX**, **Traefik** | ~USD 1,000 – USD 10,000/yr | OSS APIs/gateways are production-grade; commercial adds support, RBAC, and analytics. |
967
+ | Logging | Datadog / Splunk / New Relic | **Loki + Promtail**, **OpenSearch**, **Vector + ClickHouse** | ~USD 1,000 – USD 8,000/yr | Loki is the easiest OSS drop-in for K8s logging. OpenSearch is heavier but more capable. |
968
+ | Monitoring/APM | Datadog / New Relic / Azure Monitor | **Prometheus + Grafana**, **SigNoz**, **Grafana Cloud** | ~USD 1,500 – USD 10,000+/yr | Grafana Cloud Pro is cheaper than Datadog. Self-hosted Prometheus saves most, but needs SRE skill. |
969
+ | Static Analysis | SonarCloud | **SonarQube Community**, **CodeQL**, **ESLint + TS strict + Coverage** | ~USD 2,000 – USD 15,000/yr | SonarQube CE is capable. CodeQL is free on GitHub. Pre-commit linting is free regardless. |
970
+ | Dependency Scanning | Snyk | **Trivy**, **OWASP Dependency-Check**, **Dependabot**, **Renovate** | ~USD 0 – USD 5,000/yr | Dependabot + Trivy usually cover needs at zero marginal license cost. Renovate is best for automated updates. |
971
+ | Secret Storage | Azure Key Vault premium | **HashiCorp Vault OSS**, **SOPS + age/GPG**, **Doppler CLI (free tier)** | ~USD 200 – USD 1,500/yr | Vault OSS is mature. SOPS is best if secrets are already committed encrypted in Git. |
972
+ | Object / File Storage | Blob / S3 managed | **MinIO**, **SeaweedFS** | ~USD 240 – USD 960/yr | Useful on-prem or when egress is expensive. Managed cloud is simpler for small teams. |
936
973
 
937
974
  ### A.5 Cost Optimization Tips for Backend
938
975
 
@@ -962,5 +999,5 @@ Use this table to compare partner-inclusive pricing vs. standard list pricing wh
962
999
  - Azure Hybrid Benefit: https://azure.microsoft.com/en-us/pricing/benefits/azure-hybrid-benefit/
963
1000
  - Visual Studio subscriptions: https://visualstudio.microsoft.com/subscriptions/
964
1001
  - FastTrack: https://azure.microsoft.com/en-us/programs/azure-fasttrack/
965
- Actual entitlement depends on partner tier, agreement, and region; treat these as **items to explore** with your account team.
1002
+ Actual entitlement depends on partner tier, agreement, and region; treat these as **items to explore** with your account team.
966
1003
  - Infrastructure estimates use managed cloud VM/service pricing without reserved/commitment discounts by default; exact regional pricing is available from cloud provider calculators.