@ifecodes/backend-template 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 (54) hide show
  1. package/README.md +288 -0
  2. package/bin/cli.js +249 -0
  3. package/bin/lib/microservice-config.js +89 -0
  4. package/bin/lib/prompts.js +159 -0
  5. package/bin/lib/readme-generator.js +245 -0
  6. package/bin/lib/service-setup.js +316 -0
  7. package/package.json +52 -0
  8. package/template/base/.env.example +5 -0
  9. package/template/base/.eslintrc.json +17 -0
  10. package/template/base/.husky/pre-commit +13 -0
  11. package/template/base/.prettierignore +47 -0
  12. package/template/base/.prettierrc +7 -0
  13. package/template/base/eslint.config.js +33 -0
  14. package/template/base/package.json +32 -0
  15. package/template/base/src/app.ts +9 -0
  16. package/template/base/src/config/db.ts +4 -0
  17. package/template/base/src/config/env.ts +9 -0
  18. package/template/base/src/config/index.ts +2 -0
  19. package/template/base/src/middlewares/index.ts +3 -0
  20. package/template/base/src/middlewares/method-not-allowed.middleware.ts +17 -0
  21. package/template/base/src/middlewares/not-found.middleware.ts +8 -0
  22. package/template/base/src/middlewares/root.middleware.ts +14 -0
  23. package/template/base/src/modules/index.ts +8 -0
  24. package/template/base/src/modules/v1/health/health.controller.ts +18 -0
  25. package/template/base/src/modules/v1/health/health.route.ts +9 -0
  26. package/template/base/src/modules/v1/health/index.ts +1 -0
  27. package/template/base/src/modules/v1/index.ts +8 -0
  28. package/template/base/src/routes.ts +15 -0
  29. package/template/base/src/server.ts +11 -0
  30. package/template/base/src/utils/http-error.ts +47 -0
  31. package/template/base/src/utils/index.ts +11 -0
  32. package/template/base/src/utils/logger.ts +34 -0
  33. package/template/base/tsconfig.json +22 -0
  34. package/template/features/auth/argon2/inject.js +25 -0
  35. package/template/features/auth/base/inject.js +89 -0
  36. package/template/features/auth/bcrypt/inject.js +18 -0
  37. package/template/features/auth/models/index.ts +1 -0
  38. package/template/features/auth/models/user.model.ts +28 -0
  39. package/template/features/auth/modules/auth.controller.ts +20 -0
  40. package/template/features/auth/modules/auth.routes.ts +11 -0
  41. package/template/features/auth/modules/auth.service.ts +38 -0
  42. package/template/features/auth/modules/index.ts +1 -0
  43. package/template/features/auth/utils/hash.ts +20 -0
  44. package/template/features/auth/utils/jwt.ts +15 -0
  45. package/template/features/cors/inject.js +9 -0
  46. package/template/features/helmet/inject.js +3 -0
  47. package/template/features/morgan/inject.js +3 -0
  48. package/template/features/rate-limit/inject.js +3 -0
  49. package/template/gateway/app.ts +26 -0
  50. package/template/gateway/inject.js +27 -0
  51. package/template/gateway/server.ts +19 -0
  52. package/template/microservice/docker/Dockerfile +5 -0
  53. package/template/microservice/docker/docker-compose.yml +6 -0
  54. package/template/microservice/nodocker/pm2.config.js +3 -0
@@ -0,0 +1,316 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import prompts from "prompts";
4
+ import { execSync } from "child_process";
5
+ import { fileURLToPath } from "url";
6
+
7
+ export const setupService = async (
8
+ res,
9
+ serviceName,
10
+ serviceRoot,
11
+ shouldIncludeAuth,
12
+ allServices = []
13
+ ) => {
14
+ let imports = [];
15
+ let middlewares = [];
16
+ let deps = [];
17
+ let v1Imports = [];
18
+ let v1Routes = [];
19
+
20
+ // Special handling for gateway service
21
+ if (serviceName === "gateway") {
22
+ const gatewayModule = await import("../../template/gateway/inject.js");
23
+ deps.push(...gatewayModule.gatewayDeps);
24
+
25
+ // Copy gateway-specific files
26
+ const gatewayAppPath = path.join(serviceRoot, "src/app.ts");
27
+ const gatewayServerPath = path.join(serviceRoot, "src/server.ts");
28
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
+
30
+ const gatewayAppContent = fs.readFileSync(
31
+ path.join(__dirname, "../../template/gateway/app.ts"),
32
+ "utf8"
33
+ );
34
+ const gatewayServerContent = fs.readFileSync(
35
+ path.join(__dirname, "../../template/gateway/server.ts"),
36
+ "utf8"
37
+ );
38
+
39
+ // Generate routes for all services
40
+ const routes = gatewayModule.generateGatewayRoutes(allServices);
41
+ const finalAppContent = gatewayAppContent.replace("/*__ROUTES__*/", routes);
42
+
43
+ fs.writeFileSync(gatewayAppPath, finalAppContent);
44
+ fs.writeFileSync(gatewayServerPath, gatewayServerContent);
45
+
46
+ // Remove unnecessary files for gateway
47
+ const routesPath = path.join(serviceRoot, "src/routes.ts");
48
+ const modulesPath = path.join(serviceRoot, "src/modules");
49
+ const middlewaresPath = path.join(serviceRoot, "src/middlewares");
50
+
51
+ if (fs.existsSync(routesPath)) fs.rmSync(routesPath);
52
+ if (fs.existsSync(modulesPath)) fs.rmSync(modulesPath, { recursive: true });
53
+ if (fs.existsSync(middlewaresPath))
54
+ fs.rmSync(middlewaresPath, { recursive: true });
55
+ } else {
56
+ // Regular service setup (existing code)
57
+ // Add features (only for monolith or health-service)
58
+ if (res.projectType === "monolith" || serviceName === "health-service") {
59
+ for (const f of res.features) {
60
+ const feature = await import(`../../template/features/${f}/inject.js`);
61
+ imports.push(feature.imports);
62
+ middlewares.push(feature.middleware);
63
+ deps.push(...feature.deps);
64
+ }
65
+ }
66
+
67
+ // Add authentication (only for monolith or auth-service)
68
+ if (shouldIncludeAuth && res.auth) {
69
+ const baseAuth = await import(
70
+ "../../template/features/auth/base/inject.js"
71
+ );
72
+ deps.push(...baseAuth.deps);
73
+
74
+ for (const file in baseAuth.files) {
75
+ const fullPath = path.join(serviceRoot, file);
76
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
77
+ fs.writeFileSync(fullPath, baseAuth.files[file]);
78
+ }
79
+
80
+ const algo = await prompts({
81
+ type: "select",
82
+ name: "hasher",
83
+ message: `Password hashing method${
84
+ serviceName ? ` for ${serviceName}` : ""
85
+ }`,
86
+ choices: [
87
+ {
88
+ title: process.platform === "win32"
89
+ ? "bcrypt (recommended for Windows)"
90
+ : "argon2 (recommended)",
91
+ value: process.platform === "win32" ? "bcrypt" : "argon2"
92
+ },
93
+ {
94
+ title: process.platform === "win32"
95
+ ? "argon2 (requires build tools)"
96
+ : "bcrypt",
97
+ value: process.platform === "win32" ? "argon2" : "bcrypt"
98
+ },
99
+ ],
100
+ });
101
+
102
+ const hashFeature = await import(
103
+ `../../template/features/auth/${algo.hasher}/inject.js`
104
+ );
105
+ deps.push(...hashFeature.deps);
106
+
107
+ for (const file in hashFeature.files) {
108
+ const fullPath = path.join(serviceRoot, file);
109
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
110
+ fs.writeFileSync(fullPath, hashFeature.files[file]);
111
+ }
112
+
113
+ v1Imports.push(baseAuth.imports);
114
+ v1Routes.push(baseAuth.middleware);
115
+ }
116
+
117
+ // Update app.ts
118
+ const appPath = path.join(serviceRoot, "src/app.ts");
119
+ let content = fs.readFileSync(appPath, "utf8");
120
+ content = content.replace("/*__IMPORTS__*/", imports.join("\n"));
121
+ // import env if cors is selected
122
+ if (res.features && res.features.includes("cors")) {
123
+ envContent = envContent.replace(
124
+ "/*__ENV_CORS__*/",
125
+ "import { ENV } from '@/config';"
126
+ );
127
+ }
128
+ content = content.replace("/*__MIDDLEWARE__*/", middlewares.join("\n"));
129
+ fs.writeFileSync(appPath, content);
130
+
131
+ // Update root endpoint middleware with project info
132
+ const rootMiddlewarePath = path.join(serviceRoot, "src/middlewares/root.middleware.ts");
133
+ if (fs.existsSync(rootMiddlewarePath)) {
134
+ let rootContent = fs.readFileSync(rootMiddlewarePath, "utf8");
135
+ rootContent = rootContent.replace("/*__PROJECT_NAME__*/", serviceName || res.sanitizedName);
136
+ rootContent = rootContent.replace("/*__PROJECT_TYPE__*/", res.projectType);
137
+
138
+ // Add auth endpoint if auth is enabled
139
+ if (shouldIncludeAuth && res.auth) {
140
+ rootContent = rootContent.replace(
141
+ "/*__AUTH_ENDPOINT__*/",
142
+ 'auth: "/v1/auth",'
143
+ );
144
+ } else {
145
+ rootContent = rootContent.replace("/*__AUTH_ENDPOINT__*/", "");
146
+ }
147
+
148
+ fs.writeFileSync(rootMiddlewarePath, rootContent);
149
+ }
150
+
151
+ // Update v1 index.ts if needed
152
+ if (v1Imports.length || v1Routes.length) {
153
+ const v1IndexPath = path.join(serviceRoot, "src/modules/v1/index.ts");
154
+ let v1Content = fs.readFileSync(v1IndexPath, "utf8");
155
+
156
+ const lastImportIndex = v1Content.lastIndexOf("import");
157
+ const importEndIndex = v1Content.indexOf("\n", lastImportIndex) + 1;
158
+ v1Content =
159
+ v1Content.slice(0, importEndIndex) +
160
+ v1Imports.join("\n") +
161
+ "\n" +
162
+ v1Content.slice(importEndIndex);
163
+
164
+ const exportIndex = v1Content.lastIndexOf("export default");
165
+ v1Content =
166
+ v1Content.slice(0, exportIndex) +
167
+ v1Routes.join("\n") +
168
+ "\n\n" +
169
+ v1Content.slice(exportIndex);
170
+
171
+ fs.writeFileSync(v1IndexPath, v1Content);
172
+ }
173
+
174
+ // Update env.ts to conditionally include ALLOWED_ORIGIN and MONGO_URI
175
+ const envPath = path.join(serviceRoot, "src/config/env.ts");
176
+ if (fs.existsSync(envPath)) {
177
+ let envContent = fs.readFileSync(envPath, "utf8");
178
+
179
+ // Add ALLOWED_ORIGIN if CORS is selected
180
+ if (res.features && res.features.includes("cors")) {
181
+ envContent = envContent.replace(
182
+ "/*__ALLOWED_ORIGIN__*/",
183
+ 'ALLOWED_ORIGIN: process.env.ALLOWED_ORIGIN!,'
184
+ );
185
+ } else {
186
+ envContent = envContent.replace("/*__ALLOWED_ORIGIN__*/", "");
187
+ }
188
+
189
+ // Add MONGO_URI if auth is enabled
190
+ if (shouldIncludeAuth && res.auth) {
191
+ envContent = envContent.replace(
192
+ "/*__MONGO_URI__*/",
193
+ 'MONGO_URI: process.env.MONGO_URI!,'
194
+ );
195
+ } else {
196
+ envContent = envContent.replace("/*__MONGO_URI__*/", "");
197
+ }
198
+
199
+ fs.writeFileSync(envPath, envContent);
200
+ }
201
+
202
+ // Update server.ts to connect to DB if auth is enabled
203
+ const serverPath = path.join(serviceRoot, "src/server.ts");
204
+ if (fs.existsSync(serverPath)) {
205
+ let serverContent = fs.readFileSync(serverPath, "utf8");
206
+
207
+ if (shouldIncludeAuth && res.auth) {
208
+ serverContent = serverContent.replace(
209
+ "/*__DB_IMPORT__*/",
210
+ 'import { connectDB } from "./config";'
211
+ );
212
+ serverContent = serverContent.replace(
213
+ "/*__DB_CONNECT__*/",
214
+ `// Connect to MongoDB
215
+ await connectDB();`
216
+ );
217
+ } else {
218
+ serverContent = serverContent.replace("/*__DB_IMPORT__*/", "");
219
+ serverContent = serverContent.replace("/*__DB_CONNECT__*/", "");
220
+ }
221
+
222
+ fs.writeFileSync(serverPath, serverContent);
223
+ }
224
+
225
+ // Update .env.example to conditionally include environment variables
226
+ const envExamplePath = path.join(serviceRoot, ".env.example");
227
+ if (fs.existsSync(envExamplePath)) {
228
+ let envExampleContent = fs.readFileSync(envExamplePath, "utf8");
229
+
230
+ // Add ALLOWED_ORIGIN if CORS is selected
231
+ if (res.features && res.features.includes("cors")) {
232
+ envExampleContent = envExampleContent.replace(
233
+ "/*__ALLOWED_ORIGIN_ENV__*/",
234
+ 'ALLOWED_ORIGIN=http://localhost:3000'
235
+ );
236
+ } else {
237
+ envExampleContent = envExampleContent.replace("/*__ALLOWED_ORIGIN_ENV__*/", "");
238
+ }
239
+
240
+ // Add MONGO_URI and JWT_SECRET if auth is enabled
241
+ if (shouldIncludeAuth && res.auth) {
242
+ envExampleContent = envExampleContent.replace(
243
+ "/*__MONGO_URI_ENV__*/",
244
+ 'MONGO_URI=mongodb://localhost:27017/your-database-name'
245
+ );
246
+ envExampleContent = envExampleContent.replace(
247
+ "/*__JWT_SECRET_ENV__*/",
248
+ 'JWT_SECRET=your-super-secret-jwt-key-change-this-in-production'
249
+ );
250
+ } else {
251
+ envExampleContent = envExampleContent.replace("/*__MONGO_URI_ENV__*/", "");
252
+ envExampleContent = envExampleContent.replace("/*__JWT_SECRET_ENV__*/", "");
253
+ }
254
+
255
+ fs.writeFileSync(envExamplePath, envExampleContent);
256
+ }
257
+ } // End of else block for non-gateway services
258
+
259
+ // Update tsconfig.json for microservices to support @/ alias with shared folder
260
+ if (res.projectType === "microservice") {
261
+ const tsconfigPath = path.join(serviceRoot, "tsconfig.json");
262
+ let tsconfigContent = fs.readFileSync(tsconfigPath, "utf8");
263
+
264
+ // Remove comments from JSON (strip-json-comments approach)
265
+ tsconfigContent = tsconfigContent
266
+ .replace(/\/\/.*$/gm, '') // Remove single-line comments
267
+ .replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments
268
+
269
+ const tsconfig = JSON.parse(tsconfigContent);
270
+
271
+ // Update paths to include shared folder
272
+ tsconfig.compilerOptions.paths = {
273
+ "@/*": ["*"],
274
+ "@/config/*": ["../../shared/config/*"],
275
+ "@/utils/*": ["../../shared/utils/*"],
276
+ };
277
+
278
+ fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
279
+ }
280
+
281
+ // Update package.json
282
+ const packageJsonPath = path.join(serviceRoot, "package.json");
283
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
284
+ packageJson.name = serviceName || res.sanitizedName;
285
+ fs.writeFileSync(
286
+ packageJsonPath,
287
+ JSON.stringify(packageJson, null, 2) + "\n"
288
+ );
289
+
290
+ // Install dependencies
291
+ console.log(
292
+ `\n📦 Installing dependencies for ${serviceName || "project"}...\n`
293
+ );
294
+
295
+ try {
296
+ if (deps.length) {
297
+ execSync(`npm install ${deps.join(" ")}`, {
298
+ cwd: serviceRoot,
299
+ stdio: "inherit",
300
+ });
301
+ }
302
+ execSync("npm install", { cwd: serviceRoot, stdio: "inherit" });
303
+ } catch (error) {
304
+ console.error("\n⚠️ Warning: Some dependencies failed to install.");
305
+ console.error("This is usually due to native modules (like argon2) requiring build tools.\n");
306
+ console.error("💡 Solutions:");
307
+ console.error(" 1. Install build tools: npm install --global windows-build-tools");
308
+ console.error(" 2. Or switch to bcrypt (works better on Windows)");
309
+ console.error(" 3. Or manually install later: cd " + (serviceName || res.sanitizedName) + " && npm install\n");
310
+
311
+ // Don't exit - let the project be created anyway
312
+ console.log("⏭️ Continuing with project creation...\n");
313
+ }
314
+
315
+ return deps;
316
+ };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@ifecodes/backend-template",
3
+ "version": "1.0.0",
4
+ "description": "Production-ready Express + TypeScript backend generator with optional features and microservice support",
5
+ "bin": {
6
+ "ifecodes-template": "bin/cli.js"
7
+ },
8
+ "type": "module",
9
+ "files": [
10
+ "bin",
11
+ "template"
12
+ ],
13
+ "scripts": {
14
+ "test": "echo \"Error: no test specified\" && exit 1"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/ALADETAN-IFE/backend-template.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/ALADETAN-IFE/backend-template/issues"
22
+ },
23
+ "homepage": "https://github.com/ALADETAN-IFE/backend-template#readme",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "keywords": [
28
+ "cli",
29
+ "template",
30
+ "boilerplate",
31
+ "typescript",
32
+ "backend",
33
+ "express",
34
+ "microservice",
35
+ "monolith",
36
+ "nodejs",
37
+ "generator",
38
+ "scaffold",
39
+ "docker",
40
+ "pm2",
41
+ "jwt",
42
+ "authentication"
43
+ ],
44
+ "license": "MIT",
45
+ "author": "Aladetan Fortune Ifeloju (IfeCodes) <ifecodes01@gmail.com>",
46
+ "dependencies": {
47
+ "prompts": "^2.4.2"
48
+ },
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ }
52
+ }
@@ -0,0 +1,5 @@
1
+ PORT=4000
2
+ NODE_ENV=development
3
+ /*__ALLOWED_ORIGIN_ENV__*/
4
+ /*__MONGO_URI_ENV__*/
5
+ /*__JWT_SECRET_ENV__*/
@@ -0,0 +1,17 @@
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
+ }
@@ -0,0 +1,13 @@
1
+ set -e
2
+
3
+ echo "Checking format (prettier)..."
4
+ npm run check-format
5
+
6
+ echo "Running TypeScript type-check..."
7
+ npx tsc --noEmit
8
+
9
+ echo "Checking lint..."
10
+ npm run lint -- --max-warnings=0
11
+
12
+ echo "Building project..."
13
+ npm run build
@@ -0,0 +1,47 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+ .yarn/install-state.gz
8
+
9
+ # testing
10
+ /coverage
11
+
12
+ # next.js
13
+ /.next/
14
+ /out/
15
+ .next/
16
+
17
+ # production
18
+ /build
19
+
20
+ # misc
21
+ .DS_Store
22
+ *.pem
23
+
24
+ # debug
25
+ npm-debug.log*
26
+ yarn-debug.log*
27
+ yarn-error.log*
28
+
29
+ # local env files
30
+ .env*.local
31
+
32
+ # vercel
33
+ .vercel
34
+
35
+ # typescript
36
+ *.tsbuildinfo
37
+ next-env.d.ts
38
+
39
+
40
+ ./dist
41
+
42
+ /*.yml
43
+ /*.yaml
44
+ /*.md
45
+ /*.json
46
+ README.md
47
+ eslint.config.ts
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "all",
5
+ "printWidth": 90,
6
+ "tabWidth": 2
7
+ }
@@ -0,0 +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
+ ];
@@ -0,0 +1,32 @@
1
+ {
2
+ "scripts": {
3
+ "dev": "ts-node-dev --respawn --transpile-only -r tsconfig-paths/register src/server.ts",
4
+ "start": "node dist/server.js",
5
+ "build": "tsc",
6
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
7
+ "format": "prettier --write \"src/**/*.{ts,json}\"",
8
+ "check-format": "prettier --check \"src/**/*.{ts,json}\"",
9
+ "prepare": "husky"
10
+ },
11
+ "dependencies": {
12
+ "dotenv": "^16.3.1",
13
+ "express": "^5.2.1",
14
+ "module-alias": "^2.2.3"
15
+ },
16
+ "devDependencies": {
17
+ "@types/express": "^5.0.6",
18
+ "@types/node": "^25.0.3",
19
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
20
+ "@typescript-eslint/parser": "^8.50.1",
21
+ "eslint": "^9.39.2",
22
+ "eslint-config-prettier": "^10.1.8",
23
+ "husky": "^9.1.7",
24
+ "prettier": "^3.7.4",
25
+ "ts-node-dev": "^2.0.0",
26
+ "tsconfig-paths": "^4.2.0",
27
+ "typescript": "^5.9.3"
28
+ },
29
+ "_moduleAliases": {
30
+ "@": "dist"
31
+ }
32
+ }
@@ -0,0 +1,9 @@
1
+ import express from "express";
2
+ /*__IMPORTS__*/
3
+ /*__ENV_CORS__*/
4
+
5
+ const app = express();
6
+
7
+ /*__MIDDLEWARE__*/
8
+
9
+ export default app;
@@ -0,0 +1,4 @@
1
+ // MongoDB connection will be added here when authentication is enabled
2
+ export const connectDB = async () => {
3
+ // Database connection placeholder
4
+ };
@@ -0,0 +1,9 @@
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
+ };
@@ -0,0 +1,2 @@
1
+ export { connectDB } from "./db";
2
+ export { ENV } from "./env";
@@ -0,0 +1,3 @@
1
+ export { default as methodNotAllowedHandler } from "./method-not-allowed.middleware";
2
+ export { notFound } from "./not-found.middleware";
3
+ export { rootHandler } from "./root.middleware";
@@ -0,0 +1,17 @@
1
+ // middlewares/methodNotAllowed.ts
2
+ import { Request, Response, NextFunction } from "express";
3
+
4
+ const methodNotAllowed =
5
+ (allowedMethods: string[]) => (req: Request, res: Response, next: NextFunction) => {
6
+ if (!allowedMethods.includes(req.method)) {
7
+ res.set("Allow", allowedMethods.join(", "));
8
+ return res.status(405).json({
9
+ status: "error",
10
+ message: `Method ${req.method} not allowed for ${req.originalUrl}`,
11
+ allowed: allowedMethods,
12
+ });
13
+ }
14
+ next();
15
+ };
16
+
17
+ export default methodNotAllowed;
@@ -0,0 +1,8 @@
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
+ };
@@ -0,0 +1,14 @@
1
+ import { Request, Response } from "express";
2
+
3
+ export const rootHandler = (req: Request, res: Response) => {
4
+ res.json({
5
+ name: "/*__PROJECT_NAME__*/",
6
+ type: "/*__PROJECT_TYPE__*/",
7
+ version: "1.0.0",
8
+ status: "running",
9
+ endpoints: {
10
+ health: "/v1/health",
11
+ /*__AUTH_ENDPOINT__*/
12
+ },
13
+ });
14
+ };
@@ -0,0 +1,8 @@
1
+ import { Router } from "express";
2
+ import V1Routes from "./v1";
3
+
4
+ const router = Router();
5
+
6
+ router.use("/v1", V1Routes);
7
+
8
+ export default router;
@@ -0,0 +1,18 @@
1
+ import { Request, Response } from "express";
2
+ import { logger } from "@/utils";
3
+
4
+ export const healthCheck = async (_: Request, res: Response) => {
5
+ logger.info("Health", "healthy");
6
+
7
+ return res.status(200).json({
8
+ status: "healthy",
9
+ uptime: process.uptime(),
10
+ timestamp: new Date().toISOString(),
11
+ services: {
12
+ memory: {
13
+ rss: process.memoryUsage().rss,
14
+ heapUsed: process.memoryUsage().heapUsed,
15
+ },
16
+ },
17
+ });
18
+ };
@@ -0,0 +1,9 @@
1
+ import { Router } from "express";
2
+ import { healthCheck } from "./health.controller";
3
+ import { methodNotAllowedHandler } from "@/middlewares";
4
+
5
+ const router = Router();
6
+ router.use(methodNotAllowedHandler(["GET"]));
7
+ router.get("/", healthCheck);
8
+
9
+ export default router;
@@ -0,0 +1 @@
1
+ export { default as healthRoutes } from "./health.route";
@@ -0,0 +1,8 @@
1
+ import { Router } from "express";
2
+ import { healthRoutes } from "./health";
3
+
4
+ const router = Router();
5
+
6
+ router.use("/health", healthRoutes);
7
+
8
+ export default router;
@@ -0,0 +1,15 @@
1
+ import { Router } from "express";
2
+ import modulesRouter from "./modules";
3
+ import { notFound, rootHandler } from "./middlewares";
4
+
5
+ const router = Router();
6
+
7
+ // Root endpoint
8
+ router.get("/", rootHandler);
9
+
10
+ router.use("/", modulesRouter);
11
+
12
+ // 404 handler - must be last
13
+ router.use(notFound);
14
+
15
+ export default router;