create-craftjs 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 (66) hide show
  1. package/README.md +137 -0
  2. package/bin/index.js +158 -0
  3. package/package.json +24 -0
  4. package/template/.dockerignore +4 -0
  5. package/template/Dockerfile +12 -0
  6. package/template/babel.config.json +3 -0
  7. package/template/craft/commands/build.js +15 -0
  8. package/template/craft/commands/db-fresh.js +22 -0
  9. package/template/craft/commands/db-generate.js +23 -0
  10. package/template/craft/commands/db-migrate.js +22 -0
  11. package/template/craft/commands/dev.js +16 -0
  12. package/template/craft/commands/key-generate.js +41 -0
  13. package/template/craft/commands/make-apidocs.js +121 -0
  14. package/template/craft/commands/make-command.js +38 -0
  15. package/template/craft/commands/make-dto.js +39 -0
  16. package/template/craft/commands/make-middleware.js +46 -0
  17. package/template/craft/commands/make-repository.js +36 -0
  18. package/template/craft/commands/make-route.js +88 -0
  19. package/template/craft/commands/make-service.js +39 -0
  20. package/template/craft/commands/make-test.js +48 -0
  21. package/template/craft/commands/make-utils.js +30 -0
  22. package/template/craft/commands/make-validation.js +42 -0
  23. package/template/craft/commands/make-view.js +42 -0
  24. package/template/craft/commands/start.js +29 -0
  25. package/template/craft/commands/test.js +20 -0
  26. package/template/craft.js +256 -0
  27. package/template/nodemon.json +6 -0
  28. package/template/package-lock.json +8777 -0
  29. package/template/package.json +79 -0
  30. package/template/prisma/migrations/20250518142257_create_table_users/migration.sql +13 -0
  31. package/template/prisma/migrations/migration_lock.toml +3 -0
  32. package/template/prisma/schema.prisma +22 -0
  33. package/template/prisma/seed.ts +29 -0
  34. package/template/public/assets/images/default-user.png +0 -0
  35. package/template/src/apidocs/auth-docs.ts +314 -0
  36. package/template/src/apidocs/users-docs.ts +240 -0
  37. package/template/src/config/database.ts +90 -0
  38. package/template/src/config/env.ts +29 -0
  39. package/template/src/config/logger.ts +116 -0
  40. package/template/src/config/web.ts +40 -0
  41. package/template/src/controllers/auth-controller.ts +88 -0
  42. package/template/src/controllers/user-controller.ts +79 -0
  43. package/template/src/dtos/list-dto.ts +12 -0
  44. package/template/src/dtos/user-dto.ts +57 -0
  45. package/template/src/main.ts +28 -0
  46. package/template/src/middleware/auth-middleware.ts +44 -0
  47. package/template/src/middleware/error-middleware.ts +27 -0
  48. package/template/src/middleware/http-logger-middleware.ts +31 -0
  49. package/template/src/repositories/user-repository.ts +75 -0
  50. package/template/src/routes/auth-route.ts +20 -0
  51. package/template/src/routes/main-route.ts +25 -0
  52. package/template/src/routes/user-route.ts +35 -0
  53. package/template/src/services/auth-service.ts +162 -0
  54. package/template/src/services/user-service.ts +102 -0
  55. package/template/src/types/type-request.ts +6 -0
  56. package/template/src/utils/async-handler.ts +9 -0
  57. package/template/src/utils/response-error.ts +10 -0
  58. package/template/src/utils/response.ts +60 -0
  59. package/template/src/utils/swagger.ts +135 -0
  60. package/template/src/utils/validation.ts +7 -0
  61. package/template/src/validations/user-validation.ts +127 -0
  62. package/template/src/views/index.ejs +6 -0
  63. package/template/src/views/layouts/main.ejs +14 -0
  64. package/template/src/views/partials/header.ejs +3 -0
  65. package/template/test/user.test.ts +16 -0
  66. package/template/tsconfig.json +13 -0
@@ -0,0 +1,90 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+ import { logger } from "./logger";
3
+ import { DateTime } from "luxon";
4
+ import { dbLogger } from "./logger";
5
+ import { env } from "./env";
6
+
7
+ export const prismaClient = new PrismaClient({
8
+ log: [
9
+ {
10
+ emit: "event",
11
+ level: "query",
12
+ },
13
+ {
14
+ emit: "event",
15
+ level: "error",
16
+ },
17
+ {
18
+ emit: "event",
19
+ level: "info",
20
+ },
21
+ {
22
+ emit: "event",
23
+ level: "warn",
24
+ },
25
+ ],
26
+ });
27
+
28
+ prismaClient.$use(async (params, next) => {
29
+ const result = await next(params);
30
+
31
+ const convertToYourTimeZone = (date: unknown): string => {
32
+ if (!(date instanceof Date) || isNaN(date.getTime())) {
33
+ return "";
34
+ }
35
+
36
+ return DateTime.fromJSDate(date)
37
+ .setZone(env.TZ || "UTC")
38
+ .toFormat(env.DATETIME_FORMAT);
39
+ };
40
+
41
+ const transformDates = (item: any): any => {
42
+ if (!item) return item;
43
+
44
+ if (Array.isArray(item)) {
45
+ return item.map(transformDates);
46
+ }
47
+
48
+ if (typeof item === "object") {
49
+ const newItem: any = { ...item };
50
+ for (const key in newItem) {
51
+ const value = newItem[key];
52
+ if (
53
+ ["created_at", "updated_at", "deleted_at"].includes(key) &&
54
+ value instanceof Date
55
+ ) {
56
+ newItem[key] = convertToYourTimeZone(value);
57
+ } else if (typeof value === "object" && value !== null) {
58
+ newItem[key] = transformDates(value);
59
+ }
60
+ }
61
+ return newItem;
62
+ }
63
+
64
+ return item;
65
+ };
66
+
67
+ return transformDates(result);
68
+ });
69
+
70
+ prismaClient.$on("query", (e) => {
71
+ dbLogger.info(`${e.query} - ${e.params}`);
72
+ });
73
+ prismaClient.$on("warn", (e) => {
74
+ logger.warn(e);
75
+ });
76
+ prismaClient.$on("info", (e) => {
77
+ logger.info(e);
78
+ });
79
+ prismaClient.$on("error", (e) => {
80
+ logger.error(e);
81
+ });
82
+ export const connectDatabase = async () => {
83
+ try {
84
+ await prismaClient.$connect();
85
+ logger.info("✅ Connected Database");
86
+ } catch (error) {
87
+ logger.error("❌ Failed Connect To Database", error);
88
+ throw error;
89
+ }
90
+ };
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import "dotenv/config";
3
+
4
+ const envSchema = z.object({
5
+ APP_NAME: z.string().default("Craft JS"),
6
+ APP_SECRET: z.string(),
7
+ NODE_ENV: z
8
+ .enum(["development", "production", "test"])
9
+ .default("development"),
10
+ TZ: z.string().default("Asia/Jakarta"),
11
+ DATETIME_FORMAT: z.string().default("dd-MM-yyyy HH:mm:ss"),
12
+ DATABASE_URL: z
13
+ .string()
14
+ .url({ message: "DATABASE_URL harus URL yang valid" }),
15
+ BASE_URL: z.string().url(),
16
+ BASE_API_URL: z.string().url(),
17
+ CLIENT_URL: z.string().url().optional(),
18
+ PORT: z.coerce.number().default(3000),
19
+ JWT_SECRET: z.string(),
20
+ });
21
+
22
+ const _env = envSchema.safeParse(process.env);
23
+
24
+ if (!_env.success) {
25
+ console.error("❌ ENV ERROR:", _env.error.format());
26
+ process.exit(1);
27
+ }
28
+
29
+ export const env = _env.data;
@@ -0,0 +1,116 @@
1
+ import winston from "winston";
2
+ import chalk from "chalk";
3
+ import DailyRotateFile from "winston-daily-rotate-file";
4
+ import path from "path";
5
+ import fs from "fs";
6
+
7
+ const customFormat = winston.format.printf(({ level, message, timestamp }) => {
8
+ const formattedMessage =
9
+ typeof message === "object" ? JSON.stringify(message, null, 2) : message;
10
+
11
+ let coloredLevel = level.toUpperCase();
12
+
13
+ switch (level) {
14
+ case "error":
15
+ coloredLevel = chalk.redBright.bold(level.toUpperCase());
16
+ break;
17
+ case "warn":
18
+ coloredLevel = chalk.yellowBright.bold(level.toUpperCase());
19
+ break;
20
+ case "info":
21
+ coloredLevel = chalk.greenBright.bold(level.toUpperCase());
22
+ break;
23
+ case "debug":
24
+ coloredLevel = chalk.bgBlueBright.bold(level.toUpperCase());
25
+ break;
26
+ default:
27
+ coloredLevel = chalk.bgBlueBright.bold(level.toUpperCase());
28
+ break;
29
+ }
30
+
31
+ return `[${chalk.gray(timestamp)}] ${coloredLevel} - ${formattedMessage}`;
32
+ });
33
+
34
+ export const logger = winston.createLogger({
35
+ level: "debug",
36
+ format: winston.format.combine(
37
+ winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
38
+ customFormat
39
+ ),
40
+ transports: [new winston.transports.Console()],
41
+ });
42
+
43
+ const logDir = path.join(__dirname, "../../logs");
44
+ if (!fs.existsSync(logDir)) {
45
+ fs.mkdirSync(logDir);
46
+ }
47
+
48
+ export const dbLogger = winston.createLogger({
49
+ level: "debug",
50
+ format: winston.format.combine(
51
+ winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
52
+ winston.format.printf(
53
+ ({ timestamp, level, message }) =>
54
+ `[${timestamp}] ${level.toUpperCase()}: ${message}`
55
+ )
56
+ ),
57
+ transports: [
58
+ new DailyRotateFile({
59
+ filename: path.join(logDir, "database-log-%DATE%.log"),
60
+ datePattern: "YYYY-MM-DD",
61
+ zippedArchive: false,
62
+ maxFiles: "30d",
63
+ maxSize: "10m",
64
+ }),
65
+ ],
66
+ });
67
+
68
+ // Format untuk FILE logs — tanpa warna
69
+ const plainFormat = winston.format.printf(({ timestamp, level, message }) => {
70
+ return `[${timestamp}] ${level.toUpperCase()}: ${message}`;
71
+ });
72
+
73
+ // Format untuk CONSOLE logs — dengan warna
74
+ const coloredHttpFormat = winston.format.printf(
75
+ ({ timestamp, level, message }) => {
76
+ let coloredLevel = level.toUpperCase();
77
+ switch (level) {
78
+ case "error":
79
+ coloredLevel = chalk.red.bold(level.toUpperCase());
80
+ break;
81
+ case "warn":
82
+ coloredLevel = chalk.yellow.bold(level.toUpperCase());
83
+ break;
84
+ case "info":
85
+ coloredLevel = chalk.blue.bold(level.toUpperCase());
86
+ break;
87
+ default:
88
+ coloredLevel = chalk.white(level.toUpperCase());
89
+ break;
90
+ }
91
+ return `[${chalk.gray(timestamp)}] ${coloredLevel}: ${message}`;
92
+ }
93
+ );
94
+
95
+ export const httpAccessLogger = winston.createLogger({
96
+ level: "debug",
97
+ transports: [
98
+ new DailyRotateFile({
99
+ filename: path.join(logDir, "access-log-%DATE%.log"),
100
+ datePattern: "YYYY-MM-DD",
101
+ zippedArchive: false,
102
+ maxFiles: "30d",
103
+ maxSize: "10m",
104
+ format: winston.format.combine(
105
+ winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
106
+ plainFormat
107
+ ),
108
+ }),
109
+ new winston.transports.Console({
110
+ format: winston.format.combine(
111
+ winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
112
+ coloredHttpFormat
113
+ ),
114
+ }),
115
+ ],
116
+ });
@@ -0,0 +1,40 @@
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import cookieParser from "cookie-parser";
4
+ import { env } from "./env";
5
+ // import expressLayouts from "express-ejs-layouts";
6
+ // import path from "path";
7
+
8
+ import { errorMiddleware } from "../middleware/error-middleware";
9
+ import { httpLogger } from "../middleware/http-logger-middleware";
10
+ import { errorResponse } from "../utils/response";
11
+ import { setupSwagger } from "../utils/swagger";
12
+ import { mainRouter } from "../routes/main-route";
13
+
14
+ export const web = express();
15
+ // EJS View Engine Setup
16
+ // web.set("view engine", "ejs");
17
+ // web.set("views", path.join(__dirname, "..", "views"));
18
+ // web.use(expressLayouts);
19
+ // web.set("layout", "layouts/main");
20
+
21
+ // Middleware
22
+ web.use(express.json());
23
+ web.use(cookieParser());
24
+ web.use(cors({ credentials: true, origin: `${env.CLIENT_URL}` }));
25
+ web.use(express.static("public"));
26
+ web.use(httpLogger);
27
+
28
+ // Swagger Setup
29
+ setupSwagger(web);
30
+
31
+ // Routes
32
+ web.use(mainRouter);
33
+
34
+ // 404 Handler
35
+ web.use((req, res) => {
36
+ res.status(404).json(errorResponse("Request Tidak Ada", 404));
37
+ });
38
+
39
+ // Global Error Handler
40
+ web.use(errorMiddleware);
@@ -0,0 +1,88 @@
1
+ import { NextFunction, Request, Response } from "express";
2
+ import {
3
+ loginRequest,
4
+ CreateUserRequest,
5
+ UpdateUserRequest,
6
+ } from "../dtos/user-dto";
7
+ import { successResponse, successUpdateResponse } from "../utils/response";
8
+ import { AuthService } from "../services/auth-service";
9
+ import { UserRequest } from "../types/type-request";
10
+ import { env } from "../config/env";
11
+
12
+ export class AuthController {
13
+ static async register(req: Request, res: Response, next: NextFunction) {
14
+ try {
15
+ const request: CreateUserRequest = req.body as CreateUserRequest;
16
+ const response = await AuthService.register(request);
17
+ res.status(201).json(successResponse("Register Berhasil", 201, response));
18
+ } catch (error) {
19
+ next(error);
20
+ }
21
+ }
22
+
23
+ static async login(req: Request, res: Response, next: NextFunction) {
24
+ try {
25
+ const request: loginRequest = req.body as loginRequest;
26
+ const response = await AuthService.login(request);
27
+ res.cookie("refresh_token", response.refreshToken, {
28
+ httpOnly: true,
29
+ maxAge: 24 * 60 * 60 * 1000,
30
+ secure: env.NODE_ENV === "production",
31
+ sameSite: "lax",
32
+ path: "/",
33
+ });
34
+ res.status(200).json(
35
+ successResponse("Login Berhasil", 200, {
36
+ user: response.user,
37
+ accessToken: response.accessToken,
38
+ })
39
+ );
40
+ } catch (error) {
41
+ next(error);
42
+ }
43
+ }
44
+
45
+ static async me(req: UserRequest, res: Response, next: NextFunction) {
46
+ try {
47
+ const response = await AuthService.me(req.user!);
48
+ res
49
+ .status(200)
50
+ .json(successResponse("Get Detail User Berhasil", 200, response));
51
+ } catch (error) {
52
+ next(error);
53
+ }
54
+ }
55
+
56
+ static async updateProfile(
57
+ req: UserRequest,
58
+ res: Response,
59
+ next: NextFunction
60
+ ) {
61
+ try {
62
+ const request: UpdateUserRequest = req.body as UpdateUserRequest;
63
+ const response = await AuthService.updateProfile(req.user!, request);
64
+ res.status(200).json(successUpdateResponse(response));
65
+ } catch (error) {
66
+ next(error);
67
+ }
68
+ }
69
+
70
+ static async logout(req: UserRequest, res: Response, next: NextFunction) {
71
+ await AuthService.logout(req);
72
+ res.clearCookie("refresh_token");
73
+ res.status(200).json(successResponse("Logout berhasil", 200));
74
+ }
75
+ static async refreshToken(req: Request, res: Response, next: NextFunction) {
76
+ try {
77
+ const response = await AuthService.refreshToken(req);
78
+ res.status(200).json(
79
+ successResponse("Get Access Token Berhasil", 200, {
80
+ user: response.user,
81
+ accessToken: response.accessToken,
82
+ })
83
+ );
84
+ } catch (error) {
85
+ next(error);
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,79 @@
1
+ import { NextFunction, Request, Response } from "express";
2
+ import {
3
+ CreateUserRequest,
4
+ ListUserRequest,
5
+ UpdateUserRequest,
6
+ } from "../dtos/user-dto";
7
+ import { UserService } from "../services/user-service";
8
+ import {
9
+ successCreateResponse,
10
+ successDeleteResponse,
11
+ successResponse,
12
+ successUpdateResponse,
13
+ } from "../utils/response";
14
+ import { UserRequest } from "../types/type-request";
15
+
16
+ export class UserController {
17
+ static async create(req: Request, res: Response, next: NextFunction) {
18
+ try {
19
+ const request: CreateUserRequest = req.body as CreateUserRequest;
20
+ console.log(request);
21
+ const response = await UserService.create(request);
22
+ res.status(201).json(successCreateResponse(response));
23
+ } catch (error) {
24
+ next(error);
25
+ }
26
+ }
27
+
28
+ static async getAll(req: Request, res: Response, next: NextFunction) {
29
+ try {
30
+ const page = Number(req.query.page) || 1;
31
+ const take = Number(req.query.take) || 10;
32
+ const request: ListUserRequest = {
33
+ page: page,
34
+ take: take,
35
+ skip: (page - 1) * take,
36
+ name: req.query.name as string,
37
+ };
38
+ const response = await UserService.getAll(request);
39
+ res
40
+ .status(200)
41
+ .json(successResponse("Berhasil Get All Data", 200, response));
42
+ } catch (e) {
43
+ next(e);
44
+ }
45
+ }
46
+
47
+ static async getOne(req: Request, res: Response, next: NextFunction) {
48
+ try {
49
+ const id = req.params.id;
50
+ const response = await UserService.getOne(id);
51
+ res
52
+ .status(200)
53
+ .json(successResponse("Berhasil Get Detail Data", 200, response));
54
+ } catch (e) {
55
+ next(e);
56
+ }
57
+ }
58
+
59
+ static async update(req: UserRequest, res: Response, next: NextFunction) {
60
+ try {
61
+ const id = req.params.id;
62
+ const request: UpdateUserRequest = req.body as UpdateUserRequest;
63
+ const response = await UserService.update(id, request);
64
+ res.status(200).json(successUpdateResponse(response));
65
+ } catch (error) {
66
+ next(error);
67
+ }
68
+ }
69
+
70
+ static async delete(req: Request, res: Response, next: NextFunction) {
71
+ try {
72
+ const id = req.params.id;
73
+ await UserService.delete(id);
74
+ res.status(200).json(successDeleteResponse());
75
+ } catch (e) {
76
+ next(e);
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,12 @@
1
+ export type listResponse = {
2
+ data: any;
3
+ total_data: number;
4
+ paging: any;
5
+ };
6
+ export function tolistResponse(data: any): listResponse {
7
+ return {
8
+ data: data.data,
9
+ total_data: data.total_data,
10
+ paging: data.paging,
11
+ };
12
+ }
@@ -0,0 +1,57 @@
1
+ import { User } from "@prisma/client";
2
+ export type loginRequest = {
3
+ email: string;
4
+ password: string;
5
+ };
6
+ export type CreateUserRequest = {
7
+ fullName: string;
8
+ email: string;
9
+ password: string;
10
+ };
11
+
12
+ export type UpdateUserRequest = {
13
+ fullName?: string;
14
+ email?: string;
15
+ password?: string;
16
+ };
17
+
18
+ export type ListUserRequest = {
19
+ page: number;
20
+ take: number;
21
+ skip: number;
22
+ name?: string;
23
+ };
24
+ export type UserDetailResponse = {
25
+ id: string;
26
+ fullName: string;
27
+ email: string;
28
+ image_id?: string;
29
+ image_url?: string;
30
+ created_at: Date;
31
+ updated_at: Date;
32
+ deleted_at?: Date;
33
+ };
34
+
35
+ export type UserResponse = {
36
+ id: string;
37
+ fullName: string;
38
+ email: string;
39
+ };
40
+
41
+ export function toUserDetailResponse(user: User): UserDetailResponse {
42
+ return {
43
+ id: user.id,
44
+ fullName: user.fullName,
45
+ email: user.email,
46
+ created_at: user.created_at,
47
+ updated_at: user.updated_at,
48
+ deleted_at: user.deleted_at!,
49
+ };
50
+ }
51
+ export function toUserResponse(user: User): UserResponse {
52
+ return {
53
+ id: user.id,
54
+ fullName: user.fullName,
55
+ email: user.email,
56
+ };
57
+ }
@@ -0,0 +1,28 @@
1
+ import { web } from "./config/web";
2
+ import { connectDatabase } from "./config/database";
3
+ import { env } from "./config/env";
4
+ import { logger } from "./config/logger";
5
+
6
+ async function startServer() {
7
+ try {
8
+ if (
9
+ !env.APP_SECRET ||
10
+ env.APP_SECRET.trim() === "" ||
11
+ !env.JWT_SECRET ||
12
+ env.JWT_SECRET.trim() === ""
13
+ ) {
14
+ logger.error("❌ APP_SECRET or JWT_SECRET is missing in your .env file.");
15
+ logger.error("👉 Please run `node craft key:generate` to create them.");
16
+ process.exit(0);
17
+ }
18
+ await connectDatabase();
19
+ web.listen(env.PORT, () => {
20
+ logger.info(`🚀 Server is listening on: ${env.BASE_URL}`);
21
+ logger.info(`🔗 API Docs available at: ${env.BASE_API_URL}/docs`);
22
+ });
23
+ } catch (error) {
24
+ process.exit(0);
25
+ }
26
+ }
27
+
28
+ startServer();
@@ -0,0 +1,44 @@
1
+ import { NextFunction, Response } from "express";
2
+ import { errorResponse } from "../utils/response";
3
+ import { prismaClient } from "../config/database";
4
+ import jwt from "jsonwebtoken";
5
+ import { UserRequest } from "../types/type-request";
6
+ import { ResponseError } from "../utils/response-error";
7
+ import { env } from "../config/env";
8
+
9
+ export const authMiddleware = async (
10
+ req: UserRequest,
11
+ res: Response,
12
+ next: NextFunction
13
+ ) => {
14
+ const refreshToken = req.cookies.refresh_token;
15
+ if (!refreshToken) {
16
+ throw new ResponseError(401, "Unauthorized: Anda Belum Login.");
17
+ }
18
+ const token = req.get("Authorization")?.split(" ")[1];
19
+ if (!token) {
20
+ return res
21
+ .status(401)
22
+ .json(errorResponse("Unauthorized: Access Token Tidak Valid.", 401));
23
+ }
24
+ let payload;
25
+ try {
26
+ payload = jwt.verify(token, env.JWT_SECRET as string) as {
27
+ user_id: string;
28
+ user_email: string;
29
+ user_fullName: string;
30
+ };
31
+ } catch (err) {
32
+ throw new ResponseError(401, "Unauthorized: Access Token Tidak Valid.");
33
+ }
34
+ const user = await prismaClient.user.findUnique({
35
+ where: { id: payload.user_id },
36
+ });
37
+ if (!user) {
38
+ return res
39
+ .status(401)
40
+ .json(errorResponse("Unauthorized: Anda belum login", 401));
41
+ }
42
+ req.user = user;
43
+ next();
44
+ };
@@ -0,0 +1,27 @@
1
+ import { NextFunction, Request, Response } from "express";
2
+ import { ZodError } from "zod";
3
+ import { errorResponse } from "../utils/response";
4
+ import { ResponseError } from "../utils/response-error";
5
+
6
+ export const errorMiddleware = async (
7
+ error: Error,
8
+ req: Request,
9
+ res: Response,
10
+ next: NextFunction
11
+ ) => {
12
+ if (error instanceof ZodError) {
13
+ let formattedErrors = error.errors.map((err) => ({
14
+ field: err.path.join("."),
15
+ message: err.message,
16
+ }));
17
+ res
18
+ .status(400)
19
+ .json(errorResponse("Validation Error", 400, formattedErrors));
20
+ } else if (error instanceof ResponseError) {
21
+ res
22
+ .status(error.status_code)
23
+ .json(errorResponse(error.message, error.status_code));
24
+ } else {
25
+ res.status(500).json(errorResponse(error.message, 500));
26
+ }
27
+ };
@@ -0,0 +1,31 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { httpAccessLogger } from "../config/logger";
3
+
4
+ export const httpLogger = async (
5
+ req: Request,
6
+ res: Response,
7
+ next: NextFunction
8
+ ) => {
9
+ const start = process.hrtime();
10
+
11
+ res.on("finish", () => {
12
+ const diff = process.hrtime(start);
13
+ const timeInMs = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(3);
14
+
15
+ const logMessage = `${req.method} ${req.originalUrl} ${res.statusCode} ${timeInMs} ms - ${res.get("Content-Length") || 0}`;
16
+
17
+ if (res.statusCode >= 200 && res.statusCode < 300) {
18
+ httpAccessLogger.info(logMessage);
19
+ } else if (res.statusCode >= 300 && res.statusCode < 400) {
20
+ httpAccessLogger.info(logMessage);
21
+ } else if (res.statusCode >= 400 && res.statusCode < 500) {
22
+ httpAccessLogger.warn(logMessage);
23
+ } else if (res.statusCode >= 500) {
24
+ httpAccessLogger.error(logMessage);
25
+ } else {
26
+ httpAccessLogger.debug(logMessage);
27
+ }
28
+ });
29
+
30
+ next();
31
+ };