@ifecodes/backend-template 1.1.9 → 1.4.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 (94) hide show
  1. package/README.md +423 -383
  2. package/bin/cli.js +1276 -964
  3. package/bin/lib/microservice-config.js +155 -150
  4. package/bin/lib/prompts.js +277 -241
  5. package/bin/lib/readme-generator.js +364 -329
  6. package/bin/lib/service-setup.js +901 -684
  7. package/package.json +64 -55
  8. package/template/base/js/.eslintrc.json +10 -13
  9. package/template/base/js/.prettierrc +7 -7
  10. package/template/base/js/eslint.config.js +33 -31
  11. package/template/base/js/package.json +29 -28
  12. package/template/base/js/src/app.js +20 -18
  13. package/template/base/js/src/config/db.js +8 -8
  14. package/template/base/js/src/config/env.js +44 -14
  15. package/template/base/js/src/config/index.js +7 -7
  16. package/template/base/js/src/docs/index.js +5 -0
  17. package/template/base/js/src/docs/route-registry.js +63 -0
  18. package/template/base/js/src/middlewares/error-handler.middleware.js +22 -19
  19. package/template/base/js/src/middlewares/index.js +15 -11
  20. package/template/base/js/src/middlewares/method-not-allowed.middleware.js +19 -13
  21. package/template/base/js/src/middlewares/not-found.middleware.js +13 -10
  22. package/template/base/js/src/middlewares/observability.middleware.js +24 -0
  23. package/template/base/js/src/middlewares/root.middleware.js +18 -16
  24. package/template/base/js/src/middlewares/validation.middleware.js +39 -0
  25. package/template/base/js/src/modules/index.js +8 -8
  26. package/template/base/js/src/modules/v1/health/health.controller.auth.js +29 -0
  27. package/template/base/js/src/modules/v1/health/health.controller.js +21 -21
  28. package/template/base/js/src/modules/v1/health/health.route.js +74 -9
  29. package/template/base/js/src/modules/v1/health/index.js +5 -5
  30. package/template/base/js/src/modules/v1/index.js +8 -8
  31. package/template/base/js/src/routes.js +23 -16
  32. package/template/base/js/src/server.js +18 -18
  33. package/template/base/js/src/utils/http-error.js +74 -74
  34. package/template/base/js/src/utils/index.js +28 -28
  35. package/template/base/js/src/utils/logger.js +57 -67
  36. package/template/base/ts/.eslintrc.json +13 -17
  37. package/template/base/ts/.prettierrc +7 -7
  38. package/template/base/ts/eslint.config.js +33 -33
  39. package/template/base/ts/package.json +41 -39
  40. package/template/base/ts/src/app.ts +20 -18
  41. package/template/base/ts/src/config/db.ts +4 -4
  42. package/template/base/ts/src/config/env.ts +40 -10
  43. package/template/base/ts/src/config/index.ts +2 -2
  44. package/template/base/ts/src/docs/index.ts +3 -0
  45. package/template/base/ts/src/docs/route-registry.ts +98 -0
  46. package/template/base/ts/src/middlewares/error-handler.middleware.ts +4 -1
  47. package/template/base/ts/src/middlewares/index.ts +6 -4
  48. package/template/base/ts/src/middlewares/method-not-allowed.middleware.ts +23 -18
  49. package/template/base/ts/src/middlewares/not-found.middleware.ts +10 -8
  50. package/template/base/ts/src/middlewares/observability.middleware.ts +25 -0
  51. package/template/base/ts/src/middlewares/root.middleware.ts +16 -14
  52. package/template/base/ts/src/middlewares/validation.middleware.ts +46 -0
  53. package/template/base/ts/src/modules/index.ts +8 -8
  54. package/template/base/ts/src/modules/v1/health/health.controller.auth.ts +26 -0
  55. package/template/base/ts/src/modules/v1/health/health.controller.ts +18 -18
  56. package/template/base/ts/src/modules/v1/health/health.route.ts +68 -9
  57. package/template/base/ts/src/modules/v1/health/index.ts +1 -1
  58. package/template/base/ts/src/modules/v1/index.ts +8 -8
  59. package/template/base/ts/src/routes.ts +23 -15
  60. package/template/base/ts/src/server.ts +19 -19
  61. package/template/base/ts/src/utils/http-error.ts +63 -63
  62. package/template/base/ts/src/utils/index.ts +14 -14
  63. package/template/base/ts/src/utils/logger.ts +58 -68
  64. package/template/base/ts/tsconfig.json +21 -21
  65. package/template/features/auth/argon2/inject.js +50 -50
  66. package/template/features/auth/base/health-openapi.ts +62 -0
  67. package/template/features/auth/base/inject.js +174 -172
  68. package/template/features/auth/bcrypt/inject.js +40 -40
  69. package/template/features/auth/models/index.ts +1 -1
  70. package/template/features/auth/models/user.model.js +24 -24
  71. package/template/features/auth/models/user.model.ts +28 -28
  72. package/template/features/auth/modules/auth.controller.js +21 -21
  73. package/template/features/auth/modules/auth.controller.ts +28 -20
  74. package/template/features/auth/modules/auth.routes.js +89 -10
  75. package/template/features/auth/modules/auth.routes.ts +86 -11
  76. package/template/features/auth/modules/auth.service.js +29 -29
  77. package/template/features/auth/modules/auth.service.ts +38 -38
  78. package/template/features/auth/modules/index.js +1 -1
  79. package/template/features/auth/modules/index.ts +1 -1
  80. package/template/features/auth/utils/hash.ts +20 -20
  81. package/template/features/auth/utils/jwt.js +12 -12
  82. package/template/features/auth/utils/jwt.ts +15 -15
  83. package/template/features/cors/inject.js +14 -13
  84. package/template/features/helmet/inject.js +7 -6
  85. package/template/features/morgan/inject.js +8 -7
  86. package/template/features/rate-limit/inject.js +7 -6
  87. package/template/gateway/js/app.js +42 -42
  88. package/template/gateway/js/inject.js +33 -33
  89. package/template/gateway/js/server.js +19 -19
  90. package/template/gateway/ts/app.ts +43 -43
  91. package/template/gateway/ts/inject.js +33 -33
  92. package/template/gateway/ts/server.ts +19 -19
  93. package/template/microservice/docker/docker-compose.yml +5 -5
  94. package/template/microservice/nodocker/pm2.config.js +3 -3
@@ -1,67 +1,57 @@
1
- const { ENV } = require("../config/env");
2
- // ANSI color codes for terminal output
3
- const colors = {
4
- reset: "\x1b[0m",
5
- blue: "\x1b[34m",
6
- cyan: "\x1b[36m",
7
- yellow: "\x1b[33m",
8
- red: "\x1b[31m",
9
- bold: "\x1b[1m",
10
- };
11
-
12
- function format(tag, color) {
13
- return `${color}${colors.bold}[${tag}]${colors.reset}`;
14
- }
15
-
16
- function getEnvironment() {
17
- return ENV.NODE_ENV === "development" || ENV.NODE_ENV === "staging"
18
- ? "development"
19
- : ENV.NODE_ENV;
20
- }
21
-
22
- console.log(
23
- format("logger", colors.blue),
24
- `Logger initialized for ${getEnvironment()} environment.`,
25
- );
26
-
27
- const requiredKeys = ENV && Object.keys(ENV).length ? Object.keys(ENV) : [];
28
-
29
- const missing = requiredKeys.filter(
30
- (k) => ENV == null || ENV[k] === undefined || ENV[k] === "",
31
- );
32
-
33
- if (missing.length === requiredKeys.length) {
34
- console.warn(
35
- format("logger", colors.yellow),
36
- "ENV values missing — make sure to set up your environment variables correctly.",
37
- );
38
- } else if (missing.length > 0) {
39
- console.warn(
40
- format("logger", colors.yellow),
41
- `Missing ENV keys: ${missing.join(", ")}`,
42
- );
43
- }
44
-
45
- const logger = {
46
- log(tag, ...args) {
47
- if (getEnvironment() !== "development") return;
48
- console.log(format(tag, colors.blue), ...args);
49
- },
50
-
51
- info(tag, ...args) {
52
- if (getEnvironment() !== "development") return;
53
- console.info(format(tag, colors.cyan), ...args);
54
- },
55
-
56
- warn(tag, ...args) {
57
- if (getEnvironment() !== "development") return;
58
- console.warn(format(tag, colors.yellow), ...args);
59
- },
60
-
61
- error(tag, ...args) {
62
- if (getEnvironment() !== "development") return;
63
- console.error(format(tag, colors.red), ...args);
64
- },
65
- };
66
-
67
- module.exports = logger;
1
+ const { ENV } = require('../config/env');
2
+ // ANSI color codes for terminal output
3
+ const colors = {
4
+ reset: '\x1b[0m',
5
+ blue: '\x1b[34m',
6
+ cyan: '\x1b[36m',
7
+ yellow: '\x1b[33m',
8
+ red: '\x1b[31m',
9
+ bold: '\x1b[1m',
10
+ };
11
+
12
+ function format(tag, color) {
13
+ return `${color}${colors.bold}[${tag}]${colors.reset}`;
14
+ }
15
+
16
+ function getEnvironment() {
17
+ return ENV.NODE_ENV === 'development' || ENV.NODE_ENV === 'staging'
18
+ ? 'development'
19
+ : ENV.NODE_ENV;
20
+ }
21
+
22
+ function shouldLog(level) {
23
+ if (level === 'log') {
24
+ return getEnvironment() === 'development';
25
+ }
26
+
27
+ return true;
28
+ }
29
+
30
+ console.log(
31
+ format('logger', colors.blue),
32
+ `Logger initialized for ${getEnvironment()} environment.`
33
+ );
34
+
35
+ const logger = {
36
+ log(tag, ...args) {
37
+ if (!shouldLog('log')) return;
38
+ console.log(format(tag, colors.blue), ...args);
39
+ },
40
+
41
+ info(tag, ...args) {
42
+ if (!shouldLog('info')) return;
43
+ console.info(format(tag, colors.cyan), ...args);
44
+ },
45
+
46
+ warn(tag, ...args) {
47
+ if (!shouldLog('warn')) return;
48
+ console.warn(format(tag, colors.yellow), ...args);
49
+ },
50
+
51
+ error(tag, ...args) {
52
+ if (!shouldLog('error')) return;
53
+ console.error(format(tag, colors.red), ...args);
54
+ },
55
+ };
56
+
57
+ module.exports = logger;
@@ -1,17 +1,13 @@
1
- {
2
- "parser": "@typescript-eslint/parser",
3
- "plugins": ["@typescript-eslint"],
4
- "extends": [
5
- "eslint:recommended",
6
- "plugin:@typescript-eslint/recommended",
7
- "prettier"
8
- ],
9
- "env": {
10
- "node": true,
11
- "es2021": true
12
- },
13
- "rules": {
14
- "@typescript-eslint/no-unused-vars": ["warn"],
15
- "@typescript-eslint/no-explicit-any": "error"
16
- }
17
- }
1
+ {
2
+ "parser": "@typescript-eslint/parser",
3
+ "plugins": ["@typescript-eslint"],
4
+ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
5
+ "env": {
6
+ "node": true,
7
+ "es2021": true
8
+ },
9
+ "rules": {
10
+ "@typescript-eslint/no-unused-vars": ["warn"],
11
+ "@typescript-eslint/no-explicit-any": "error"
12
+ }
13
+ }
@@ -1,7 +1,7 @@
1
- {
2
- "semi": true,
3
- "singleQuote": false,
4
- "trailingComma": "all",
5
- "printWidth": 90,
6
- "tabWidth": 2
7
- }
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "all",
5
+ "printWidth": 90,
6
+ "tabWidth": 2
7
+ }
@@ -1,33 +1,33 @@
1
- const tsParser = require("@typescript-eslint/parser");
2
- const tsPlugin = require("@typescript-eslint/eslint-plugin");
3
-
4
- module.exports = [
5
- // Files/paths to ignore (replaces .eslintignore usage in flat config)
6
- {
7
- ignores: ["node_modules/**", "dist/**"],
8
- },
9
-
10
- // TypeScript rules for source files
11
- {
12
- files: ["src/**/*.{ts,tsx}"],
13
- languageOptions: {
14
- parser: tsParser,
15
- parserOptions: {
16
- project: "./tsconfig.json",
17
- tsconfigRootDir: __dirname,
18
- ecmaVersion: 2020,
19
- sourceType: "module",
20
- },
21
- },
22
- plugins: {
23
- "@typescript-eslint": tsPlugin,
24
- },
25
- rules: {
26
- // Disallow explicit `any`
27
- "@typescript-eslint/no-explicit-any": "error",
28
-
29
- // You can add or tune more TypeScript rules here
30
- "@typescript-eslint/explicit-module-boundary-types": "off",
31
- },
32
- },
33
- ];
1
+ const tsParser = require("@typescript-eslint/parser");
2
+ const tsPlugin = require("@typescript-eslint/eslint-plugin");
3
+
4
+ module.exports = [
5
+ // Files/paths to ignore (replaces .eslintignore usage in flat config)
6
+ {
7
+ ignores: ["node_modules/**", "dist/**"],
8
+ },
9
+
10
+ // TypeScript rules for source files
11
+ {
12
+ files: ["src/**/*.{ts,tsx}"],
13
+ languageOptions: {
14
+ parser: tsParser,
15
+ parserOptions: {
16
+ project: "./tsconfig.json",
17
+ tsconfigRootDir: __dirname,
18
+ ecmaVersion: 2020,
19
+ sourceType: "module",
20
+ },
21
+ },
22
+ plugins: {
23
+ "@typescript-eslint": tsPlugin,
24
+ },
25
+ rules: {
26
+ // Disallow explicit `any`
27
+ "@typescript-eslint/no-explicit-any": "error",
28
+
29
+ // You can add or tune more TypeScript rules here
30
+ "@typescript-eslint/explicit-module-boundary-types": "off",
31
+ },
32
+ },
33
+ ];
@@ -1,39 +1,41 @@
1
- {
2
- "version": "1.0.0",
3
- "description": "",
4
- "main": "index.js",
5
- "scripts": {
6
- "dev": "ts-node-dev --respawn --transpile-only -r tsconfig-paths/register src/server.ts",
7
- "start": "node -r module-alias/register dist/server.js",
8
- "build": "tsc",
9
- "lint": "eslint \"src/**/*.{ts,tsx}\"",
10
- "format": "prettier --write \"src/**/*.{ts,json}\"",
11
- "check-format": "prettier --check \"src/**/*.{ts,json}\"",
12
- "prepare": "husky"
13
- },
14
- "keywords": [],
15
- "author": "",
16
- "license": "ISC",
17
- "type": "commonjs",
18
- "dependencies": {
19
- "dotenv": "^16.3.1",
20
- "express": "^5.2.1",
21
- "module-alias": "^2.2.3"
22
- },
23
- "devDependencies": {
24
- "@types/express": "^5.0.6",
25
- "@types/node": "^25.0.3",
26
- "@typescript-eslint/eslint-plugin": "^8.50.1",
27
- "@typescript-eslint/parser": "^8.50.1",
28
- "eslint": "^9.39.2",
29
- "eslint-config-prettier": "^10.1.8",
30
- "husky": "^9.1.7",
31
- "prettier": "^3.7.4",
32
- "ts-node-dev": "^2.0.0",
33
- "tsconfig-paths": "^4.2.0",
34
- "typescript": "^5.9.3"
35
- },
36
- "_moduleAliases": {
37
- "@": "dist"
38
- }
39
- }
1
+ {
2
+ "version": "1.0.0",
3
+ "description": "",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "dev": "ts-node-dev --respawn --transpile-only -r tsconfig-paths/register src/server.ts",
7
+ "start": "node -r module-alias/register dist/server.js",
8
+ "build": "tsc",
9
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
10
+ "format": "prettier --write \"src/**/*.{ts,json}\"",
11
+ "check-format": "prettier --check \"src/**/*.{ts,json}\"",
12
+ "prepare": "husky"
13
+ },
14
+ "keywords": [],
15
+ "author": "",
16
+ "license": "ISC",
17
+ "type": "commonjs",
18
+ "dependencies": {
19
+ "dotenv": "^16.3.1",
20
+ "express": "^5.2.1",
21
+ "module-alias": "^2.2.3",
22
+ "swagger-ui-express": "^5.0.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/express": "^5.0.6",
26
+ "@types/node": "^25.0.3",
27
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
28
+ "@typescript-eslint/parser": "^8.50.1",
29
+ "@types/swagger-ui-express": "^4.1.8",
30
+ "eslint": "^9.39.2",
31
+ "eslint-config-prettier": "^10.1.8",
32
+ "husky": "^9.1.7",
33
+ "prettier": "^3.7.4",
34
+ "ts-node-dev": "^2.0.0",
35
+ "tsconfig-paths": "^4.2.0",
36
+ "typescript": "^5.9.3"
37
+ },
38
+ "_moduleAliases": {
39
+ "@": "dist"
40
+ }
41
+ }
@@ -1,18 +1,20 @@
1
- import express from "express";
2
- import router from "./routes";
3
- import { errorHandler } from "@/middlewares";
4
- /*__IMPORTS__*/
5
-
6
- const app = express();
7
-
8
- // Parse JSON request bodies
9
- app.use(express.json());
10
-
11
- /*__MIDDLEWARE__*/
12
-
13
- // Connect routes
14
- app.use(router);
15
-
16
- app.use(errorHandler);
17
-
18
- export default app;
1
+ import express from "express";
2
+ import router from "./routes";
3
+ import { errorHandler, observabilityMiddleware } from "@/middlewares";
4
+ /*__IMPORTS__*/
5
+
6
+ const app = express();
7
+
8
+ // Parse JSON request bodies
9
+ app.use(express.json());
10
+
11
+ app.use(observabilityMiddleware);
12
+
13
+ /*__MIDDLEWARE__*/
14
+
15
+ // Connect routes
16
+ app.use(router);
17
+
18
+ app.use(errorHandler);
19
+
20
+ export default app;
@@ -1,4 +1,4 @@
1
- // MongoDB connection will be added here when authentication is enabled
2
- export const connectDB = async () => {
3
- // Database connection placeholder
4
- };
1
+ // MongoDB connection will be added here when authentication is enabled
2
+ export const connectDB = async () => {
3
+ // Database connection placeholder
4
+ };
@@ -1,10 +1,40 @@
1
- import dotenv from "dotenv";
2
- dotenv.config();
3
-
4
- export const ENV = {
5
- PORT: process.env.PORT!,
6
- /*__ALLOWED_ORIGIN__*/
7
- NODE_ENV: process.env.NODE_ENV!,
8
- /*__MONGO_URI__*/
9
- /*__JWT_SECRET__*/
10
- };
1
+ import dotenv from "dotenv";
2
+ dotenv.config();
3
+
4
+ const colors = {
5
+ reset: "\x1b[0m",
6
+ red: "\x1b[31m",
7
+ bold: "\x1b[1m",
8
+ };
9
+
10
+ function format(tag: string, color: string) {
11
+ return `${color}${colors.bold}[${tag}]${colors.reset}`;
12
+ }
13
+
14
+ const validateEnv = (env: Record<string, string | undefined>) => {
15
+ const missing = Object.entries(env)
16
+ .filter(([, value]) => value === undefined || value === "")
17
+ .map(([key]) => key);
18
+
19
+ if (missing.length > 0) {
20
+ console.error(
21
+ format("env", colors.red),
22
+ `Missing required environment variables: ${missing.join(", ")}`,
23
+ );
24
+ console.error(
25
+ format("env", colors.red),
26
+ "Please update your .env file and restart the server.",
27
+ );
28
+ process.exit(1);
29
+ }
30
+ };
31
+
32
+ export const ENV = {
33
+ PORT: process.env.PORT!,
34
+ /*__ALLOWED_ORIGIN__*/
35
+ NODE_ENV: process.env.NODE_ENV!,
36
+ /*__MONGO_URI__*/
37
+ /*__JWT_SECRET__*/
38
+ };
39
+
40
+ validateEnv(ENV);
@@ -1,2 +1,2 @@
1
- export { connectDB } from "./db";
2
- export { ENV } from "./env";
1
+ export { connectDB } from "./db";
2
+ export { ENV } from "./env";
@@ -0,0 +1,3 @@
1
+ import { routeRegistry } from "./route-registry";
2
+
3
+ export { routeRegistry };
@@ -0,0 +1,98 @@
1
+ import { RequestHandler } from "express";
2
+
3
+ export interface RouteDoc {
4
+ tags?: string[];
5
+ summary: string;
6
+ description?: string;
7
+ parameters?: Array<{
8
+ name: string;
9
+ in: "query" | "path" | "header" | "cookie";
10
+ required?: boolean;
11
+ schema: Record<string, unknown>;
12
+ description?: string;
13
+ }>;
14
+ requestBody?: {
15
+ required?: boolean;
16
+ content: {
17
+ "application/json": {
18
+ schema: Record<string, unknown>;
19
+ };
20
+ };
21
+ };
22
+ responses: Record<
23
+ number | string,
24
+ {
25
+ description: string;
26
+ content?: {
27
+ "application/json": {
28
+ schema?: Record<string, unknown>;
29
+ };
30
+ };
31
+ }
32
+ >;
33
+ }
34
+
35
+ export interface RouteSchema {
36
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
37
+ path: string;
38
+ handler: RequestHandler | RequestHandler[];
39
+ docs: RouteDoc;
40
+ }
41
+
42
+ class RouteRegistry {
43
+ private routes: RouteSchema[] = [];
44
+
45
+ register(route: RouteSchema): void {
46
+ this.routes.push(route);
47
+ }
48
+
49
+ getRoutes(): RouteSchema[] {
50
+ return this.routes;
51
+ }
52
+
53
+ generateOpenAPI(projectName: string, version = "1.0.0"): Record<string, unknown> {
54
+ const paths: Record<string, unknown> = {};
55
+ const tags = new Set<string>();
56
+
57
+ // Collect all unique tags
58
+ this.routes.forEach((route) => {
59
+ route.docs.tags?.forEach((tag) => tags.add(tag));
60
+ });
61
+
62
+ // Build paths from routes
63
+ this.routes.forEach((route) => {
64
+ if (!paths[route.path]) {
65
+ paths[route.path] = {};
66
+ }
67
+
68
+ const pathItem = paths[route.path] as Record<string, unknown>;
69
+ const method = route.method.toLowerCase();
70
+
71
+ pathItem[method] = {
72
+ tags: route.docs.tags || [],
73
+ summary: route.docs.summary,
74
+ description: route.docs.description,
75
+ parameters: route.docs.parameters || [],
76
+ requestBody: route.docs.requestBody,
77
+ responses: route.docs.responses,
78
+ };
79
+ });
80
+
81
+ return {
82
+ openapi: "3.0.3",
83
+ info: {
84
+ title: `${projectName} API`,
85
+ version,
86
+ description: "Auto-generated from route schemas.",
87
+ },
88
+ servers: [{ url: "http://localhost:4000" }],
89
+ tags: Array.from(tags).map((tag) => ({
90
+ name: tag,
91
+ description: `${tag} endpoints`,
92
+ })),
93
+ paths,
94
+ };
95
+ }
96
+ }
97
+
98
+ export const routeRegistry = new RouteRegistry();
@@ -1,5 +1,5 @@
1
1
  import { NextFunction, Request, Response } from "express";
2
- import { HttpError } from "@/utils";
2
+ import { HttpError, logger } from "@/utils";
3
3
 
4
4
  // Centralized error handling middleware
5
5
 
@@ -10,12 +10,15 @@ export const errorHandler = (
10
10
  __: NextFunction,
11
11
  ) => {
12
12
  if (err instanceof HttpError) {
13
+ logger.warn("ErrorHandler", `${err.status} ${err.message}`);
13
14
  return res.status(err.status).json({
14
15
  status: "error",
15
16
  message: err.message,
16
17
  });
17
18
  }
18
19
 
20
+ logger.error("ErrorHandler", "Unhandled error", err as Error);
21
+
19
22
  return res.status(500).json({
20
23
  status: "error",
21
24
  message: "Internal Server Error",
@@ -1,4 +1,6 @@
1
- export { default as methodNotAllowedHandler } from "./method-not-allowed.middleware";
2
- export { notFound } from "./not-found.middleware";
3
- export { rootHandler } from "./root.middleware";
4
- export { errorHandler } from "./error-handler.middleware";
1
+ export { default as methodNotAllowedHandler } from "./method-not-allowed.middleware";
2
+ export { notFound } from "./not-found.middleware";
3
+ export { rootHandler } from "./root.middleware";
4
+ export { errorHandler } from "./error-handler.middleware";
5
+ export { observabilityMiddleware } from "./observability.middleware";
6
+ export { validateRequest } from "./validation.middleware";
@@ -1,18 +1,23 @@
1
- import { Request, Response, NextFunction } from "express";
2
-
3
- // Middleware to handle 405 Method Not Allowed for unsupported HTTP methods on defined routes
4
-
5
- const methodNotAllowed =
6
- (allowedMethods: string[]) => (req: Request, res: Response, next: NextFunction) => {
7
- if (!allowedMethods.includes(req.method)) {
8
- res.set("Allow", allowedMethods.join(", "));
9
- return res.status(405).json({
10
- status: "error",
11
- message: `Method ${req.method} not allowed for ${req.originalUrl}`,
12
- allowed: allowedMethods,
13
- });
14
- }
15
- next();
16
- };
17
-
18
- export default methodNotAllowed;
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { logger } from "@/utils";
3
+
4
+ // Middleware to handle 405 Method Not Allowed for unsupported HTTP methods on defined routes
5
+
6
+ const methodNotAllowed =
7
+ (allowedMethods: string[]) => (req: Request, res: Response, next: NextFunction) => {
8
+ if (!allowedMethods.includes(req.method)) {
9
+ logger.warn(
10
+ "MethodNotAllowed",
11
+ `${req.method} ${req.originalUrl} | allowed: ${allowedMethods.join(", ")}`,
12
+ );
13
+ res.set("Allow", allowedMethods.join(", "));
14
+ return res.status(405).json({
15
+ status: "error",
16
+ message: `Method ${req.method} not allowed for ${req.originalUrl}`,
17
+ allowed: allowedMethods,
18
+ });
19
+ }
20
+ next();
21
+ };
22
+
23
+ export default methodNotAllowed;
@@ -1,8 +1,10 @@
1
- import { Request, Response } from "express";
2
-
3
- export const notFound = (req: Request, res: Response) => {
4
- res.status(404).json({
5
- status: "error",
6
- message: `Route ${req.originalUrl} not found`,
7
- });
8
- };
1
+ import { Request, Response } from "express";
2
+ import { logger } from "@/utils";
3
+
4
+ export const notFound = (req: Request, res: Response) => {
5
+ logger.warn("NotFound", `${req.method} ${req.originalUrl}`);
6
+ res.status(404).json({
7
+ status: "error",
8
+ message: `Route ${req.originalUrl} not found`,
9
+ });
10
+ };
@@ -0,0 +1,25 @@
1
+ import { randomUUID } from "crypto";
2
+ import { NextFunction, Request, Response } from "express";
3
+ import { logger } from "@/utils";
4
+
5
+ export const observabilityMiddleware = (
6
+ req: Request,
7
+ res: Response,
8
+ next: NextFunction,
9
+ ) => {
10
+ const requestId = req.get("X-Request-Id") || randomUUID();
11
+ const startedAt = Date.now();
12
+
13
+ res.locals.requestId = requestId;
14
+ res.setHeader("X-Request-Id", requestId);
15
+
16
+ res.on("finish", () => {
17
+ const duration = Date.now() - startedAt;
18
+ logger.info(
19
+ "HTTP",
20
+ `${requestId} ${req.method} ${req.originalUrl} -> ${res.statusCode} (${duration}ms)`,
21
+ );
22
+ });
23
+
24
+ next();
25
+ };