@synergyerp/backend-standards 1.1.0 → 1.1.2
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.
- package/BACKEND_STANDARDS.md +218 -181
- package/CICD_STANDARDS.md +355 -341
- package/README.md +0 -1
- package/package.json +1 -1
package/BACKEND_STANDARDS.md
CHANGED
|
@@ -44,21 +44,21 @@
|
|
|
44
44
|
|
|
45
45
|
### 2.1 Framework & Runtime
|
|
46
46
|
|
|
47
|
-
| Layer
|
|
48
|
-
|
|
49
|
-
| **Runtime**
|
|
50
|
-
| **Framework**
|
|
51
|
-
| **Language**
|
|
52
|
-
| **Package Manager
|
|
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
|
|
57
|
-
|
|
58
|
-
| **Database**
|
|
59
|
-
| **ORM / Query Builder** | Prisma or TypeORM
|
|
60
|
-
| **Caching**
|
|
61
|
-
| **Migrations**
|
|
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
|
|
125
|
-
|
|
126
|
-
| Module A may import from Module B's barrel (`index.ts`) only
|
|
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
|
|
129
|
-
| Every module subdirectory must contain an `index.ts` barrel
|
|
130
|
-
| `index.ts` must only re-export the module's public service(s)
|
|
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
|
|
262
|
-
|
|
263
|
-
| @eslint/js
|
|
264
|
-
| typescript-eslint
|
|
265
|
-
| eslint-plugin-import
|
|
266
|
-
| eslint-plugin-unicorn
|
|
267
|
-
| eslint-plugin-prettier
|
|
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
|
|
283
|
-
|
|
284
|
-
| Files (General)
|
|
285
|
-
| Directories
|
|
286
|
-
| Variables
|
|
287
|
-
| Booleans
|
|
288
|
-
| Functions
|
|
289
|
-
| Classes
|
|
290
|
-
| Interfaces/Types| PascalCase
|
|
291
|
-
| Enums
|
|
292
|
-
| Constants
|
|
293
|
-
| Database Tables
|
|
294
|
-
| Database Columns| snake_case
|
|
295
|
-
| Env variables
|
|
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
|
|
300
|
-
|
|
301
|
-
| const data
|
|
302
|
-
| const temp
|
|
303
|
-
| const arr
|
|
304
|
-
| const obj
|
|
305
|
-
| const result | Everything is a result
|
|
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
|
|
318
|
-
import importPlugin from
|
|
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
|
-
|
|
326
|
-
|
|
325
|
+
'boundaries/include': ['src/**/*'],
|
|
326
|
+
'boundaries/elements': [
|
|
327
327
|
{
|
|
328
|
-
mode:
|
|
329
|
-
type:
|
|
330
|
-
pattern:
|
|
328
|
+
mode: 'full',
|
|
329
|
+
type: 'modules',
|
|
330
|
+
pattern: 'src/modules/*/**',
|
|
331
331
|
},
|
|
332
332
|
{
|
|
333
|
-
mode:
|
|
334
|
-
type:
|
|
333
|
+
mode: 'full',
|
|
334
|
+
type: 'shared',
|
|
335
335
|
pattern: [
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
348
|
-
|
|
347
|
+
'boundaries/entry-point': [
|
|
348
|
+
'error',
|
|
349
349
|
{
|
|
350
|
-
default:
|
|
350
|
+
default: 'disallow',
|
|
351
351
|
rules: [
|
|
352
352
|
{
|
|
353
|
-
target: [
|
|
354
|
-
allow:
|
|
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
|
-
|
|
362
|
-
|
|
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
|
-
|
|
366
|
+
'src/modules/*/index',
|
|
367
367
|
// Allow shared utilities
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
379
|
-
|
|
378
|
+
'import/no-restricted-paths': [
|
|
379
|
+
'error',
|
|
380
380
|
{
|
|
381
381
|
zones: [
|
|
382
382
|
{
|
|
383
|
-
target:
|
|
384
|
-
from:
|
|
385
|
-
except: [
|
|
383
|
+
target: './src/modules/*/',
|
|
384
|
+
from: './src/modules/*/',
|
|
385
|
+
except: ['./index.ts'],
|
|
386
386
|
message:
|
|
387
|
-
|
|
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
|
|
401
|
-
|
|
402
|
-
| `boundaries/entry-point`
|
|
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.)
|
|
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": [
|
|
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
|
|
471
|
-
|
|
472
|
-
| `page`
|
|
473
|
-
| `limit`
|
|
474
|
-
| `sortBy`
|
|
475
|
-
| `order`
|
|
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
|
|
551
|
-
|
|
552
|
-
| General API
|
|
553
|
-
| Auth endpoints (login, register, MFA) | 15 min | 5 per IP
|
|
554
|
-
| Password reset / email verification
|
|
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,
|
|
567
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
561
568
|
max: 100,
|
|
562
569
|
standardHeaders: true,
|
|
563
570
|
legacyHeaders: false,
|
|
564
|
-
message: {
|
|
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(
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
|
629
|
-
|
|
630
|
-
| L1: Editor
|
|
631
|
-
| L2: Pre-commit
|
|
632
|
-
| L3: CI Pipeline | GitHub Actions
|
|
633
|
-
| L4: CD Pipeline | Smoke Tests
|
|
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) {
|
|
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)
|
|
714
|
-
|
|
715
|
-
|
|
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(
|
|
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(
|
|
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
|
|
748
|
-
|
|
749
|
-
| `git commit --no-verify`
|
|
750
|
-
| Tampering with `.husky/`
|
|
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
|
|
884
|
-
|
|
885
|
-
| Small (MVP / closed beta)
|
|
886
|
-
| Medium (production SaaS)
|
|
887
|
-
| Large (enterprise / multi-tenant)
|
|
888
|
-
| Enterprise (regulated / high scale) | 6+
|
|
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
|
|
893
|
-
|
|
894
|
-
| GitHub Enterprise / Teams
|
|
895
|
-
| Sentry (Error Tracking)
|
|
896
|
-
| Datadog / New Relic / Azure Monitor
|
|
897
|
-
| SonarCloud / SonarQube
|
|
898
|
-
| Snyk / Trivy / Dependabot
|
|
899
|
-
| Auth0 / WorkOS / Clerk
|
|
900
|
-
| Redis Cloud (optional)
|
|
901
|
-
| Managed Postgres (Azure/RDS/Cloud SQL) | 2 vCPU / 8 GB + storage
|
|
902
|
-
| Azure Key Vault / AWS Secrets Manager
|
|
903
|
-
| Object Storage (S3 / Blob)
|
|
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
|
|
908
|
-
|
|
909
|
-
| Dev (1 replica, no HA)
|
|
910
|
-
| Staging (2 replicas)
|
|
911
|
-
| Production (min 3 replicas, autoscale) | 2 vCPU / 4 GB RAM + LB
|
|
912
|
-
| Managed PostgreSQL
|
|
913
|
-
| Managed Redis / Valkey
|
|
914
|
-
| Object Storage
|
|
915
|
-
| SSL / ingress certs
|
|
916
|
-
| DNS / domain
|
|
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
|
|
925
|
-
|
|
926
|
-
| CI/CD Platform
|
|
927
|
-
| Auth (optional)
|
|
928
|
-
| Managed Redis
|
|
929
|
-
| API Gateway / BFF
|
|
930
|
-
| Logging
|
|
931
|
-
| Monitoring/APM
|
|
932
|
-
| Static Analysis
|
|
933
|
-
| Dependency Scanning
|
|
934
|
-
| Secret Storage
|
|
935
|
-
| Object / File Storage | Blob / S3 managed
|
|
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
|
-
|
|
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.
|