@ifecodes/backend-template 1.1.8 → 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.
- package/LICENSE +201 -0
- package/README.md +423 -365
- package/bin/cli.js +1276 -836
- package/bin/lib/microservice-config.js +155 -150
- package/bin/lib/prompts.js +277 -241
- package/bin/lib/readme-generator.js +364 -335
- package/bin/lib/service-setup.js +901 -679
- package/package.json +64 -55
- package/template/base/js/.env.example +5 -5
- package/template/base/js/.eslintrc.json +10 -13
- package/template/base/js/.husky/pre-commit +1 -7
- package/template/base/js/.prettierrc +7 -7
- package/template/base/js/eslint.config.js +33 -31
- package/template/base/js/package.json +29 -28
- package/template/base/js/src/app.js +20 -15
- package/template/base/js/src/config/env.js +32 -2
- package/template/base/js/src/config/index.js +2 -2
- package/template/base/js/src/docs/index.js +5 -0
- package/template/base/js/src/docs/route-registry.js +63 -0
- package/template/base/js/src/middlewares/error-handler.middleware.js +22 -0
- package/template/base/js/src/middlewares/index.js +9 -3
- package/template/base/js/src/middlewares/method-not-allowed.middleware.js +8 -2
- package/template/base/js/src/middlewares/not-found.middleware.js +4 -1
- package/template/base/js/src/middlewares/observability.middleware.js +24 -0
- package/template/base/js/src/middlewares/root.middleware.js +7 -5
- package/template/base/js/src/middlewares/validation.middleware.js +39 -0
- package/template/base/js/src/modules/index.js +8 -8
- package/template/base/js/src/modules/v1/health/health.controller.auth.js +29 -0
- package/template/base/js/src/modules/v1/health/health.controller.js +4 -4
- package/template/base/js/src/modules/v1/health/health.route.js +70 -5
- package/template/base/js/src/modules/v1/health/index.js +1 -1
- package/template/base/js/src/modules/v1/index.js +3 -3
- package/template/base/js/src/routes.js +13 -6
- package/template/base/js/src/server.js +18 -18
- package/template/base/js/src/utils/http-error.js +27 -6
- package/template/base/js/src/utils/index.js +28 -22
- package/template/base/js/src/utils/logger.js +57 -67
- package/template/base/ts/.eslintrc.json +13 -17
- package/template/base/ts/.prettierrc +7 -7
- package/template/base/ts/eslint.config.js +33 -33
- package/template/base/ts/package.json +41 -39
- package/template/base/ts/src/app.ts +20 -15
- package/template/base/ts/src/config/db.ts +4 -4
- package/template/base/ts/src/config/env.ts +40 -10
- package/template/base/ts/src/config/index.ts +2 -2
- package/template/base/ts/src/docs/index.ts +3 -0
- package/template/base/ts/src/docs/route-registry.ts +98 -0
- package/template/base/ts/src/middlewares/error-handler.middleware.ts +26 -0
- package/template/base/ts/src/middlewares/index.ts +6 -3
- package/template/base/ts/src/middlewares/method-not-allowed.middleware.ts +23 -16
- package/template/base/ts/src/middlewares/not-found.middleware.ts +10 -8
- package/template/base/ts/src/middlewares/observability.middleware.ts +25 -0
- package/template/base/ts/src/middlewares/root.middleware.ts +16 -14
- package/template/base/ts/src/middlewares/validation.middleware.ts +46 -0
- package/template/base/ts/src/modules/index.ts +8 -8
- package/template/base/ts/src/modules/v1/health/health.controller.auth.ts +26 -0
- package/template/base/ts/src/modules/v1/health/health.controller.ts +18 -18
- package/template/base/ts/src/modules/v1/health/health.route.ts +68 -9
- package/template/base/ts/src/modules/v1/health/index.ts +1 -1
- package/template/base/ts/src/modules/v1/index.ts +8 -8
- package/template/base/ts/src/routes.ts +23 -15
- package/template/base/ts/src/server.ts +19 -19
- package/template/base/ts/src/utils/http-error.ts +63 -45
- package/template/base/ts/src/utils/index.ts +14 -11
- package/template/base/ts/src/utils/logger.ts +58 -68
- package/template/base/ts/tsconfig.json +21 -22
- package/template/features/auth/argon2/inject.js +50 -50
- package/template/features/auth/base/health-openapi.ts +62 -0
- package/template/features/auth/base/inject.js +174 -172
- package/template/features/auth/bcrypt/inject.js +40 -40
- package/template/features/auth/models/user.model.js +24 -24
- package/template/features/auth/models/user.model.ts +1 -1
- package/template/features/auth/modules/auth.controller.js +21 -21
- package/template/features/auth/modules/auth.controller.ts +28 -20
- package/template/features/auth/modules/auth.routes.js +89 -10
- package/template/features/auth/modules/auth.routes.ts +86 -11
- package/template/features/auth/modules/auth.service.js +29 -29
- package/template/features/auth/modules/auth.service.ts +38 -38
- package/template/features/auth/modules/index.js +1 -1
- package/template/features/auth/utils/hash.ts +20 -20
- package/template/features/auth/utils/jwt.js +12 -12
- package/template/features/cors/inject.js +14 -13
- package/template/features/helmet/inject.js +7 -6
- package/template/features/morgan/inject.js +8 -7
- package/template/features/rate-limit/inject.js +7 -6
- package/template/gateway/js/app.js +42 -42
- package/template/gateway/js/inject.js +33 -31
- package/template/gateway/js/server.js +19 -19
- package/template/gateway/ts/app.ts +43 -43
- package/template/gateway/ts/inject.js +33 -33
- package/template/gateway/ts/server.ts +19 -19
- package/template/microservice/docker/docker-compose.yml +5 -5
- package/template/microservice/nodocker/pm2.config.js +3 -3
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import router from "./routes";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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,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();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from "express";
|
|
2
|
+
import { HttpError, logger } from "@/utils";
|
|
3
|
+
|
|
4
|
+
// Centralized error handling middleware
|
|
5
|
+
|
|
6
|
+
export const errorHandler = (
|
|
7
|
+
err: unknown,
|
|
8
|
+
_: Request,
|
|
9
|
+
res: Response,
|
|
10
|
+
__: NextFunction,
|
|
11
|
+
) => {
|
|
12
|
+
if (err instanceof HttpError) {
|
|
13
|
+
logger.warn("ErrorHandler", `${err.status} ${err.message}`);
|
|
14
|
+
return res.status(err.status).json({
|
|
15
|
+
status: "error",
|
|
16
|
+
message: err.message,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
logger.error("ErrorHandler", "Unhandled error", err as Error);
|
|
21
|
+
|
|
22
|
+
return res.status(500).json({
|
|
23
|
+
status: "error",
|
|
24
|
+
message: "Internal Server Error",
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -1,3 +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";
|
|
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,16 +1,23 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
};
|
|
@@ -1,14 +1,16 @@
|
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
root: "/",
|
|
11
|
+
health: "/api/v1/health",
|
|
12
|
+
docs: "/api-docs",
|
|
13
|
+
/*__AUTH_ENDPOINT__*/
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from "express";
|
|
2
|
+
import { BadRequestError } from "@/utils";
|
|
3
|
+
|
|
4
|
+
type RequestSchemas = {
|
|
5
|
+
body?: { parse: (value: unknown) => unknown };
|
|
6
|
+
query?: { parse: (value: unknown) => unknown };
|
|
7
|
+
params?: { parse: (value: unknown) => unknown };
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const isValidationError = (error: unknown): error is { issues: Array<{ path?: Array<string | number>; message: string }> } => {
|
|
11
|
+
return (
|
|
12
|
+
typeof error === "object" &&
|
|
13
|
+
error !== null &&
|
|
14
|
+
"issues" in error &&
|
|
15
|
+
Array.isArray((error as { issues?: unknown }).issues)
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const validateRequest = (schemas: RequestSchemas) => {
|
|
20
|
+
return (req: Request, _: Response, next: NextFunction) => {
|
|
21
|
+
try {
|
|
22
|
+
if (schemas.body) {
|
|
23
|
+
req.body = schemas.body.parse(req.body);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (schemas.query) {
|
|
27
|
+
req.query = schemas.query.parse(req.query) as Request["query"];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (schemas.params) {
|
|
31
|
+
req.params = schemas.params.parse(req.params) as Request["params"];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
next();
|
|
35
|
+
} catch (error: unknown) {
|
|
36
|
+
if (isValidationError(error)) {
|
|
37
|
+
const issues = error.issues
|
|
38
|
+
.map((issue) => `${issue.path?.join(".") || "request"}: ${issue.message}`)
|
|
39
|
+
.join("; ");
|
|
40
|
+
return next(new BadRequestError(`Request validation failed - ${issues}`));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return next(error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
@@ -1,8 +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;
|
|
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,26 @@
|
|
|
1
|
+
import { Request, Response } from "express";
|
|
2
|
+
import mongoose from "mongoose";
|
|
3
|
+
import { logger } from "@/utils";
|
|
4
|
+
|
|
5
|
+
export const healthCheck = async (_: Request, res: Response) => {
|
|
6
|
+
const mongoState = mongoose.connection.readyState;
|
|
7
|
+
|
|
8
|
+
const healthy = mongoState === 1;
|
|
9
|
+
|
|
10
|
+
const failed: string[] = [];
|
|
11
|
+
if (mongoState !== 1) failed.push("mongodb");
|
|
12
|
+
|
|
13
|
+
return res.status(healthy ? 200 : 503).json({
|
|
14
|
+
status: healthy ? "healthy" : "unhealthy",
|
|
15
|
+
uptime: process.uptime(),
|
|
16
|
+
timestamp: new Date().toISOString(),
|
|
17
|
+
services: {
|
|
18
|
+
mongodb: mongoState === 1 ? "connected" : "disconnected",
|
|
19
|
+
memory: {
|
|
20
|
+
rss: process.memoryUsage().rss,
|
|
21
|
+
heapUsed: process.memoryUsage().heapUsed,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
failed,
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -1,18 +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
|
-
};
|
|
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
|
+
};
|
|
@@ -1,9 +1,68 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
2
|
-
import { healthCheck } from "./health.controller";
|
|
3
|
-
import { methodNotAllowedHandler } from "@/middlewares";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
router
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { healthCheck } from "./health.controller";
|
|
3
|
+
import { methodNotAllowedHandler, validateRequest } from "@/middlewares";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { routeRegistry } from "@/docs";
|
|
6
|
+
|
|
7
|
+
const router = Router();
|
|
8
|
+
const healthQuerySchema = z
|
|
9
|
+
.object({
|
|
10
|
+
verbose: z.coerce.boolean().optional(),
|
|
11
|
+
})
|
|
12
|
+
.strict();
|
|
13
|
+
|
|
14
|
+
// Register route schema with auto-generated docs
|
|
15
|
+
routeRegistry.register({
|
|
16
|
+
method: "GET",
|
|
17
|
+
path: "/api/v1/health",
|
|
18
|
+
handler: healthCheck,
|
|
19
|
+
docs: {
|
|
20
|
+
tags: ["Health"],
|
|
21
|
+
summary: "Health check endpoint",
|
|
22
|
+
description:
|
|
23
|
+
"Returns API health status and runtime metrics including uptime and memory usage.",
|
|
24
|
+
parameters: [
|
|
25
|
+
{
|
|
26
|
+
name: "verbose",
|
|
27
|
+
in: "query",
|
|
28
|
+
required: false,
|
|
29
|
+
schema: { type: "boolean" },
|
|
30
|
+
description: "Optional verbose mode for detailed diagnostics.",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
responses: {
|
|
34
|
+
"200": {
|
|
35
|
+
description: "Healthy response with system metrics",
|
|
36
|
+
content: {
|
|
37
|
+
"application/json": {
|
|
38
|
+
schema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
status: { type: "string", example: "healthy" },
|
|
42
|
+
uptime: { type: "number", example: 123.45 },
|
|
43
|
+
timestamp: { type: "string", example: "2024-01-01T00:00:00.000Z" },
|
|
44
|
+
services: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
memory: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
rss: { type: "number" },
|
|
51
|
+
heapUsed: { type: "number" },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
router.use(methodNotAllowedHandler(["GET"]));
|
|
66
|
+
router.get("/", validateRequest({ query: healthQuerySchema }), healthCheck);
|
|
67
|
+
|
|
68
|
+
export default router;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as healthRoutes } from "./health.route";
|
|
1
|
+
export { default as healthRoutes } from "./health.route";
|
|
@@ -1,8 +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;
|
|
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;
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
2
|
-
import modulesRouter from "./modules";
|
|
3
|
-
import { notFound, rootHandler } from "./middlewares";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
router.
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
router.use(
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { NextFunction, Request, Response, Router } from "express";
|
|
2
|
+
import modulesRouter from "./modules";
|
|
3
|
+
import { notFound, rootHandler } from "./middlewares";
|
|
4
|
+
import swaggerUi from "swagger-ui-express";
|
|
5
|
+
import { routeRegistry } from "./docs";
|
|
6
|
+
|
|
7
|
+
const router = Router();
|
|
8
|
+
|
|
9
|
+
// Root endpoint
|
|
10
|
+
router.get("/", rootHandler);
|
|
11
|
+
|
|
12
|
+
// Swagger UI with auto-generated spec
|
|
13
|
+
router.use("/api-docs", swaggerUi.serve, (_req: Request, res: Response, next: NextFunction) => {
|
|
14
|
+
const spec = routeRegistry.generateOpenAPI("/*__PROJECT_NAME__*/", "1.0.0");
|
|
15
|
+
swaggerUi.setup(spec)(_req, res, next);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
router.use("/api", modulesRouter);
|
|
19
|
+
|
|
20
|
+
// 404 handler - must be last
|
|
21
|
+
router.use(notFound);
|
|
22
|
+
|
|
23
|
+
export default router;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import app from "./app";
|
|
2
|
-
import { ENV } from "./config";
|
|
3
|
-
import { logger } from "@/utils";
|
|
4
|
-
/*__DB_IMPORT__*/
|
|
5
|
-
|
|
6
|
-
const PORT = ENV.PORT || 3000;
|
|
7
|
-
|
|
8
|
-
const startServer = async () => {
|
|
9
|
-
/*__DB_CONNECT__*/
|
|
10
|
-
|
|
11
|
-
app.listen(PORT, () => {
|
|
12
|
-
logger.info("Server", `Server is running on port ${PORT}`);
|
|
13
|
-
});
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
startServer().catch((error) => {
|
|
17
|
-
logger.error("Server", "Failed to start server", error as Error);
|
|
18
|
-
process.exit(1);
|
|
19
|
-
});
|
|
1
|
+
import app from "./app";
|
|
2
|
+
import { ENV } from "./config";
|
|
3
|
+
import { logger } from "@/utils";
|
|
4
|
+
/*__DB_IMPORT__*/
|
|
5
|
+
|
|
6
|
+
const PORT = ENV.PORT || 3000;
|
|
7
|
+
|
|
8
|
+
const startServer = async () => {
|
|
9
|
+
/*__DB_CONNECT__*/
|
|
10
|
+
|
|
11
|
+
app.listen(PORT, () => {
|
|
12
|
+
logger.info("Server", `Server is running on port ${PORT}`);
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
startServer().catch((error) => {
|
|
17
|
+
logger.error("Server", "Failed to start server", error as Error);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|