@synergyerp/backend-standards 1.0.0 → 1.1.1

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.
@@ -0,0 +1,966 @@
1
+ # Backend Standardization & Deployment Guide
2
+
3
+ > **Purpose**: Company-wide standards for backend project architecture, code quality, security, and deployment.
4
+ > **Target Audience**: All backend engineers, DevOps teams, and QA engineers.
5
+ > **Date**: June 2026
6
+ > **Status**: v1.0 -- Standard
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ 1. [Philosophy & Guiding Principles](#1-philosophy--guiding-principles)
13
+ 2. [Recommended Tech Stack](#2-recommended-tech-stack)
14
+ 3. [Project Scaffolding & Directory Structure](#3-project-scaffolding--directory-structure)
15
+ 4. [TypeScript Configuration Standards](#4-typescript-configuration-standards)
16
+ 5. [Code Quality & Linting Standards](#5-code-quality--linting-standards)
17
+ 6. [Formatting Standards](#6-formatting-standards)
18
+ 7. [API Service Layer & Database Standards](#7-api-service-layer--database-standards)
19
+ 8. [Testing Standards](#8-testing-standards)
20
+ 9. [Git Workflow & Commit Standards](#9-git-workflow--commit-standards)
21
+ 10. [Security Standards](#10-security-standards)
22
+ 11. [Enforcement: Husky, Git Hooks & CI/CD Validation](#11-enforcement-husky-git-hooks--cicd-validation)
23
+ 12. [CI/CD & Deployment Standards](#12-cicd--deployment-standards)
24
+ 13. [Monitoring & Observability](#13-monitoring--observability)
25
+ 14. [Logging & Error Handling](#14-logging--error-handling)
26
+ 15. [Documentation Standards](#15-documentation-standards)
27
+ 16. [Appendix: Quick References](#16-appendix-quick-references)
28
+
29
+ ---
30
+
31
+ ## 1. Philosophy & Guiding Principles
32
+
33
+ ### 1.1 Core Tenets
34
+
35
+ 1. **Consistency Over Configuration**: Every backend project should follow the same architectural patterns.
36
+ 2. **Type Safety Everywhere**: Use TypeScript strictly to catch errors at compile time.
37
+ 3. **Security By Design**: Authentication, authorization, and input validation are first-class citizens.
38
+ 4. **Performance & Scalability**: Modular architecture ensures we can scale features independently.
39
+ 5. **Separation of Concerns**: Controllers handle requests, Services handle logic, Repositories handle data.
40
+
41
+ ---
42
+
43
+ ## 2. Recommended Tech Stack
44
+
45
+ ### 2.1 Framework & Runtime
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. |
53
+
54
+ ### 2.2 Database & Storage
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. |
62
+
63
+ ---
64
+
65
+ ## 3. Project Scaffolding & Directory Structure
66
+
67
+ ### 3.1 Modular Feature-Based Architecture
68
+
69
+ Projects must adopt a feature-based structure where each business domain owns its logic.
70
+
71
+ ```
72
+ src/
73
+ modules/ # Business logic split by domain
74
+ auth/
75
+ auth.controller.ts
76
+ auth.service.ts
77
+ auth.repository.ts
78
+ auth.validation.ts
79
+ auth.routes.ts
80
+ users/
81
+ user.controller.ts
82
+ user.service.ts
83
+ ...
84
+ shared/ # Shared logic across modules
85
+ middlewares/
86
+ utils/
87
+ constants/
88
+ config/ # Configuration files (db, environment)
89
+ routes/ # Centralized route registration
90
+ app.ts # Express app definition
91
+ server.ts # Server entry point
92
+ ```
93
+
94
+ ### 3.2 Standards
95
+
96
+ - **File naming**: `kebab-case.extension.ts` (e.g., `user.controller.ts`). Use dot notation for role separation.
97
+ - **Directory naming**: `kebab-case` (e.g., `user-profile/`).
98
+ - **Module isolation**: Modules should ideally only interact with other modules via their Services, not Repositories.
99
+ - **.nvmrc**: Every project must include a `.nvmrc` file pinning the Node.js version (e.g., `20`). This ensures consistent runtime across all environments.
100
+
101
+ #### 3.2.1 Module Isolation & Barrel File Enforcement (Strict)
102
+
103
+ Every module subdirectory **must** expose its public surface via an `index.ts` barrel file. Cross-module imports **must** go through the barrel — deep imports into another module's internals are **prohibited**.
104
+
105
+ ```
106
+ src/modules/
107
+ auth/
108
+ index.ts # Barrel: exports ONLY auth.service (public API)
109
+ auth.controller.ts
110
+ auth.service.ts
111
+ auth.repository.ts # INTERNAL -- never imported by other modules
112
+ auth.validation.ts # INTERNAL
113
+ auth.routes.ts
114
+ users/
115
+ index.ts # Barrel: exports ONLY user.service (public API)
116
+ user.controller.ts
117
+ user.service.ts
118
+ user.repository.ts # INTERNAL
119
+ ...
120
+ ```
121
+
122
+ **Module Communication Rules**:
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 |
131
+
132
+ **Violation examples**:
133
+
134
+ ```typescript
135
+ // ❌ BLOCKED: Deep import into another module's repository
136
+ import { UserRepository } from '@/modules/users/user.repository';
137
+
138
+ // ❌ BLOCKED: Importing another module's controller
139
+ import { UserController } from '@/modules/users/user.controller';
140
+
141
+ // ✅ ALLOWED: Import from barrel (public API only)
142
+ import { userService } from '@/modules/users';
143
+ ```
144
+
145
+ **Barrel File Template**:
146
+
147
+ ```typescript
148
+ // src/modules/users/index.ts
149
+ export { userService } from './user.service';
150
+ // NOTE: Do NOT export user.repository, user.controller, or user.validation
151
+ // Those are module-internal -- other modules must go through userService
152
+ ```
153
+
154
+ > For ESLint enforcement configuration, see [Section 5.1.1](#511-module-isolation--barrel-file-eslint-configuration).
155
+
156
+ ### 3.3 Graceful Shutdown (Required)
157
+
158
+ Every backend service must handle SIGTERM/SIGINT gracefully to support zero-downtime deployments.
159
+
160
+ ```typescript
161
+ // server.ts
162
+ import { createServer } from 'http';
163
+ import { app } from './app';
164
+ import { logger } from './shared/utils/logger';
165
+
166
+ const server = createServer(app);
167
+ const PORT = process.env.PORT || 3000;
168
+
169
+ server.listen(PORT, () => {
170
+ logger.info({ port: PORT }, 'Server started');
171
+ });
172
+
173
+ const shutdown = async (signal: string) => {
174
+ logger.info({ signal }, 'Shutdown signal received. Draining connections...');
175
+
176
+ server.close(async (err) => {
177
+ if (err) {
178
+ logger.error({ err }, 'Error during graceful shutdown');
179
+ process.exit(1);
180
+ }
181
+
182
+ // Close database connections
183
+ // await prisma.$disconnect();
184
+ // Close Redis connections
185
+ // await redis.quit();
186
+
187
+ logger.info('Server shut down gracefully');
188
+ process.exit(0);
189
+ });
190
+
191
+ // Force shutdown after 30 seconds
192
+ setTimeout(() => {
193
+ logger.error('Forced shutdown after 30s timeout');
194
+ process.exit(1);
195
+ }, 30_000).unref();
196
+ };
197
+
198
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
199
+ process.on('SIGINT', () => shutdown('SIGINT'));
200
+ ```
201
+
202
+ ---
203
+
204
+ ## 4. TypeScript Configuration Standards
205
+
206
+ ### 4.1 Required Compiler Options
207
+
208
+ These options ensure a modern, strict, and performant TypeScript environment for Node.js:
209
+
210
+ - **target: ES2022**: Emits JavaScript compatible with modern Node.js versions.
211
+ - **module: ESNext**: Uses the latest ECMAScript module system.
212
+ - **moduleResolution: bundler**: Optimized for modern toolchains (use `node` if not using a bundler).
213
+ - **strict: true**: Enables all strict type-checking flags.
214
+ - **esModuleInterop: true**: Ensures compatibility with CommonJS imports.
215
+ - **skipLibCheck: true**: Speeds up compilation by skipping type checks on dependencies.
216
+ - **emitDecoratorMetadata**: Required for dependency injection and ORM decorators (NestJS/TypeORM).
217
+ - **experimentalDecorators**: Enables support for decorators.
218
+ - **baseUrl & paths**: Configures path aliases (e.g., `@/*`) for cleaner imports.
219
+
220
+ ```jsonc
221
+ {
222
+ "compilerOptions": {
223
+ "target": "ES2022",
224
+ "module": "ESNext",
225
+ "moduleResolution": "bundler",
226
+ "strict": true,
227
+ "esModuleInterop": true,
228
+ "skipLibCheck": true,
229
+ "forceConsistentCasingInFileNames": true,
230
+ "resolveJsonModule": true,
231
+ "isolatedModules": true,
232
+ "emitDecoratorMetadata": true,
233
+ "experimentalDecorators": true,
234
+ "baseUrl": ".",
235
+ "paths": { "@/*": ["src/*"] }
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### 4.2 Strict Mode Checklist
241
+
242
+ ```jsonc
243
+ // Additional strict flags beyond "strict": true
244
+ {
245
+ "compilerOptions": {
246
+ // ... standard config from 4.1 ...
247
+ "noUncheckedIndexedAccess": true,
248
+ "exactOptionalPropertyTypes": true,
249
+ "noPropertyAccessFromIndexSignature": true,
250
+ "allowJs": false
251
+ }
252
+ }
253
+ ```
254
+
255
+ ---
256
+
257
+ ## 5. Code Quality & Linting Standards
258
+
259
+ ### 5.0 Required ESLint Plugins for Backend
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 |
268
+ | eslint-plugin-boundaries | Enforce module isolation and folder dependency rules |
269
+
270
+ Install:
271
+
272
+ ```bash
273
+ pnpm add -D eslint @eslint/js typescript-eslint eslint-plugin-import eslint-plugin-unicorn eslint-plugin-prettier eslint-plugin-boundaries
274
+ ```
275
+
276
+ ### 5.1 Naming Conventions -- Complete Reference
277
+
278
+ #### Core Principle: Names Must Be Self-Documenting
279
+
280
+ **Single-letter variables are strictly prohibited** except loop indices `i, j, k` and unused parameters `_`.
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 |
296
+
297
+ #### Banned Variable Names
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 |
306
+
307
+ #### Boolean Naming
308
+
309
+ Booleans must use a prefix that makes true/false obvious:
310
+ `GOOD: isActive, hasPermission, canSubmit, shouldRetry, wasProcessed`
311
+ `BAD: active, permission, submit, retry, processed`
312
+
313
+ #### 5.1.1 Module Isolation & Barrel File ESLint Configuration
314
+
315
+ ```javascript
316
+ // eslint.config.js (flat config -- backend)
317
+ import boundaries from "eslint-plugin-boundaries";
318
+ import importPlugin from "eslint-plugin-import";
319
+
320
+ export default [
321
+ // ... other configs
322
+ {
323
+ plugins: { boundaries, import: importPlugin },
324
+ settings: {
325
+ "boundaries/include": ["src/**/*"],
326
+ "boundaries/elements": [
327
+ {
328
+ mode: "full",
329
+ type: "modules",
330
+ pattern: "src/modules/*/**",
331
+ },
332
+ {
333
+ mode: "full",
334
+ type: "shared",
335
+ pattern: [
336
+ "src/shared/**",
337
+ "src/config/**",
338
+ "src/routes/**",
339
+ "src/app.ts",
340
+ "src/server.ts",
341
+ ],
342
+ },
343
+ ],
344
+ },
345
+ rules: {
346
+ // BLOCK: flat files at modules/ root (all modules must be in subdirectories)
347
+ "boundaries/entry-point": [
348
+ "error",
349
+ {
350
+ default: "disallow",
351
+ rules: [
352
+ {
353
+ target: ["src/modules/**/*.ts"],
354
+ allow: "src/modules/**/index.ts",
355
+ },
356
+ ],
357
+ },
358
+ ],
359
+
360
+ // BLOCK: deep imports bypassing barrel files (imports must go through index.ts)
361
+ "import/no-internal-modules": [
362
+ "error",
363
+ {
364
+ allow: [
365
+ // Only allow importing the barrel (index.ts) from each module
366
+ "src/modules/*/index",
367
+ // Allow shared utilities
368
+ "src/shared/**",
369
+ "src/config/**",
370
+ "src/routes/**",
371
+ "src/app",
372
+ "src/server",
373
+ ],
374
+ },
375
+ ],
376
+
377
+ // BLOCK: importing repositories or controllers from other modules
378
+ "import/no-restricted-paths": [
379
+ "error",
380
+ {
381
+ zones: [
382
+ {
383
+ target: "./src/modules/*/",
384
+ from: "./src/modules/*/",
385
+ except: ["./index.ts"],
386
+ message:
387
+ "Cross-module imports must go through the barrel (index.ts). " +
388
+ "Do not import another module's repository, controller, or validation files directly.",
389
+ },
390
+ ],
391
+ },
392
+ ],
393
+ },
394
+ },
395
+ ];
396
+ ```
397
+
398
+ **Enforcement Summary**:
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` |
405
+
406
+ **Adding a New Module**:
407
+ 1. Create `src/modules/<module-name>/`
408
+ 2. Create the standard files: `*.controller.ts`, `*.service.ts`, `*.repository.ts`, `*.validation.ts`, `*.routes.ts`
409
+ 3. Create `index.ts` barrel that exports **only** the public service
410
+ 4. Add the new module to the `import/no-internal-modules` allow list: `"src/modules/<module-name>/index"`
411
+ 5. Update `import/no-restricted-paths` if the module needs cross-module access patterns
412
+
413
+ ---
414
+
415
+ ## 6. Formatting Standards
416
+
417
+ ### 6.1 Prettier Configuration
418
+
419
+ ```javascript
420
+ // .prettierrc.js
421
+ export default {
422
+ semi: true,
423
+ singleQuote: true,
424
+ tabWidth: 2,
425
+ useTabs: false,
426
+ trailingComma: 'es5',
427
+ printWidth: 100,
428
+ bracketSpacing: true,
429
+ arrowParens: 'always',
430
+ endOfLine: 'lf',
431
+ plugins: ['prettier-plugin-organize-imports'],
432
+ };
433
+ ```
434
+
435
+ ---
436
+
437
+ ## 7. API Service Layer & Database Standards
438
+
439
+ ### 7.1 API Endpoint Standards
440
+
441
+ - Use versioned REST endpoints: `GET /api/v1.0/users`
442
+ - For multi-tenant applications: `GET /api/v1.0/:tenant_id/users`
443
+ - Use plural nouns for resources, but singular for database tables.
444
+
445
+ ### 7.2 Standard Response Envelopes
446
+
447
+ **Success (200/201)**:
448
+ ```json
449
+ {
450
+ "statusCode": 200,
451
+ "success": true,
452
+ "message": "User retrieved successfully",
453
+ "data": [],
454
+ "pagination": { "page": 1, "limit": 10, "totalRecords": 100, "totalPages": 10 }
455
+ }
456
+ ```
457
+
458
+ **Error (400/500)**:
459
+ ```json
460
+ {
461
+ "statusCode": 400,
462
+ "success": false,
463
+ "message": "Validation failed",
464
+ "errors": [ { "field": "email", "message": "Email is required" } ]
465
+ }
466
+ ```
467
+
468
+ ### 7.2.1 Pagination Standard
469
+
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 |
476
+
477
+ Example: `GET /api/v1.0/users?page=1&limit=20&sortBy=name&order=asc`
478
+
479
+ ---
480
+
481
+ ## 8. Testing Standards
482
+
483
+ - **Unit Testing**: Vitest or Jest. Aim for 80% line coverage, 75% branch coverage, 80% function coverage, and 80% statement coverage. Enforcement via CI.
484
+ - **Integration Testing**: Test API endpoints with a test database (e.g., using Supertest).
485
+ - **Mocks**: Mock external services (Email, S3, etc.) but use real databases (via TestContainers) if possible for integration tests.
486
+
487
+ ---
488
+
489
+ ## 9. Git Workflow & Commit Standards
490
+
491
+ Follow the Git workflow and commit standards defined in the **CI/CD Standardization & DevOps Guide** (Section 2) and **Frontend Standardization & Deployment Guide** (Section 9).
492
+
493
+ ### 9.1 PR Template Enforcement
494
+
495
+ Every repository must contain `.github/PULL_REQUEST_TEMPLATE.md`. The canonical backend PR template is provided by **`@aoholdings/backend-standards`** and is auto-copied into the consumer repo's `.github/` directory on `pnpm install`.
496
+
497
+ ```bash
498
+ # Install the standards package (includes PR template)
499
+ pnpm add -D @aoholdings/backend-standards
500
+
501
+ # The PR template is automatically placed at .github/PULL_REQUEST_TEMPLATE.md
502
+ ```
503
+
504
+ CI validates that all PRs follow the template structure. Module isolation compliance is a mandatory section in the template (see BACKEND_STANDARDS.md Section 3.2.1).
505
+
506
+ ---
507
+
508
+ ## 10. Security Standards
509
+
510
+ ### 10.1 Authentication & Authorization
511
+ - **JWT**: Stateless authentication with short-lived access tokens and refresh tokens.
512
+ - **Password Hashing**: Use `bcrypt` with 12 salt rounds.
513
+ - **RBAC (Role-Based Access Control)**: Assign users to roles (Admin, Manager, User).
514
+ - **ABAC (Attribute-Based Access Control)**: Fine-grained permissions based on attributes (Tenant ID, Owner ID).
515
+
516
+ ### 10.2 API Response Masking (Critical)
517
+ Sensitive fields must NEVER be returned to the client. Use public DTOs in the service layer to ensure only safe fields are returned.
518
+
519
+ **Global Response Interceptor Example**:
520
+ ```typescript
521
+ class ResponseInterceptor {
522
+ private SENSITIVE_FIELDS = ['password', 'password_hash', 'salt', 'ssn', 'api_key', 'token'];
523
+
524
+ public mask(data: any): any {
525
+ if (typeof data !== 'object' || data === null) return data;
526
+ if (Array.isArray(data)) return data.map(item => this.mask(item));
527
+
528
+ const masked: Record<string, any> = {};
529
+ for (const [key, value] of Object.entries(data)) {
530
+ if (this.SENSITIVE_FIELDS.includes(key.toLowerCase())) {
531
+ masked[key] = '[REDACTED]';
532
+ } else {
533
+ masked[key] = this.mask(value);
534
+ }
535
+ }
536
+ return masked;
537
+ }
538
+ }
539
+ ```
540
+
541
+ ### 10.3 Input Validation & Sanitization
542
+ - Validate all incoming data using Zod or Class-Validator.
543
+ - Sanitize HTML to prevent XSS if your application processes user-generated content.
544
+ - Whitelist allowed request fields to prevent mass-assignment vulnerabilities.
545
+
546
+ ### 10.4 Rate Limiting
547
+
548
+ All API endpoints must be protected by rate limiting to prevent abuse and ensure fair resource usage.
549
+
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 |
555
+
556
+ ```typescript
557
+ import rateLimit from 'express-rate-limit';
558
+
559
+ export const generalLimiter = rateLimit({
560
+ windowMs: 15 * 60 * 1000, // 15 minutes
561
+ max: 100,
562
+ standardHeaders: true,
563
+ legacyHeaders: false,
564
+ message: { statusCode: 429, success: false, message: 'Too many requests. Please try again later.' },
565
+ });
566
+
567
+ export const authLimiter = rateLimit({
568
+ windowMs: 15 * 60 * 1000,
569
+ max: 5,
570
+ standardHeaders: true,
571
+ legacyHeaders: false,
572
+ });
573
+ ```
574
+
575
+ ### 10.5 CORS Configuration
576
+
577
+ All backend services must configure CORS with an explicit origin whitelist. Never use wildcard (`*`) origins in production.
578
+
579
+ ```typescript
580
+ import cors from 'cors';
581
+
582
+ const ALLOWED_ORIGINS = process.env.CORS_ORIGINS?.split(',') || ['http://localhost:5173'];
583
+
584
+ export const corsMiddleware = cors({
585
+ origin: (origin, callback) => {
586
+ if (!origin || ALLOWED_ORIGINS.includes(origin)) {
587
+ callback(null, true);
588
+ } else {
589
+ callback(new Error('Not allowed by CORS'));
590
+ }
591
+ },
592
+ credentials: true,
593
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
594
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
595
+ exposedHeaders: ['X-Request-ID', 'X-RateLimit-Remaining'],
596
+ maxAge: 86400,
597
+ });
598
+ ```
599
+
600
+ ### 10.6 Security Headers
601
+
602
+ Use `helmet` middleware to set standard security headers on all responses.
603
+
604
+ ```typescript
605
+ import helmet from 'helmet';
606
+
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
+ ```
619
+
620
+ ---
621
+
622
+ ## 11. Enforcement: Husky, Git Hooks & CI/CD Validation
623
+
624
+ ### 11.1 Core Principle: Automation Over Manual Review
625
+
626
+ Every standard must be machine-enforced to be effective.
627
+
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 |
634
+
635
+ ### 11.2 Required Git Hooks (Husky)
636
+
637
+ - **pre-commit**: Runs `lint-staged` (ESLint + Prettier).
638
+ - **commit-msg**: Validates commit messages using `commitlint`.
639
+ - **pre-push**: Runs linting, full test suite, type checking, **and modularization validation**.
640
+
641
+ ### 11.3 Pre-push Hook with Modularization Check
642
+
643
+ ```bash
644
+ # .husky/pre-push
645
+ #!/usr/bin/env sh
646
+ . "$(dirname -- "$0")/_/husky.sh"
647
+
648
+ echo "Running quality checks..."
649
+ pnpm lint && pnpm check-modularization && pnpm check-types && pnpm test:ci
650
+ ```
651
+
652
+ ### 11.4 Modularization Validation Script
653
+
654
+ Add to `package.json`:
655
+
656
+ ```json
657
+ {
658
+ "scripts": {
659
+ "check-modularization": "node scripts/check-modularization.mjs",
660
+ "lint": "eslint --ext .ts --max-warnings 0 .",
661
+ "lint:fix": "eslint --ext .ts . --fix",
662
+ "format": "prettier --write \"src/**/*.ts\"",
663
+ "format:check": "prettier --check \"src/**/*.ts\"",
664
+ "check-types": "tsc --noEmit",
665
+ "prepare": "husky"
666
+ }
667
+ }
668
+ ```
669
+
670
+ Create `scripts/check-modularization.mjs`:
671
+
672
+ ```javascript
673
+ // scripts/check-modularization.mjs
674
+ import { readdirSync, existsSync, statSync } from 'fs';
675
+ import { join, dirname } from 'path';
676
+ import { fileURLToPath } from 'url';
677
+
678
+ const __dirname = dirname(fileURLToPath(import.meta.url));
679
+ const ROOT = join(__dirname, '..');
680
+ const MODULES_DIR = join(ROOT, 'src', 'modules');
681
+
682
+ const BARREL_FILE = 'index.ts';
683
+ let errors = 0;
684
+
685
+ function isDir(p) { try { return statSync(p).isDirectory(); } catch { return false; } }
686
+
687
+ console.log('\n🔍 Checking backend module modularization...\n');
688
+
689
+ if (!existsSync(MODULES_DIR)) {
690
+ console.log(' No src/modules/ directory found. Skipping.');
691
+ } else {
692
+ const entries = readdirSync(MODULES_DIR);
693
+
694
+ for (const entry of entries) {
695
+ const entryPath = join(MODULES_DIR, entry);
696
+
697
+ if (isDir(entryPath)) {
698
+ console.log(` 📁 modules/${entry}/`);
699
+ const barrelPath = join(entryPath, BARREL_FILE);
700
+
701
+ if (!existsSync(barrelPath)) {
702
+ console.error(` ❌ ERROR: Missing required ${BARREL_FILE} barrel file`);
703
+ errors++;
704
+ continue;
705
+ }
706
+
707
+ // Verify barrel only exports service (not repository, controller, etc.)
708
+ 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}`);
716
+ } else if (entry.endsWith('.ts')) {
717
+ console.error(` ❌ ERROR: Flat file "modules/${entry}" at root -- move into a module subdirectory`);
718
+ errors++;
719
+ }
720
+ }
721
+ }
722
+
723
+ console.log(`\n${errors === 0 ? '✅' : '❌'} Backend modularization check complete. Errors: ${errors}\n`);
724
+ if (errors > 0) process.exit(1);
725
+ ```
726
+
727
+ ### 11.5 Anti-Bypass (`--no-verify`) Protection
728
+
729
+ The `git commit --no-verify` flag skips local hooks. Since CI runs the same checks independently, `--no-verify` only delays validation — it never bypasses it.
730
+
731
+ **CI duplicates ALL pre-commit checks**:
732
+
733
+ ```yaml
734
+ # .github/workflows/ci.yml (backend)
735
+ - name: Lint (catches --no-verify bypass)
736
+ run: pnpm lint
737
+
738
+ - name: Check Modularization (catches --no-verify bypass)
739
+ run: pnpm check-modularization
740
+
741
+ - name: Validate Commit Messages (catches --no-verify bypass)
742
+ run: pnpm exec commitlint --from origin/${{ github.base_ref }} --to HEAD
743
+ ```
744
+
745
+ **The `--no-verify` policy**:
746
+
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` |
752
+
753
+ > **There is no merge path that bypasses these standards.** Even if hooks are skipped locally, CI enforces the same checks before merge.
754
+
755
+ ---
756
+
757
+ ## 12. CI/CD & Deployment Standards
758
+
759
+ Refer to the **CI/CD Standardization & DevOps Guide** for full details.
760
+ - **Docker**: Every project must have a multi-stage Dockerfile.
761
+ - **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
+ - **Health Checks**: Implement `/health` endpoint for K8s liveness/readiness probes.
763
+
764
+ ---
765
+
766
+ ## 13. Monitoring & Observability
767
+
768
+ - **Sentry**: Capture runtime exceptions.
769
+ - **Prometheus/Grafana**: Track API latency, request counts, and error rates.
770
+ - **Structured Logging**: Log in JSON format to stdout for log aggregation (ELK/Loki).
771
+
772
+ ---
773
+
774
+ ## 14. Logging & Error Handling
775
+
776
+ ### 14.1 Structured Logging
777
+
778
+ All applications must use structured JSON logging to stdout. This allows for easy parsing by log aggregators (ELK, Loki, Datadog).
779
+
780
+ **Recommended Logger (Pino)**:
781
+ ```typescript
782
+ import pino from 'pino';
783
+ export const logger = pino({
784
+ level: process.env.LOG_LEVEL || 'info',
785
+ formatters: {
786
+ level: (label) => ({ level: label.toUpperCase() }),
787
+ },
788
+ timestamp: pino.stdTimeFunctions.isoTime,
789
+ });
790
+ ```
791
+
792
+ ### 14.1.1 Request ID Middleware
793
+
794
+ Every request must carry a unique `X-Request-ID` header for distributed tracing.
795
+
796
+ ```typescript
797
+ import { v4 as uuidv4 } from 'uuid';
798
+ import type { Request, Response, NextFunction } from 'express';
799
+
800
+ export const requestIdMiddleware = (req: Request, res: Response, next: NextFunction) => {
801
+ const requestId = (req.headers['x-request-id'] as string) || uuidv4();
802
+ req.headers['x-request-id'] = requestId;
803
+ res.setHeader('X-Request-ID', requestId);
804
+ next();
805
+ };
806
+
807
+ // In logger: include requestId in every log entry
808
+ logger.child({ requestId: req.headers['x-request-id'] });
809
+ ```
810
+
811
+ ### 14.2 Centralized Error Handling
812
+
813
+ > **CRITICAL**: Import `express-async-errors` at the top of `app.ts` to ensure async route handler rejections are caught by the error handler middleware. Without this, unhandled promise rejections in async routes will crash the process.
814
+ >
815
+ > ```typescript
816
+ > import 'express-async-errors';
817
+ > ```
818
+
819
+ Use a global error middleware to catch all unhandled exceptions and return a standardized response.
820
+
821
+ ```typescript
822
+ export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
823
+ const statusCode = (err as any).statusCode || 500;
824
+ logger.error({ err, path: req.path }, 'Unhandled exception');
825
+
826
+ res.status(statusCode).json({
827
+ statusCode,
828
+ success: false,
829
+ message: process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message,
830
+ stack: process.env.NODE_ENV === 'production' ? undefined : err.stack,
831
+ });
832
+ };
833
+ ```
834
+
835
+ ---
836
+
837
+ ## 15. Documentation Standards
838
+
839
+ - **Swagger**: Every API must expose a Swagger UI at `/docs`.
840
+ - **README**: Clear setup instructions, environment variables, and script definitions.
841
+
842
+ ---
843
+
844
+ ## 16. Appendix: Quick References
845
+
846
+ Standard `package.json` scripts:
847
+ ```json
848
+ {
849
+ "scripts": {
850
+ "dev": "tsx watch src/server.ts",
851
+ "build": "tsc",
852
+ "start": "node dist/server.js",
853
+ "test": "vitest",
854
+ "lint": "eslint .",
855
+ "check-modularization": "node scripts/check-modularization.mjs",
856
+ "db:migrate": "prisma migrate deploy"
857
+ }
858
+ }
859
+ ```
860
+
861
+ ---
862
+
863
+ > **This is a living document.** It should be reviewed and updated quarterly by the line manager and architecture team.
864
+ ---
865
+ ## Appendix A. Backend Cost & Pricing Reference (June 2026)
866
+
867
+ > 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.
868
+
869
+ ### Partner Benefits & Azure Cost Relief
870
+
871
+ As a **Microsoft Partner**, your organization may qualify for:
872
+
873
+ - **Microsoft Action Pack / Solutions Partner** benefits: included Azure credits, free/dev CI-CD minutes, and co-selling/support access.
874
+ - **Azure Hybrid Benefit / Reserved Instances**: reduces VM and managed-service compute cost by **20%–40%**.
875
+ - **Visual Studio Enterprise / GitHub Enterprise** bundled licensing: avoids duplicate license spend and typically lowers total cost of ownership.
876
+ - **Azure for Startups / ISV Pied Piper**: early-stage credits and advisory hours to reduce ramp-up cost.
877
+ - **FastTrack / Partner Support**: faster migrations and production-readiness reviews reduce delivery risk and project overruns.
878
+
879
+ Use this table to compare partner-inclusive pricing vs. standard list pricing when planning workloads.
880
+
881
+ ### A.1 Estimated Annual Costs by Project Size
882
+
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+ |
889
+
890
+ ### A.2 Tooling & License Costs (Annual)
891
+
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. |
904
+
905
+ ### A.3 Infrastructure Costs (Monthly)
906
+
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 |
917
+
918
+ > These are mid-2026 reference prices. Reserved/commitment terms generally reduce compute cost by 20%–40%.
919
+
920
+ ---
921
+
922
+ ### A.4 Open-Source vs Commercial Alternatives (2026)
923
+
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. |
936
+
937
+ ### A.5 Cost Optimization Tips for Backend
938
+
939
+ - **Cache first**: Redis/Valkey reduces DB load and compute spend.
940
+ - **Connection pooling**: Prevents overprovisioning DB and app replicas.
941
+ - **Queue async work**: Bull/BullMQ smooths spikes without scaling stateless workers.
942
+ - **Batch and debounce**: Fewer API calls reduce downstream costs.
943
+ - **Right-size replicas**: Start small; use HPA before adding more replicas.
944
+ - **Reserved/commitment terms**: 1-year or 3-year reserved compute often saves 20%–40%.
945
+ - **OSS first, buy when support matters**: Choose open-source defaults; move to commercial only when support contracts or compliance features justify it.
946
+
947
+ ### A.6 Sources & Methodology
948
+
949
+ - Pricing is based on **public list prices** from vendor pricing pages:
950
+ - Azure pricing: https://azure.microsoft.com/en-us/pricing/
951
+ - Azure DevOps pricing: https://azure.microsoft.com/en-us/pricing/details/devops/azure-devops-services/
952
+ - GitHub: https://github.com/pricing
953
+ - Sentry: https://sentry.io/pricing/
954
+ - Datadog: https://www.datadoghq.com/pricing/
955
+ - SonarCloud: https://www.sonarsource.com/pricing/
956
+ - Snyk: https://snyk.io/pricing/
957
+ - Redis Cloud: https://redis.io/pricing/
958
+ - Trivy: https://github.com/aquasecurity/trivy (open source)
959
+ - Microsoft Partner benefits sourced from:
960
+ - Microsoft Partner Network: https://partner.microsoft.com/
961
+ - Microsoft for Startups / ISV Pied Piper: https://startups.microsoft.com/
962
+ - Azure Hybrid Benefit: https://azure.microsoft.com/en-us/pricing/benefits/azure-hybrid-benefit/
963
+ - Visual Studio subscriptions: https://visualstudio.microsoft.com/subscriptions/
964
+ - 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.
966
+ - Infrastructure estimates use managed cloud VM/service pricing without reserved/commitment discounts by default; exact regional pricing is available from cloud provider calculators.