cqrs-boilerplate-code 1.0.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.
Files changed (36) hide show
  1. package/.prettierrc +0 -0
  2. package/README.md +2 -0
  3. package/nest-cli.json +8 -0
  4. package/package.json +67 -0
  5. package/src/common-infra/common-infra.module.ts +9 -0
  6. package/src/common-infra/database/database.module.ts +15 -0
  7. package/src/common-infra/database/typeorm.config.ts +18 -0
  8. package/src/core-common/constant/app.constant.ts +2 -0
  9. package/src/core-common/core-common.module.ts +9 -0
  10. package/src/core-common/error/custom-error/already-exists.error.ts +17 -0
  11. package/src/core-common/error/custom-error/bad-request.error.ts +8 -0
  12. package/src/core-common/error/custom-error/conflict.error.ts +17 -0
  13. package/src/core-common/error/custom-error/custom-validation-error.ts +9 -0
  14. package/src/core-common/error/custom-error/forbidden.error.ts +8 -0
  15. package/src/core-common/error/custom-error/internal-server.error.ts +11 -0
  16. package/src/core-common/error/custom-error/not-found.error.ts +8 -0
  17. package/src/core-common/error/custom-error/service-unavailable.error.ts +8 -0
  18. package/src/core-common/error/custom-error/unauthorized.error.ts +8 -0
  19. package/src/core-common/error/custom-error/unprocess-entity.error.ts +8 -0
  20. package/src/core-common/error/custom-error/validation.error.ts +8 -0
  21. package/src/core-common/error/generic.error.ts +31 -0
  22. package/src/core-common/error/index.ts +10 -0
  23. package/src/core-common/logger/index.ts +175 -0
  24. package/src/core-common/logger/logger.module.ts +8 -0
  25. package/src/core-common/logger/logger.provider.ts +7 -0
  26. package/src/core-common/response-model/generic-error-response.model.ts +44 -0
  27. package/src/core-common/response-model/generic-success-response.model.ts +25 -0
  28. package/src/core-common/result-model/result.ts +38 -0
  29. package/src/middleware/async-storage.middleware.ts +29 -0
  30. package/src/middleware/filter/global-exeception.filter.ts +117 -0
  31. package/src/middleware/index.ts +2 -0
  32. package/src/middleware/interceptor/response-handler.ts +69 -0
  33. package/src/middleware/platform-auth.middleware.ts +13 -0
  34. package/src/middleware/utils/http-response.formatter.ts +126 -0
  35. package/tsconfig.build.json +10 -0
  36. package/tsconfig.json +58 -0
package/.prettierrc ADDED
File without changes
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # cqrs-pattern-boilerplate-code
2
+ cqrs-pattern-boilerplate-code
package/nest-cli.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true
7
+ }
8
+ }
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "cqrs-boilerplate-code",
3
+ "version": "1.0.0",
4
+ "description": "AI Platform Service API Gateway",
5
+ "private": false,
6
+ "license": "UNLICENSED",
7
+ "author": "ankitdetroja",
8
+ "scripts": {
9
+ "clean": "rimraf dist coverage",
10
+ "build": "nest build",
11
+ "start": "nest start",
12
+ "start:dev": "nest start --watch",
13
+ "start:prod": "node dist/main.js",
14
+ "start:debug": "nest start --debug --watch",
15
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
16
+ "lint": "eslint \"{src,tests}/**/*.ts\" --fix"
17
+ },
18
+ "dependencies": {
19
+ "@nestjs/common": "^11.1.3",
20
+ "@nestjs/config": "^4.0.2",
21
+ "@nestjs/core": "^11.1.3",
22
+ "@nestjs/cqrs": "^11.0.3",
23
+ "@nestjs/platform-express": "^11.1.3",
24
+ "@nestjs/swagger": "^11.2.0",
25
+ "@nestjs/typeorm": "11.0.0",
26
+ "axios": "1.13.2",
27
+ "axios-retry": "^4.4.0",
28
+ "bottleneck": "^2.19.5",
29
+ "class-transformer": "^0.5.1",
30
+ "class-validator": "^0.14.1",
31
+ "dotenv": "^16.4.5",
32
+ "jsonwebtoken": "^9.0.2",
33
+ "module-alias": "^2.2.3",
34
+ "nestjs-request-context": "^4.0.0",
35
+ "pg": "8.16.3",
36
+ "reflect-metadata": "^0.2.2",
37
+ "typeorm": "0.3.28",
38
+ "uuid": "^10.0.0",
39
+ "winston": "3.19.0"
40
+ },
41
+ "devDependencies": {
42
+ "@faker-js/faker": "^8.4.1",
43
+ "@golevelup/ts-jest": "^0.5.6",
44
+ "@nestjs/cli": "^10.3.2",
45
+ "@nestjs/schematics": "^10.1.1",
46
+ "@nestjs/testing": "^11.1.3",
47
+ "@types/jest": "^29.5.12",
48
+ "@types/jsonwebtoken": "^9.0.10",
49
+ "@types/lodash": "^4.17.5",
50
+ "@types/multer": "^1.4.11",
51
+ "@types/node": "^20.14.2",
52
+ "@types/supertest": "^6.0.2",
53
+ "@types/uuid": "^9.0.8",
54
+ "jest": "^29.7.0",
55
+ "jest-mock-extended": "^3.0.7",
56
+ "prettier": "^3.3.2",
57
+ "rimraf": "^5.0.5",
58
+ "source-map-support": "^0.5.21",
59
+ "supertest": "^7.0.0",
60
+ "ts-jest": "^29.1.4",
61
+ "ts-loader": "^9.5.1",
62
+ "ts-mockito": "^2.6.1",
63
+ "ts-node": "^10.9.2",
64
+ "tsc-files": "^1.1.4",
65
+ "typescript": "^5.4.5"
66
+ }
67
+ }
@@ -0,0 +1,9 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { DBModule } from "./database/database.module";
3
+
4
+ @Module({
5
+ imports: [DBModule],
6
+ providers: [],
7
+ exports: [],
8
+ })
9
+ export class CommonInfraModule {}
@@ -0,0 +1,15 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { TypeOrmModule } from "@nestjs/typeorm";
3
+ import { ConfigModule, ConfigService } from "@nestjs/config";
4
+ import { typeOrmConfig } from "./typeorm.config";
5
+
6
+ @Module({
7
+ imports: [
8
+ TypeOrmModule.forRootAsync({
9
+ imports: [ConfigModule],
10
+ inject: [ConfigService],
11
+ useFactory: typeOrmConfig,
12
+ }),
13
+ ],
14
+ })
15
+ export class DBModule {}
@@ -0,0 +1,18 @@
1
+ import { TypeOrmModuleOptions } from "@nestjs/typeorm";
2
+ import { ConfigService } from "@nestjs/config";
3
+
4
+ export const typeOrmConfig = (
5
+ configService: ConfigService,
6
+ ): TypeOrmModuleOptions => ({
7
+ type: "postgres",
8
+ host: configService.get<string>("DB_HOST"),
9
+ port: configService.get<number>("DB_PORT"),
10
+ username: configService.get<string>("DB_USERNAME"),
11
+ password: configService.get<string>("DB_PASSWORD"),
12
+ database: configService.get<string>("DB_NAME"),
13
+ autoLoadEntities: true,
14
+ synchronize: configService.get<string>("NODE_ENV") !== "production",
15
+ migrations: ["dist/migrations/*.js"],
16
+ migrationsRun: true,
17
+ logging: configService.get<string>("NODE_ENV") !== "production",
18
+ });
@@ -0,0 +1,2 @@
1
+ export const PORT = 3001;
2
+ export const BASE_URL = '/api/retail'
@@ -0,0 +1,9 @@
1
+ import { Module } from "@nestjs/common";
2
+
3
+ @Module({
4
+ providers: [
5
+ ],
6
+ exports: [
7
+ ],
8
+ })
9
+ export class CoreCommonModule {}
@@ -0,0 +1,17 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ /**
5
+ * Custom error class for representing the scenario where an entity already exists/Conflict with existing.
6
+ * Extends the GenericError class.
7
+ */
8
+ export class AlreadyExistsError extends GenericError {
9
+ /**
10
+ * Constructor for the AlreadyExistsError class.
11
+ * @param code - Short code associated with the error.
12
+ * @param message - Detailed error information.
13
+ */
14
+ constructor(code: string, message: string) {
15
+ super(code, message, HttpStatus.CONFLICT);
16
+ }
17
+ }
@@ -0,0 +1,8 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ export class BadRequestError extends GenericError {
5
+ constructor(code: string, message: string) {
6
+ super(code, message, HttpStatus.BAD_REQUEST);
7
+ }
8
+ }
@@ -0,0 +1,17 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ /**
5
+ * Custom error class for representing conflict errors.
6
+ * Extends the GenericError class.
7
+ */
8
+ export class ConflictError extends GenericError {
9
+ /**
10
+ * Constructor for the ConflictError class.
11
+ * @param code - Short code associated with the error.
12
+ * @param message - Detailed error information.
13
+ */
14
+ constructor(code: string, message: string) {
15
+ super(code, message, HttpStatus.CONFLICT);
16
+ }
17
+ }
@@ -0,0 +1,9 @@
1
+ import { BadRequestError } from "./bad-request.error";
2
+
3
+ export class CustomValidationError extends BadRequestError {
4
+ constructor(
5
+ public validationErrors: any
6
+ ) {
7
+ super("ValidationError", "There are issues found in the input provided.");
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ export class ForbiddenError extends GenericError {
5
+ constructor(code: string, message: string) {
6
+ super(code, message, HttpStatus.FORBIDDEN);
7
+ }
8
+ }
@@ -0,0 +1,11 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ export class InternalServerError extends GenericError {
5
+ constructor(
6
+ code = "INTERNAL_SERVER_ERROR",
7
+ message = "An unexpected error occurred",
8
+ ) {
9
+ super(code, message, HttpStatus.INTERNAL_SERVER_ERROR);
10
+ }
11
+ }
@@ -0,0 +1,8 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ export class NotFoundError extends GenericError {
5
+ constructor(code: string, message: string) {
6
+ super(code, message, HttpStatus.NOT_FOUND);
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ export class ServiceUnavailableError extends GenericError {
5
+ constructor(code: string, message: string) {
6
+ super(code, message, HttpStatus.SERVICE_UNAVAILABLE);
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ export class UnauthorizedError extends GenericError {
5
+ constructor(code: string, message: string) {
6
+ super(code, message, HttpStatus.UNAUTHORIZED);
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ export class UnprocessableEntityError extends GenericError {
5
+ constructor(code: string, message: string) {
6
+ super(code, message, HttpStatus.UNPROCESSABLE_ENTITY);
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { GenericError } from "../generic.error";
3
+
4
+ export class ValidationError extends GenericError {
5
+ constructor(code: string, message: string) {
6
+ super(code, message, HttpStatus.UNPROCESSABLE_ENTITY);
7
+ }
8
+ }
@@ -0,0 +1,31 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { ApiProperty } from "@nestjs/swagger";
3
+
4
+ /**
5
+ * Custom error class which needs to be returned from the lower layers to the higher calling layers
6
+ */
7
+ export class GenericError {
8
+ @ApiProperty({ description: "Short error code" })
9
+ public code: string;
10
+
11
+ @ApiProperty({ description: "Detailed error description" })
12
+ public message: string;
13
+
14
+ @ApiProperty({ description: "Detailed error description" })
15
+ public statusCode: HttpStatus;
16
+
17
+ /**
18
+ * Default constructor
19
+ * @param code - Short error code e.g. "InvaliOperation", "ItemNotFound" etc
20
+ * @param message - Detailed error message
21
+ */
22
+ constructor(
23
+ code: string,
24
+ message: string,
25
+ statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR,
26
+ ) {
27
+ this.code = code;
28
+ this.message = message;
29
+ this.statusCode = statusCode;
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ export * from "./generic.error";
2
+ export * from "./custom-error/bad-request.error";
3
+ export * from "./custom-error/unauthorized.error";
4
+ export * from "./custom-error/forbidden.error";
5
+ export * from "./custom-error/not-found.error";
6
+ export * from "./custom-error/conflict.error";
7
+ export * from "./custom-error/already-exists.error";
8
+ export * from "./custom-error/validation.error";
9
+ export * from "./custom-error/unprocess-entity.error";
10
+ export * from "./custom-error/service-unavailable.error";
@@ -0,0 +1,175 @@
1
+ import winston, { Logger } from "winston";
2
+
3
+ /* ---------------------------------- Types --------------------------------- */
4
+
5
+ enum SeverityText {
6
+ INFO = "Information",
7
+ DEBUG = "Debug",
8
+ WARNING = "Warning",
9
+ ERROR = "Error",
10
+ }
11
+
12
+ type LogMetadata = {
13
+ ClassName?: string;
14
+ MethodName?: string;
15
+ };
16
+
17
+ export type OrgContext = {
18
+ orgId?: string;
19
+ orgFid?: string;
20
+ userId?: string;
21
+ };
22
+
23
+ /* --------------------------------- Service -------------------------------- */
24
+
25
+ export class LoggerService {
26
+ private readonly logger: Logger;
27
+ private readonly deploymentEnv: string;
28
+ private readonly hostImageVersion: string;
29
+ private readonly otelAgentHost: string;
30
+
31
+ constructor(logLevel: string = "debug") {
32
+ this.deploymentEnv = "rls-dev";
33
+ this.hostImageVersion = process.env.SERVICE_VERSION ?? "202501.1";
34
+ this.otelAgentHost = process.env.OTEL_AGENT_HOST ?? "10.0.0.1";
35
+ this.logger = this.createLogger(logLevel);
36
+ this.overrideConsole();
37
+ }
38
+
39
+ /* ------------------------------- Public API ------------------------------- */
40
+
41
+ log(message: string, metadata?: LogMetadata) {
42
+ this.write("info", SeverityText.INFO, message, metadata);
43
+ }
44
+
45
+ debug(message: string, metadata?: LogMetadata) {
46
+ this.write("debug", SeverityText.DEBUG, message, metadata);
47
+ }
48
+
49
+ warn(message: string, metadata?: LogMetadata) {
50
+ this.write("warn", SeverityText.WARNING, message, metadata);
51
+ }
52
+
53
+ error(message: string, metadata?: LogMetadata, error?: Error) {
54
+ this.write("error", SeverityText.ERROR, message, metadata, error);
55
+ }
56
+
57
+ /* ------------------------------ Logger Core ------------------------------ */
58
+
59
+ private write(
60
+ level: keyof Logger,
61
+ severity: SeverityText,
62
+ message: string,
63
+ metadata: LogMetadata = {},
64
+ error?: Error,
65
+ ) {
66
+ const body = {
67
+ SeverityText: severity,
68
+ ...metadata,
69
+ ...(error && {
70
+ ErrorMessage: error.message,
71
+ StackTrace: error.stack,
72
+ }),
73
+ };
74
+
75
+ this.logger[level](this.redactSecrets(message), body);
76
+ }
77
+
78
+ private createLogger(level: string): Logger {
79
+ return winston.createLogger({
80
+ level,
81
+ levels: winston.config.npm.levels,
82
+ format: winston.format.combine(
83
+ winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
84
+ winston.format.printf(({ level, message, timestamp, ...meta }) =>
85
+ JSON.stringify({
86
+ message: this.safeStringify(message),
87
+ attributes: this.buildAttributes(level),
88
+ timestamp: timestamp,
89
+ ...this.cleanMeta(meta),
90
+ }),
91
+ ),
92
+ ),
93
+ transports: [new winston.transports.Console()],
94
+ });
95
+ }
96
+
97
+ /* ------------------------------- Context -------------------------------- */
98
+
99
+ private buildAttributes(level: string) {
100
+ return {
101
+ "service.image.version": this.hostImageVersion,
102
+ "deployment.environment": this.deploymentEnv,
103
+ "otel.agent.host": this.otelAgentHost,
104
+ "service.log.level": level,
105
+ };
106
+ }
107
+
108
+ /* ------------------------------- Helpers -------------------------------- */
109
+
110
+ private cleanMeta(meta: Record<string, unknown>) {
111
+ return Object.fromEntries(
112
+ Object.entries(meta).filter(
113
+ ([_, value]) => value !== undefined && value !== "None",
114
+ ),
115
+ );
116
+ }
117
+
118
+ private safeStringify(input: unknown): string {
119
+ try {
120
+ return typeof input === "string"
121
+ ? input
122
+ : JSON.stringify(input, this.circularReplacer());
123
+ } catch {
124
+ return "[Unserializable Object]";
125
+ }
126
+ }
127
+
128
+ private circularReplacer() {
129
+ const seen = new WeakSet();
130
+ return (_: string, value: any) => {
131
+ if (typeof value === "object" && value !== null) {
132
+ if (seen.has(value)) return "[Circular]";
133
+ seen.add(value);
134
+ }
135
+ return value;
136
+ };
137
+ }
138
+
139
+ /* ----------------------------- Redaction -------------------------------- */
140
+
141
+ private redactSecrets(message: string): string {
142
+ const patterns = [
143
+ {
144
+ name: "JWT",
145
+ regex: /eyJ[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+/g,
146
+ visible: 10,
147
+ },
148
+ {
149
+ name: "BEARER",
150
+ regex: /Bearer\s+eyJ[A-Za-z0-9\-._~+/]+=*/gi,
151
+ visible: 15,
152
+ },
153
+ { name: "API_KEY", regex: /\b[A-Za-z0-9]{32,}\b/g, visible: 8 },
154
+ { name: "AWS_SECRET", regex: /AKIA[0-9A-Z]{16}/g, visible: 8 },
155
+ ];
156
+
157
+ return patterns.reduce((msg, { regex, visible, name }) => {
158
+ return msg.replace(
159
+ regex,
160
+ (m) => `${m.slice(0, visible)}...[REDACTED_${name}]`,
161
+ );
162
+ }, message);
163
+ }
164
+
165
+ /* --------------------------- Console Override ---------------------------- */
166
+
167
+ private overrideConsole() {
168
+ const blocked = ["NodeSDK", "AuthToken"];
169
+ console.log = (...args: unknown[]) => {
170
+ const msg = args.map(String).join(" ");
171
+ if (blocked.some((k) => msg.includes(k))) return;
172
+ this.log(msg);
173
+ };
174
+ }
175
+ }
@@ -0,0 +1,8 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { LoggerProvider } from "./logger.provider";
3
+
4
+ @Module({
5
+ providers: [LoggerProvider],
6
+ exports: [LoggerProvider],
7
+ })
8
+ export class LoggerModule {}
@@ -0,0 +1,7 @@
1
+ import { Provider } from "@nestjs/common";
2
+ import { LoggerService } from ".";
3
+
4
+ export const LoggerProvider: Provider = {
5
+ provide: LoggerService,
6
+ useClass: LoggerService,
7
+ };
@@ -0,0 +1,44 @@
1
+ import { Result } from "../result-model/result";
2
+
3
+ /**
4
+ * Standard error response to be returned to the caller of the API.
5
+ * This class represents the structure of an error response returned by the API
6
+ * in case of failures.
7
+ */
8
+
9
+ export class ErrorDisplay {
10
+ code: string;
11
+ message: string;
12
+ validationError?: any;
13
+ }
14
+ export class GenericErrorResponse {
15
+ statusCode: number;
16
+ success: boolean = false;
17
+ error: ErrorDisplay;
18
+ timestamp: string;
19
+ data: any;
20
+
21
+ constructor() {
22
+ this.statusCode = 500; // Default value
23
+ this.success = false;
24
+ this.timestamp = new Date().toISOString(); // Default value
25
+ }
26
+ /**
27
+ * Sets the properties from the Result object.
28
+ * @param result - Result object instance returned from the controller method.
29
+ * @param statusCode - HTTP status code.
30
+ */
31
+ initialize(
32
+ result: Result<any>,
33
+ statusCode: number,
34
+ ) {
35
+ this.statusCode = statusCode;
36
+ this.error = {
37
+ code: result.error?.code || "ERROR",
38
+ message: result.error?.message || "An error occurred.",
39
+ };
40
+ this.data = result.data
41
+ this.success = result.success || false;
42
+ this.timestamp = result.timestamp || new Date().toISOString();
43
+ }
44
+ }
@@ -0,0 +1,25 @@
1
+ import { Result } from "../result-model/result";
2
+
3
+ /**
4
+ * Standard response object to be returned to the caller of the API.
5
+ * This class represents the structure of a success response returned by the API.
6
+ * @typeparam T - The type of data included in the response.
7
+ */
8
+ export class GenericSuccessResponse<T> {
9
+ statusCode: number;
10
+ success: boolean;
11
+ data: T;
12
+ timestamp: string;
13
+
14
+ /**
15
+ * Sets the properties from the Result object.
16
+ * @param result - Result object instance returned from the controller method.
17
+ * @param statusCode - HTTP status code.
18
+ */
19
+ initialize(result: Result<T>, statusCode: number) {
20
+ this.statusCode = statusCode;
21
+ this.success = result.success;
22
+ this.data = result.data;
23
+ this.timestamp = new Date().toISOString();
24
+ }
25
+ }
@@ -0,0 +1,38 @@
1
+ import { GenericError } from "../error/generic.error";
2
+
3
+ export class Result<T> {
4
+ success: boolean;
5
+ data: T;
6
+ error: GenericError;
7
+ timestamp: string;
8
+
9
+ public static success<T>(data: T): Result<T> {
10
+ const result = new Result<T>();
11
+ result.success = true;
12
+ result.data = data;
13
+ result.timestamp = new Date().toISOString();
14
+ return result;
15
+ }
16
+
17
+ public static failed<T>(error: GenericError, data?: T): Result<T> {
18
+ const result = new Result<T>();
19
+ result.success = false;
20
+ result.timestamp = new Date().toISOString();
21
+ result.error = error;
22
+ if (data) {
23
+ result.data = data;
24
+ }
25
+ return result;
26
+ }
27
+
28
+ public static throwError<T>(error: GenericError, data?: T): Result<T> {
29
+ const result = new Result<T>();
30
+ result.success = false;
31
+ result.timestamp = new Date().toISOString();
32
+ result.error = error;
33
+ if (data) {
34
+ result.data = data;
35
+ }
36
+ throw result;
37
+ }
38
+ }
@@ -0,0 +1,29 @@
1
+ // async-storage.middleware.ts
2
+ import { Injectable, NestMiddleware } from "@nestjs/common";
3
+ import { AsyncLocalStorage } from "async_hooks";
4
+ import { Request, Response, NextFunction } from "express";
5
+
6
+ @Injectable()
7
+ export class AsyncStorageMiddleware implements NestMiddleware {
8
+ private static storage = new AsyncLocalStorage<Map<string, any>>();
9
+
10
+ static get(key: string): any {
11
+ const store = AsyncStorageMiddleware.storage.getStore();
12
+ return store?.get(key);
13
+ }
14
+
15
+ static set(key: string, value: any): void {
16
+ const store = AsyncStorageMiddleware.storage.getStore();
17
+ if (store) {
18
+ store.set(key, value);
19
+ }
20
+ }
21
+
22
+ use(req: Request, _res: Response, next: NextFunction) {
23
+ const store = new Map<string, any>();
24
+ store.set("request", req);
25
+ AsyncStorageMiddleware.storage.run(store, () => {
26
+ next();
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,117 @@
1
+ import {
2
+ ArgumentsHost,
3
+ Catch,
4
+ ExceptionFilter,
5
+ HttpException,
6
+ HttpStatus,
7
+ } from "@nestjs/common";
8
+ import { Response } from "express";
9
+ import { GenericError } from "../../core-common/error";
10
+ import { CustomValidationError } from "../../core-common/error/custom-error/custom-validation-error";
11
+ import { Result } from "../../core-common/result-model/result";
12
+
13
+ /**
14
+ * Handles and processes exceptions, providing standardized error responses.
15
+ * This global exception handler can be used to catch and respond to exceptions thrown
16
+ * during HTTP requests and format responses accordingly.
17
+ */
18
+
19
+ @Catch()
20
+ export class GlobalExceptionHandler implements ExceptionFilter {
21
+ catch(exception: unknown, host: ArgumentsHost) {
22
+ const response = host.switchToHttp().getResponse<Response>();
23
+
24
+ /* 1️⃣ Domain Result (highest priority) */
25
+ if (exception instanceof Result) {
26
+ const statusCode =
27
+ exception.error?.statusCode ?? HttpStatus.INTERNAL_SERVER_ERROR;
28
+
29
+ const payload: any = {
30
+ statusCode,
31
+ success: false,
32
+ error: {
33
+ code: exception.error?.code ?? "ERROR",
34
+ message: exception.error?.message ?? "An error occurred",
35
+ },
36
+ timestamp: exception.timestamp ?? new Date().toISOString(),
37
+ };
38
+
39
+ if (exception.data !== undefined && exception.data !== null) {
40
+ payload.data = exception.data;
41
+ }
42
+
43
+ return response.status(statusCode).json(payload);
44
+ }
45
+
46
+ /* 2️⃣ Custom domain errors */
47
+ if (exception instanceof GenericError) {
48
+ return response.status(exception.statusCode).json({
49
+ statusCode: exception.statusCode,
50
+ success: false,
51
+ error: {
52
+ code: exception.code,
53
+ message: exception.message,
54
+ },
55
+ timestamp: new Date().toISOString(),
56
+ });
57
+ }
58
+
59
+ /* 3️⃣ NestJS HTTP exceptions */
60
+ if (exception instanceof HttpException) {
61
+ const status = exception.getStatus();
62
+ const res = exception.getResponse();
63
+
64
+ const isValidationPipeError = exception.stack?.includes("ValidationPipe");
65
+
66
+ if (isValidationPipeError) {
67
+ const validationErrors = new CustomValidationError(res);
68
+ return response.status(validationErrors.statusCode).json({
69
+ success: false,
70
+ error: {
71
+ code: validationErrors.code,
72
+ message: validationErrors.message,
73
+ validationError: validationErrors.validationErrors,
74
+ },
75
+ timestamp: new Date().toISOString(),
76
+ });
77
+ }
78
+
79
+ return response.status(status).json({
80
+ statusCode: status,
81
+ success: false,
82
+ error: {
83
+ code: "HTTP_EXCEPTION",
84
+ message:
85
+ typeof res === "string"
86
+ ? res
87
+ : ((res as any).message ?? exception.message),
88
+ },
89
+ timestamp: new Date().toISOString(),
90
+ });
91
+ }
92
+
93
+ /* 4️⃣ Native JS errors (TypeError, Error, etc.) */
94
+ if (exception instanceof Error) {
95
+ return response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
96
+ statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
97
+ success: false,
98
+ error: {
99
+ code: exception.name,
100
+ message: exception.message,
101
+ },
102
+ timestamp: new Date().toISOString(),
103
+ });
104
+ }
105
+
106
+ /* 5️⃣ Truly unknown (non-Error throwables) */
107
+ return response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
108
+ statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
109
+ success: false,
110
+ error: {
111
+ code: "UNKNOWN_ERROR",
112
+ message: "Internal server error",
113
+ },
114
+ timestamp: new Date().toISOString(),
115
+ });
116
+ }
117
+ }
@@ -0,0 +1,2 @@
1
+ export * from './async-storage.middleware'
2
+ export * from './platform-auth.middleware'
@@ -0,0 +1,69 @@
1
+ import {
2
+ CallHandler,
3
+ ExecutionContext,
4
+ Injectable,
5
+ NestInterceptor,
6
+ } from "@nestjs/common";
7
+ import { ServerResponse } from "http";
8
+ import { Observable } from "rxjs";
9
+ import { GenericErrorResponse } from "../../core-common/response-model/generic-error-response.model";
10
+ import { GenericSuccessResponse } from "../../core-common/response-model/generic-success-response.model";
11
+ import { Result } from "../../core-common/result-model/result";
12
+ import { HttpResponseFormatter } from "../utils/http-response.formatter";
13
+ import { LoggerService } from "@core-common/logger";
14
+ /**
15
+ * Intercepts the response and returns a standard ApiReponse object
16
+ */
17
+ @Injectable()
18
+ export class ResponseHandler implements NestInterceptor {
19
+ constructor(
20
+ private readonly logger: LoggerService
21
+ ) {}
22
+
23
+ intercept(exContext: ExecutionContext, next: CallHandler): Observable<any> {
24
+ return new Observable((subscriber) => {
25
+ next.handle().subscribe({
26
+ next: (data) => {
27
+ const mapped = this.handleResponse(exContext, data);
28
+ subscriber.next(mapped);
29
+ },
30
+ error: (err) => {
31
+ this.logger.error('Error in ResponseHandler Interceptor', err);
32
+ subscriber.error(err);
33
+ },
34
+ complete: () => {
35
+ subscriber.complete();
36
+ },
37
+ });
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Checks the response content and appropriately sets the status code and wraps the return value in a ApiErroReponse or a
43
+ * ApiSuccessReponse
44
+ * @param excecutionContext - Instance of the executionContext object
45
+ * @param result Data returned by the controller method
46
+ * @returns - Instance of ApiSuccessReponse or ApiErrorReponse
47
+ */
48
+ private handleResponse(
49
+ excecutionContext: ExecutionContext,
50
+ result: Result<any>,
51
+ ): GenericSuccessResponse<any> | GenericErrorResponse {
52
+ let serverResponse = excecutionContext
53
+ .switchToHttp()
54
+ .getResponse<ServerResponse>();
55
+
56
+ const apiReponse: any = new HttpResponseFormatter().getStandardApiResponse(
57
+ serverResponse.statusCode,
58
+ result,
59
+ );
60
+ serverResponse.statusCode = apiReponse.statusCode
61
+ ? apiReponse.statusCode
62
+ : apiReponse?.error?.statusCode;
63
+ serverResponse[`statusCode`] = serverResponse.statusCode;
64
+ if (apiReponse?.statusCode) {
65
+ delete apiReponse?.error?.statusCode;
66
+ }
67
+ return apiReponse;
68
+ }
69
+ }
@@ -0,0 +1,13 @@
1
+ import { Injectable, NestMiddleware } from "@nestjs/common";
2
+ import { Request, Response } from "express";
3
+
4
+ @Injectable()
5
+ export class AuthMiddleware implements NestMiddleware<Request, Response> {
6
+ use(req: Request, _res: Response, next: () => void): void {
7
+ const authHeader = req.headers["authorization"];
8
+ // Implement your authentication logic here
9
+ // For example, validate JWT token from authHeader
10
+ console.log("Auth Middleware executed", authHeader);
11
+ next();
12
+ }
13
+ }
@@ -0,0 +1,126 @@
1
+ import { HttpStatus } from "@nestjs/common";
2
+ import { AlreadyExistsError } from "../../core-common/error/custom-error/already-exists.error";
3
+ import { GenericError } from "../../core-common/error/generic.error";
4
+ import { GenericErrorResponse } from "../../core-common/response-model/generic-error-response.model";
5
+ import { GenericSuccessResponse } from "../../core-common/response-model/generic-success-response.model";
6
+ import { Result } from "../../core-common/result-model/result";
7
+ import { BadRequestError, ConflictError, ForbiddenError, NotFoundError, ServiceUnavailableError, UnauthorizedError, UnprocessableEntityError, ValidationError } from "../../core-common/error";
8
+ import { InternalServerError } from "../../core-common/error/custom-error/internal-server.error";
9
+
10
+ /**
11
+ * Helps in returning a appropriate and standard Response format based on success or error scenario
12
+ */
13
+ export class HttpResponseFormatter {
14
+ constructor() {}
15
+ /**
16
+ * Returns a standard formatted ApiSuccesReponse or ApiErrorResponse depending on the response data
17
+ * @param statusCode - Http Status code
18
+ * @param responseData - Response value from the api controller method
19
+ */
20
+ public getStandardApiResponse(
21
+ statusCode: number,
22
+ responseData: any,
23
+ ): GenericSuccessResponse<any> | GenericErrorResponse {
24
+ if (this.isSuccessfulResult(responseData)) {
25
+ return this.getSuccessResponse(responseData, statusCode);
26
+ }
27
+
28
+ if (this.isFailedResult(responseData)) {
29
+ return this.getFailureResponse(responseData);
30
+ }
31
+
32
+ return responseData;
33
+ }
34
+
35
+ /**
36
+ *
37
+ * @param responseData Initializes and returns the success api response
38
+ * @param statusCode - Http Status code
39
+ * @returns - instance of ApiSuccessResponse object
40
+ */
41
+ private getSuccessResponse(
42
+ responseData: any,
43
+ statusCode: number,
44
+ ): GenericSuccessResponse<any> {
45
+ const apiSuccessResponse = new GenericSuccessResponse();
46
+ apiSuccessResponse.initialize(responseData, statusCode);
47
+ return apiSuccessResponse;
48
+ }
49
+
50
+ /**
51
+ * Checks if the the data is of type Result and is successful
52
+ * @param responseData - response data
53
+ * @returns - boolean
54
+ */
55
+ private isSuccessfulResult(responseData: any): boolean {
56
+ return responseData instanceof Result && responseData.success;
57
+ }
58
+
59
+ /**
60
+ * Checks if the the data is of type Result and is successful
61
+ * @param responseData - response data
62
+ * @returns - boolean
63
+ */
64
+ private isFailedResult(responseData: any): boolean {
65
+ return responseData instanceof Result && !responseData.success;
66
+ }
67
+
68
+ /**
69
+ * Gets the failure response based on the result contents
70
+ * @param result - Result object returned from the controller method
71
+ */
72
+ private getFailureResponse(result: Result<any>) {
73
+ const statusCode = this.getStatusCode(result.error);
74
+ const apiErrorResponse = new GenericErrorResponse();
75
+ apiErrorResponse.initialize(result, statusCode);
76
+ return apiErrorResponse;
77
+ }
78
+
79
+ private getStatusCode(error: GenericError): number {
80
+ if (error instanceof BadRequestError) {
81
+ return HttpStatus.BAD_REQUEST;
82
+ }
83
+
84
+ if (error instanceof UnauthorizedError) {
85
+ return HttpStatus.UNAUTHORIZED;
86
+ }
87
+
88
+ if (error instanceof ForbiddenError) {
89
+ return HttpStatus.FORBIDDEN;
90
+ }
91
+
92
+ if (error instanceof NotFoundError) {
93
+ return HttpStatus.NOT_FOUND;
94
+ }
95
+
96
+ if (error instanceof AlreadyExistsError) {
97
+ return HttpStatus.CONFLICT;
98
+ }
99
+
100
+ if (error instanceof ConflictError) {
101
+ return HttpStatus.CONFLICT;
102
+ }
103
+
104
+ if (error instanceof ValidationError) {
105
+ return HttpStatus.UNPROCESSABLE_ENTITY;
106
+ }
107
+
108
+ if (error instanceof UnprocessableEntityError) {
109
+ return HttpStatus.UNPROCESSABLE_ENTITY;
110
+ }
111
+
112
+ if (error instanceof ServiceUnavailableError) {
113
+ return HttpStatus.SERVICE_UNAVAILABLE;
114
+ }
115
+
116
+ if (error instanceof InternalServerError) {
117
+ return HttpStatus.INTERNAL_SERVER_ERROR;
118
+ }
119
+
120
+ if (error instanceof GenericError) {
121
+ return error.statusCode;
122
+ }
123
+
124
+ return HttpStatus.INTERNAL_SERVER_ERROR;
125
+ }
126
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": [
4
+ "node_modules",
5
+ "dist",
6
+ "coverage",
7
+ "tests",
8
+ "**/*.spec.ts"
9
+ ]
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Language & Environment */
4
+ "target": "es2024",
5
+ "lib": [
6
+ "es2024"
7
+ ],
8
+ "module": "commonjs",
9
+ "moduleResolution": "node",
10
+ "types": [
11
+ "node"
12
+ ],
13
+ /* Output */
14
+ "outDir": "./dist",
15
+ "declaration": true,
16
+ "sourceMap": true,
17
+ "removeComments": true,
18
+ "incremental": true,
19
+ /* Decorators (NestJS) */
20
+ "emitDecoratorMetadata": true,
21
+ "experimentalDecorators": true,
22
+ /* Module Interop */
23
+ "esModuleInterop": true,
24
+ "allowSyntheticDefaultImports": true,
25
+ "resolveJsonModule": true,
26
+ /* Base Paths */
27
+ "baseUrl": ".",
28
+ "paths": {
29
+ "@routes/*": [
30
+ "src/routes/*"
31
+ ],
32
+ "@core-common/*": [
33
+ "src/core-common/*"
34
+ ],
35
+ "@common-infra/*": [
36
+ "src/common-infra/*"
37
+ ],
38
+ "@middleware/*": [
39
+ "src/middleware/*"
40
+ ]
41
+ },
42
+ /* Type Safety (non-strict by design) */
43
+ "strict": false,
44
+ "strictNullChecks": false,
45
+ "strictPropertyInitialization": false,
46
+ "noImplicitAny": false,
47
+ "noImplicitReturns": false,
48
+ "strictBindCallApply": true,
49
+ /* Code Quality */
50
+ "forceConsistentCasingInFileNames": true,
51
+ "noFallthroughCasesInSwitch": true,
52
+ "noUnusedLocals": true,
53
+ "noUnusedParameters": true,
54
+ /* Performance */
55
+ "skipLibCheck": true,
56
+ "inlineSources": true
57
+ }
58
+ }