@ifecodes/backend-template 1.1.5 → 1.1.6

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.
@@ -4,7 +4,7 @@ import { fileURLToPath } from "url";
4
4
 
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
6
 
7
- export const generateDockerCompose = (target, allServices) => {
7
+ export const generateDockerCompose = (target, allServices, projectname) => {
8
8
  const dockerCompose = {
9
9
  services: {},
10
10
  };
@@ -35,7 +35,7 @@ export const generateDockerCompose = (target, allServices) => {
35
35
  dockerfile: "Dockerfile"
36
36
  },
37
37
  image: `${serviceName}:latest`,
38
- container_name: serviceName,
38
+ container_name: projectname + "_" + serviceName,
39
39
  ports: [
40
40
  `\${${envVarName}:-${port}}:\${${envVarName}:-${port}}`,
41
41
  ],
@@ -67,6 +67,16 @@ export const generateDockerCompose = (target, allServices) => {
67
67
  config.volumes.map((v) => ` - ${v}`).join("\n")
68
68
  )
69
69
  .join("\n")
70
+ +
71
+ "\n\n" +
72
+ // Add x-watch section to help file watchers map service src and shared folders
73
+ `x-watch:\n` +
74
+ allServices
75
+ .map(
76
+ (name) =>
77
+ ` ${name}:\n - ./services/${name}/src\n - ./shared`
78
+ )
79
+ .join("\n")
70
80
  );
71
81
  };
72
82
 
@@ -215,6 +215,11 @@ export const getProjectConfig = async () => {
215
215
  mode = res.mode;
216
216
  }
217
217
 
218
+ // If adding to an existing microservice project, normalize the provided service name
219
+ if (isInMicroserviceProject && res.serviceName) {
220
+ res.serviceName = normalizeServiceName(res.serviceName);
221
+ }
222
+
218
223
  return {
219
224
  ...res,
220
225
  sanitizedName,
@@ -224,3 +229,13 @@ export const getProjectConfig = async () => {
224
229
  isInMicroserviceProject,
225
230
  };
226
231
  };
232
+
233
+ // Normalize serviceName for existing microservice projects: ensure it ends with '-service'
234
+ // and is kebab-cased/lowercase for consistency.
235
+ function normalizeServiceName(name) {
236
+ if (!name) return name;
237
+ let svc = String(name).trim().toLowerCase();
238
+ svc = svc.replace(/\s+/g, "-").replace(/[^a-z0-9\-]/g, "");
239
+ if (!svc.endsWith("-service")) svc = `${svc}-service`;
240
+ return svc;
241
+ }
@@ -3,7 +3,6 @@ import path from "path";
3
3
 
4
4
  export const generateReadme = (config, serviceName = null) => {
5
5
  const { projectType, mode, features = [], auth, sanitizedName } = config;
6
- const isMonolith = projectType === "monolith";
7
6
  const isMicroservice = projectType === "microservice";
8
7
 
9
8
  let readme = `# ${serviceName || sanitizedName}\n\n`;
@@ -24,9 +23,20 @@ export const generateReadme = (config, serviceName = null) => {
24
23
  readme += `- **Deployment**: ${mode === "docker" ? "Docker" : "PM2"}\n`;
25
24
  readme += `- **Gateway**: Port 4000 (main entry point)\n`;
26
25
  readme += `- **Services**:\n`;
27
- readme += ` - Gateway (port 4000)\n`;
28
- readme += ` - Health Service (port 4001)\n`;
29
- if (auth) readme += ` - Auth Service (port 4002)\n`;
26
+ const servicesList = (config.allServices && config.allServices.length)
27
+ ? config.allServices
28
+ : ["gateway", "health-service", ...(auth ? ["auth-service"] : [])];
29
+ servicesList.forEach((service, idx) => {
30
+ const isGateway = service === "gateway";
31
+ const port = isGateway
32
+ ? 4000
33
+ : 4001 + servicesList.filter((s) => s !== "gateway" && servicesList.indexOf(s) < idx).length;
34
+ const pretty = service
35
+ .split("-")
36
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
37
+ .join(" ");
38
+ readme += ` - ${pretty} (port ${port})\n`;
39
+ });
30
40
  readme += `\n`;
31
41
  } else {
32
42
  readme += `- **Type**: Monolith API\n`;
@@ -108,11 +118,11 @@ export const generateReadme = (config, serviceName = null) => {
108
118
  readme += `### With Docker\n\n`;
109
119
  readme += `\`\`\`bash\n`;
110
120
  readme += `# Start all services\n`;
111
- readme += `docker-compose up\n\n`;
121
+ readme += `npm run dev\n\n`;
112
122
  readme += `# Start in detached mode\n`;
113
- readme += `docker-compose up -d\n\n`;
123
+ readme += `npm run dev -d\n\n`;
114
124
  readme += `# Stop all services\n`;
115
- readme += `docker-compose down\n`;
125
+ readme += `npm stop\n`;
116
126
  readme += `\`\`\`\n\n`;
117
127
  } else if (isMicroservice && mode === "nodocker") {
118
128
  readme += `### With PM2\n\n`;
@@ -159,9 +169,18 @@ export const generateReadme = (config, serviceName = null) => {
159
169
  }
160
170
 
161
171
  readme += `### Direct Service Access (Development Only)\n`;
162
- readme += `- **Gateway**: \`http://localhost:4000\`\n`;
163
- readme += `- **Health Service**: \`http://localhost:4001/api/v1/health\`\n\n`;
164
- if (auth) readme += `- **Auth Service**: \`http://localhost:4002/api/v1/auth\`\n\n`;
172
+ const directServices = (config.allServices && config.allServices.length)
173
+ ? config.allServices
174
+ : ["gateway", "health-service", ...(auth ? ["auth-service"] : [])];
175
+ directServices.forEach((service) => {
176
+ const isGateway = service === "gateway";
177
+ const port = isGateway
178
+ ? 4000
179
+ : 4001 + directServices.filter((s) => s !== "gateway" && directServices.indexOf(s) < directServices.indexOf(service)).length;
180
+ const basePath = isGateway ? `` : `/api/v1`;
181
+ readme += `- **${service}**: \`http://localhost:${port}${basePath}\`\n`;
182
+ });
183
+ readme += `\n`;
165
184
 
166
185
  readme += "### Example Requests\n";
167
186
  readme += "```bash\n";
@@ -169,28 +188,28 @@ export const generateReadme = (config, serviceName = null) => {
169
188
  readme += "curl http://localhost:4000/\n\n";
170
189
  readme += "# Gateway health\n";
171
190
  readme += "curl http://localhost:4000/health\n\n";
172
- readme += "# Health service (through gateway)\n";
173
- readme += "curl http://localhost:4000/api/v1/health\n\n";
174
- readme += "# Health service (direct access)\n";
175
- readme += "curl http://localhost:4001/api/v1/health\n";
176
- if (auth) {
177
- readme += "\n# Register user (through gateway)\n";
178
- readme += `curl -X POST http://localhost:4000/api/v1/auth/register \\\n`;
179
- readme += ` -H "Content-Type: application/json" \\\n`;
180
- readme += ` -d '{"username":"testuser","password":"password123"}'\n\n`;
181
- readme += "# Login user (through gateway)\n";
182
- readme += `curl -X POST http://localhost:4000/api/v1/auth/login \\\n`;
183
- readme += ` -H "Content-Type: application/json" \\\n`;
184
- readme += ` -d '{"username":"testuser","password":"password123"}'\n\n`;
185
- readme += "# Register user (direct access)\n";
186
- readme += `curl -X POST http://localhost:4002/api/v1/auth/register \\\n`;
187
- readme += ` -H "Content-Type: application/json" \\\n`;
188
- readme += ` -d '{"username":"testuser","password":"password123"}'\n\n`;
189
- readme += "# Login user (direct access)\n";
190
- readme += `curl -X POST http://localhost:4002/api/v1/auth/login \\\n`;
191
- readme += ` -H "Content-Type: application/json" \\\n`;
192
- readme += ` -d '{"username":"testuser","password":"password123"}'\n`;
191
+
192
+ // Direct access examples for non-gateway services
193
+ const exampleServices = (config.allServices && config.allServices.length)
194
+ ? config.allServices
195
+ : ["gateway", "health-service", ...(auth ? ["auth-service"] : [])];
196
+ exampleServices.forEach((service) => {
197
+ if (service === "gateway") return; // gateway already covered
198
+ const port = service === "gateway" ? 4000 : 4001 + exampleServices.filter((s) => s !== "gateway" && exampleServices.indexOf(s) < exampleServices.indexOf(service)).length;
199
+ readme += `# ${service} (direct access)\n`;
200
+ readme += `curl http://localhost:${port}/api/v1/health\n\n`;
201
+ });
202
+
203
+ if (auth && exampleServices.includes("auth-service")) {
204
+ const authPort = 4001 + exampleServices.filter((s) => s !== "gateway" && exampleServices.indexOf(s) < exampleServices.indexOf("auth-service")).length;
205
+ readme += "# Auth requests (through gateway)\n";
206
+ readme += `curl -X POST http://localhost:4000/api/v1/auth/register \\\n+ -H "Content-Type: application/json" \\\n+ -d '{"username":"testuser","password":"password123"}'\n\n`;
207
+ readme += `curl -X POST http://localhost:4000/api/v1/auth/login \\\n+ -H "Content-Type: application/json" \\\n+ -d '{"username":"testuser","password":"password123"}'\n\n`;
208
+ readme += `# Auth requests (direct access)\n`;
209
+ readme += `curl -X POST http://localhost:${authPort}/api/v1/auth/register \\\n+ -H "Content-Type: application/json" \\\n+ -d '{"username":"testuser","password":"password123"}'\n\n`;
210
+ readme += `curl -X POST http://localhost:${authPort}/api/v1/auth/login \\\n+ -H "Content-Type: application/json" \\\n+ -d '{"username":"testuser","password":"password123"}'\n\n`;
193
211
  }
212
+
194
213
  readme += "```\n\n";
195
214
 
196
215
  } else {
@@ -234,10 +253,12 @@ export const generateReadme = (config, serviceName = null) => {
234
253
  readme += `│ ├── config/ # Database, environment configs\n`;
235
254
  readme += `│ └── utils/ # Logger, error handlers\n`;
236
255
  readme += `├── services/\n`;
237
- readme += `│ ├── gateway/ # API Gateway (port 4000)\n`;
238
- readme += `│ ├── health-service/ # Health checks (port 4001)\n`;
239
- if (auth) readme += `│ └── auth-service/ # Authentication (port 4002)\n`;
240
- else readme += `│ └── ...\n`;
256
+ const projectServices = (config.allServices && config.allServices.length)
257
+ ? config.allServices
258
+ : ["gateway", "health-service", ...(auth ? ["auth-service"] : [])];
259
+ projectServices.forEach((service) => {
260
+ readme += `│ ├── ${service}/\n`;
261
+ });
241
262
  readme += `├── ${mode === "docker" ? "docker-compose.yml" : "pm2.config.js"}\n`;
242
263
  readme += `├── .husky/ # Git hooks\n`;
243
264
  readme += `└── package.json # Root package.json\n`;
@@ -265,8 +286,8 @@ export const generateReadme = (config, serviceName = null) => {
265
286
  readme += `## Available Scripts\n\n`;
266
287
  if (isMicroservice) {
267
288
  if (mode === "docker") {
268
- readme += `- \`docker-compose up\` - Start all services\n`;
269
- readme += `- \`docker-compose down\` - Stop all services\n`;
289
+ readme += `- \`npm run dev\` - Start all services\n`;
290
+ readme += `- \`npm stop\` - Stop all services\n`;
270
291
  readme += `- \`docker-compose logs -f [service-name]\` - View service logs\n`;
271
292
  } else {
272
293
  readme += `- \`pm2 start pm2.config.js\` - Start all services\n`;
@@ -298,9 +319,17 @@ export const generateReadme = (config, serviceName = null) => {
298
319
  }
299
320
  readme += `\n`;
300
321
 
322
+ // Scaffold attribution
323
+ readme += `\n`;
324
+ readme += `## About this Scaffold\n\n`;
325
+ readme += `This project was generated using the @ifecodes/backend-template scaffold. `;
326
+ readme += `You can recreate or customize this scaffold using the CLI: \n\n`;
327
+ readme += `- Run without installing (recommended): \`npx ifecodes-template\`\n`;
328
+ readme += `- Install globally: \`npm i -g @ifecodes/backend-template\` and run \`ifecodes-template\`\n\n`;
329
+
301
330
  // License
302
331
  readme += `## License\n\n`;
303
332
  readme += `MIT\n`;
304
-
333
+
305
334
  return readme;
306
335
  };
@@ -36,11 +36,36 @@ export const setupService = async (
36
36
  // Detect file extension (ts or js)
37
37
  const ext = getFileExtension(serviceRoot);
38
38
 
39
+ // Remove workspace-level config files from service (they should live at root)
40
+ try {
41
+ const serviceConfigFiles = [
42
+ ".prettierrc",
43
+ ".prettierignore",
44
+ ".eslintrc.json",
45
+ "eslint.config.js",
46
+ "husky",
47
+ ];
48
+ for (const f of serviceConfigFiles) {
49
+ const p = path.join(serviceRoot, f);
50
+ if (fs.existsSync(p)) {
51
+ // Remove file or directory
52
+ const stat = fs.statSync(p);
53
+ if (stat.isDirectory()) fs.rmSync(p, { recursive: true, force: true });
54
+ else fs.rmSync(p, { force: true });
55
+ }
56
+ }
57
+ } catch (err) {
58
+ // Non-fatal
59
+ }
60
+
39
61
  // Ensure service-level gitignore is renamed immediately after template copy
40
62
  try {
41
63
  const serviceGitignore = path.join(serviceRoot, "gitignore");
42
64
  const serviceDotGitignore = path.join(serviceRoot, ".gitignore");
43
- if (fs.existsSync(serviceGitignore) && !fs.existsSync(serviceDotGitignore)) {
65
+ if (
66
+ fs.existsSync(serviceGitignore) &&
67
+ !fs.existsSync(serviceDotGitignore)
68
+ ) {
44
69
  fs.renameSync(serviceGitignore, serviceDotGitignore);
45
70
  }
46
71
  } catch (err) {
@@ -49,9 +74,11 @@ export const setupService = async (
49
74
 
50
75
  // Special handling for gateway service
51
76
  if (serviceName === "gateway") {
77
+ const tmplLang = res.language === "javascript" ? "js" : "ts";
52
78
  const gatewayModule = await import(
53
- `../../template/gateway/${res.language}/inject.js`
79
+ `../../template/gateway/${tmplLang}/inject.js`
54
80
  );
81
+
55
82
  deps.push(...gatewayModule.gatewayDeps);
56
83
 
57
84
  // Copy gateway-specific files
@@ -63,7 +90,7 @@ export const setupService = async (
63
90
  const templateExt = res.language === "javascript" ? ".js" : ".ts";
64
91
  const templateDir = path.join(
65
92
  __dirname,
66
- `../../template/gateway/${res.language}`,
93
+ `../../template/gateway/${tmplLang}`,
67
94
  );
68
95
  const gatewayAppContent = fs.readFileSync(
69
96
  path.join(templateDir, `app${templateExt}`),
@@ -86,9 +113,13 @@ export const setupService = async (
86
113
  const routesPath = path.join(serviceRoot, `src/routes.${ext}`);
87
114
  const modulesPath = path.join(serviceRoot, "src/modules");
88
115
  const middlewaresPath = path.join(serviceRoot, "src/middlewares");
116
+ const configPath = path.join(serviceRoot, `src/config`);
117
+ const utilPath = path.join(serviceRoot, `src/utils`);
89
118
 
90
119
  if (fs.existsSync(routesPath)) fs.rmSync(routesPath);
91
120
  if (fs.existsSync(modulesPath)) fs.rmSync(modulesPath, { recursive: true });
121
+ if (fs.existsSync(configPath)) fs.rmSync(configPath, { recursive: true });
122
+ if (fs.existsSync(utilPath)) fs.rmSync(utilPath, { recursive: true });
92
123
  if (fs.existsSync(middlewaresPath))
93
124
  fs.rmSync(middlewaresPath, { recursive: true });
94
125
  } else {
@@ -279,7 +310,7 @@ export const setupService = async (
279
310
  envContent = envContent.replace("/*__ALLOWED_ORIGIN__*/", "");
280
311
  }
281
312
 
282
- // Add MONGO_URI if auth is enabled
313
+ // Add MONGO_URI and JWT_SECRET if auth is enabled
283
314
  if (shouldIncludeAuth && res.auth) {
284
315
  const assertion = res.language === "javascript" ? "" : "!";
285
316
  envContent = envContent.replace(
@@ -374,7 +405,8 @@ export const setupService = async (
374
405
  } // End of else block for non-gateway services
375
406
 
376
407
  // Update tsconfig.json for microservices to support @/ alias with shared folder
377
- if (res.projectType === "microservice") {
408
+ // Also run when adding a service into an existing microservice project
409
+ if (res.projectType === "microservice" || res.isInMicroserviceProject) {
378
410
  const tsconfigPath = path.join(serviceRoot, "tsconfig.json");
379
411
  let tsconfigContent = fs.readFileSync(tsconfigPath, "utf8");
380
412
 
@@ -421,17 +453,67 @@ export const setupService = async (
421
453
  // Update server.ts to use shared imports
422
454
  const serverPath = path.join(serviceRoot, `src/server.${ext}`);
423
455
  if (fs.existsSync(serverPath)) {
456
+ // Determine a single port string for this specific service.
457
+ // Gateway should use 4000; other services use 4001, 4002, ...
458
+ let serverPort = "3000";
459
+ if (Array.isArray(allServices) && allServices.length) {
460
+ if (serviceName === "gateway") {
461
+ serverPort = "4000";
462
+ } else {
463
+ const idx = allServices.indexOf(serviceName);
464
+ if (idx !== -1) {
465
+ // Count non-gateway services before this one to compute offset
466
+ const nonGatewayBefore = allServices
467
+ .slice(0, idx)
468
+ .filter((s) => s !== "gateway").length;
469
+ serverPort = `${4001 + nonGatewayBefore}`;
470
+ } else {
471
+ // Fallback: assign next available port after 4000
472
+ serverPort = `${4001 + allServices.length - 1}`;
473
+ }
474
+ }
475
+ }
476
+
424
477
  let serverContent = fs.readFileSync(serverPath, "utf8");
478
+
479
+ // Normalize imports: accept @/ or relative imports and rewrite to shared imports
425
480
  serverContent = serverContent
426
- .replace('from "@/utils"', 'from "@/shared/utils"')
427
- .replace('from "@/config"', 'from "@/shared/config"');
481
+ .replace(
482
+ /from\s+["'](?:@\/utils|\.\/utils|\.\.\/utils)["']/g,
483
+ 'from "@/shared/utils"',
484
+ )
485
+ .replace(
486
+ /from\s+["'](?:@\/config|\.\/config|\.\.\/config)["']/g,
487
+ 'from "@/shared/config"',
488
+ );
428
489
 
429
- // Update PORT to use service-specific environment variable
490
+ // Update PORT to use service-specific environment variable and a correct default port.
430
491
  const portEnvVar = `${serviceName.toUpperCase().replace(/-/g, "_")}_PORT`;
431
- serverContent = serverContent.replace(
432
- /const PORT = ENV\.PORT \|\| (\d+);/,
433
- `const PORT = ENV.${portEnvVar} || $1;`,
434
- );
492
+ const portRegex = /const\s+PORT\s*=\s*ENV\.PORT\s*\|\|\s*(\d+)\s*;/;
493
+ if (portRegex.test(serverContent)) {
494
+ serverContent = serverContent.replace(
495
+ portRegex,
496
+ `const PORT = ENV.${portEnvVar} || ${serverPort};`,
497
+ );
498
+ } else {
499
+ // Fallback: replace a simple numeric default or a bare PORT assignment
500
+ const simplePortRegex = /const\s+PORT\s*=\s*(\d+)\s*;/;
501
+ if (simplePortRegex.test(serverContent)) {
502
+ serverContent = serverContent.replace(
503
+ simplePortRegex,
504
+ `const PORT = ENV.${portEnvVar} || ${serverPort};`,
505
+ );
506
+ } else {
507
+ // Last resort: append a PORT assignment near the top after imports
508
+ const importEnd = serverContent.indexOf("\n\n");
509
+ const insertPos = importEnd === -1 ? 0 : importEnd + 2;
510
+ const portLine = `const PORT = ENV.${portEnvVar} || ${serverPort};\n\n`;
511
+ serverContent =
512
+ serverContent.slice(0, insertPos) +
513
+ portLine +
514
+ serverContent.slice(insertPos);
515
+ }
516
+ }
435
517
 
436
518
  fs.writeFileSync(serverPath, serverContent);
437
519
  }
@@ -476,14 +558,6 @@ export const setupService = async (
476
558
  .filter(Boolean);
477
559
  }
478
560
 
479
- // Add Node.js native path aliasing for JavaScript projects
480
- // This replaces TypeScript's tsconfig paths with Node's native imports field
481
- if (res.language === "javascript") {
482
- finalPackageJson.imports = {
483
- "#/*": "./src/*",
484
- };
485
- }
486
-
487
561
  // Add --poll flag to dev script for Docker mode (fixes watch mode in Docker on Windows)
488
562
  if (res.projectType === "microservice" && res.mode === "docker") {
489
563
  if (finalPackageJson.scripts && finalPackageJson.scripts.dev) {
@@ -494,6 +568,55 @@ export const setupService = async (
494
568
  }
495
569
  }
496
570
 
571
+ // If creating microservices, do not install workspace-level devDependencies per service
572
+ if (res.projectType === "microservice") {
573
+ if (finalPackageJson.devDependencies) {
574
+ const toRemove = [
575
+ "prettier",
576
+ "eslint",
577
+ "eslint-config-prettier",
578
+ "@typescript-eslint/eslint-plugin",
579
+ "@typescript-eslint/parser",
580
+ "husky",
581
+ ];
582
+ for (const dep of toRemove) {
583
+ if (finalPackageJson.devDependencies[dep]) {
584
+ delete finalPackageJson.devDependencies[dep];
585
+ }
586
+ }
587
+
588
+ // Remove @types/* from JavaScript services; keep for TypeScript
589
+ if (res.language === "javascript") {
590
+ for (const key of Object.keys(finalPackageJson.devDependencies)) {
591
+ if (key.startsWith("@types/"))
592
+ delete finalPackageJson.devDependencies[key];
593
+ }
594
+ }
595
+
596
+ // If devDependencies becomes empty, remove the field
597
+ if (Object.keys(finalPackageJson.devDependencies).length === 0) {
598
+ delete finalPackageJson.devDependencies;
599
+ }
600
+ }
601
+ }
602
+
603
+ // Remove per-service prepare script (which runs husky) for microservice workspaces
604
+ if (res.projectType === "microservice" || res.isInMicroserviceProject) {
605
+ if (finalPackageJson.scripts && finalPackageJson.scripts.prepare) {
606
+ delete finalPackageJson.scripts.prepare;
607
+ }
608
+ // Also remove per-service lint/format/check-format scripts (workspace-level tooling lives at root)
609
+ if (finalPackageJson.scripts) {
610
+ delete finalPackageJson.scripts.lint;
611
+ delete finalPackageJson.scripts.format;
612
+ delete finalPackageJson.scripts["check-format"];
613
+ // If scripts becomes empty, remove the field
614
+ if (Object.keys(finalPackageJson.scripts).length === 0) {
615
+ delete finalPackageJson.scripts;
616
+ }
617
+ }
618
+ }
619
+
497
620
  fs.writeFileSync(
498
621
  packageJsonPath,
499
622
  JSON.stringify(finalPackageJson, null, 2) + "\n",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifecodes/backend-template",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Production-ready Express + TypeScript/JavaScript backend generator with optional features and microservice support",
5
5
  "bin": {
6
6
  "ifecodes-template": "bin/cli.js"
@@ -20,7 +20,7 @@
20
20
  "bugs": {
21
21
  "url": "https://github.com/ALADETAN-IFE/backend-template/issues"
22
22
  },
23
- "homepage": "https://github.com/ALADETAN-IFE/backend-template#readme",
23
+ "homepage": "https://backend-template-generator.vercel.app",
24
24
  "publishConfig": {
25
25
  "access": "public"
26
26
  },
@@ -28,7 +28,7 @@ console.log(
28
28
  const requiredKeys = ENV && Object.keys(ENV).length ? Object.keys(ENV) : [];
29
29
 
30
30
  const missing = requiredKeys.filter(
31
- (k) => ENV == null || (ENV as Record<string, undefined>)[k] === undefined || (ENV as Record<string, undefined>)[k] === "",
31
+ (k) => ENV == null || (ENV as Record<string, string | undefined>)[k] === undefined || (ENV as Record<string, string | undefined>)[k] === "",
32
32
  );
33
33
 
34
34
  if (missing.length === requiredKeys.length) {
@@ -1,5 +1,6 @@
1
1
  import express, { Request, Response, NextFunction } from "express";
2
2
  import { createProxyMiddleware } from "http-proxy-middleware";
3
+ import { ENV } from "@/shared/config";
3
4
  import { logger } from "@/shared/utils";
4
5
 
5
6
  const app = express();
@@ -4,18 +4,19 @@ export const generateGatewayRoutes = (services, mode = "docker") => {
4
4
  const routes = services
5
5
  .filter((s) => s !== "gateway")
6
6
  .map((service, index) => {
7
+ const servicePort = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
7
8
  const port = 4001 + index; // Host port mapping: gateway=4000, services start at 4001
8
- const routePath = service.replace("-service", "");
9
9
 
10
10
  // Docker: use container name with internal port 4000
11
11
  // Non-docker: use localhost with mapped host port
12
12
  const host = mode === "docker" ? service : "localhost";
13
- const targetPort = mode === "docker" ? 4000 : port;
13
+ const servicePortEnv = `${servicePort}`
14
14
 
15
15
  return `
16
16
  // Proxy to ${service}
17
+ const ${servicePort} = ENV.${servicePort} || ${port}
17
18
  app.use("/api", createProxyMiddleware({
18
- target: "http://${host}:${targetPort}/api",
19
+ target: \`http://${host}:${servicePortEnv}/api\`,
19
20
  changeOrigin: true,
20
21
  on: {
21
22
  error: (err, req, res) => {