create-express-mongoose-app 1.0.1 → 1.0.2
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/README.md +5 -5
- package/package.json +1 -1
- package/my-test/.env.example +0 -21
- package/my-test/README.md +0 -137
- package/my-test/package.json +0 -38
- package/my-test/src/app.ts +0 -77
- package/my-test/src/config/database.ts +0 -54
- package/my-test/src/config/env.ts +0 -42
- package/my-test/src/config/winston-logger.ts +0 -75
- package/my-test/src/index.ts +0 -3
- package/my-test/src/middleware/error.middleware.ts +0 -88
- package/my-test/src/middleware/request-id.middleware.ts +0 -20
- package/my-test/src/modules/example/example.controller.ts +0 -45
- package/my-test/src/modules/example/example.model.ts +0 -31
- package/my-test/src/modules/example/example.route.ts +0 -20
- package/my-test/src/modules/example/example.service.ts +0 -92
- package/my-test/src/modules/example/example.validation.ts +0 -16
- package/my-test/src/server.ts +0 -57
- package/my-test/src/utils/apiError.ts +0 -63
- package/my-test/src/utils/asyncHandler.ts +0 -7
- package/my-test/src/utils/response.ts +0 -102
- package/my-test/src/utils/validationMiddleware.ts +0 -32
- package/my-test/tsconfig.json +0 -26
- package/my-test/yarn.lock +0 -1149
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
2
|
-
import {
|
|
3
|
-
getAllExamples,
|
|
4
|
-
getExampleById,
|
|
5
|
-
createExample,
|
|
6
|
-
updateExample,
|
|
7
|
-
deleteExample,
|
|
8
|
-
} from "./example.controller";
|
|
9
|
-
import { validateWithJoi } from "../../utils/validationMiddleware";
|
|
10
|
-
import { createExampleSchema, updateExampleSchema } from "./example.validation";
|
|
11
|
-
|
|
12
|
-
const router = Router();
|
|
13
|
-
|
|
14
|
-
router.get("/", getAllExamples);
|
|
15
|
-
router.get("/:id", getExampleById);
|
|
16
|
-
router.post("/", validateWithJoi(createExampleSchema), createExample);
|
|
17
|
-
router.put("/:id", validateWithJoi(updateExampleSchema), updateExample);
|
|
18
|
-
router.delete("/:id", deleteExample);
|
|
19
|
-
|
|
20
|
-
export default router;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { StatusCodes } from "http-status-codes";
|
|
2
|
-
import { ApiError } from "../../utils/apiError";
|
|
3
|
-
import Example, { IExample } from "./example.model";
|
|
4
|
-
|
|
5
|
-
export class ExampleService {
|
|
6
|
-
async findAll() {
|
|
7
|
-
try {
|
|
8
|
-
return await Example.find({ isActive: true })
|
|
9
|
-
.sort({ createdAt: -1 })
|
|
10
|
-
.lean();
|
|
11
|
-
} catch (error: any) {
|
|
12
|
-
throw ApiError.internal("Failed to retrieve examples");
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async findById(id: string): Promise<IExample> {
|
|
17
|
-
try {
|
|
18
|
-
const example = await Example.findById(id);
|
|
19
|
-
if (!example) {
|
|
20
|
-
throw ApiError.notFound("Example not found");
|
|
21
|
-
}
|
|
22
|
-
return example;
|
|
23
|
-
} catch (error: any) {
|
|
24
|
-
if (error instanceof ApiError) throw error;
|
|
25
|
-
if (error.kind === "ObjectId") {
|
|
26
|
-
throw ApiError.badRequest("Invalid ID format");
|
|
27
|
-
}
|
|
28
|
-
throw ApiError.internal("Failed to retrieve example");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async create(data: Partial<IExample>): Promise<IExample> {
|
|
33
|
-
try {
|
|
34
|
-
const example = new Example(data);
|
|
35
|
-
return await example.save();
|
|
36
|
-
} catch (error: any) {
|
|
37
|
-
if (error.name === "ValidationError") {
|
|
38
|
-
const messages = Object.values(error.errors)
|
|
39
|
-
.map((err: any) => err.message)
|
|
40
|
-
.join(", ");
|
|
41
|
-
throw ApiError.unprocessable(messages);
|
|
42
|
-
}
|
|
43
|
-
if (error.code === 11000) {
|
|
44
|
-
const field = Object.keys(error.keyPattern)[0];
|
|
45
|
-
throw ApiError.conflict(`${field} already exists`);
|
|
46
|
-
}
|
|
47
|
-
throw ApiError.internal("Failed to create example");
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async update(id: string, data: Partial<IExample>): Promise<IExample> {
|
|
52
|
-
try {
|
|
53
|
-
const example = await Example.findByIdAndUpdate(id, data, {
|
|
54
|
-
new: true,
|
|
55
|
-
runValidators: true,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
if (!example) {
|
|
59
|
-
throw ApiError.notFound("Example not found");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return example;
|
|
63
|
-
} catch (error: any) {
|
|
64
|
-
if (error instanceof ApiError) throw error;
|
|
65
|
-
if (error.kind === "ObjectId") {
|
|
66
|
-
throw ApiError.badRequest("Invalid ID format");
|
|
67
|
-
}
|
|
68
|
-
if (error.name === "ValidationError") {
|
|
69
|
-
const messages = Object.values(error.errors)
|
|
70
|
-
.map((err: any) => err.message)
|
|
71
|
-
.join(", ");
|
|
72
|
-
throw ApiError.unprocessable(messages);
|
|
73
|
-
}
|
|
74
|
-
throw ApiError.internal("Failed to update example");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async delete(id: string): Promise<void> {
|
|
79
|
-
try {
|
|
80
|
-
const result = await Example.findByIdAndDelete(id);
|
|
81
|
-
if (!result) {
|
|
82
|
-
throw ApiError.notFound("Example not found");
|
|
83
|
-
}
|
|
84
|
-
} catch (error: any) {
|
|
85
|
-
if (error instanceof ApiError) throw error;
|
|
86
|
-
if (error.kind === "ObjectId") {
|
|
87
|
-
throw ApiError.badRequest("Invalid ID format");
|
|
88
|
-
}
|
|
89
|
-
throw ApiError.internal("Failed to delete example");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import Joi from "joi";
|
|
2
|
-
|
|
3
|
-
export const createExampleSchema = Joi.object({
|
|
4
|
-
name: Joi.string().required().trim().messages({
|
|
5
|
-
"string.empty": "Name is required",
|
|
6
|
-
"any.required": "Name is required",
|
|
7
|
-
}),
|
|
8
|
-
description: Joi.string().optional().trim(),
|
|
9
|
-
isActive: Joi.boolean().optional().default(true),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
export const updateExampleSchema = Joi.object({
|
|
13
|
-
name: Joi.string().optional().trim(),
|
|
14
|
-
description: Joi.string().optional().trim(),
|
|
15
|
-
isActive: Joi.boolean().optional(),
|
|
16
|
-
});
|
package/my-test/src/server.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import http from "http";
|
|
2
|
-
import { createApp } from "./app";
|
|
3
|
-
import { config } from "./config/env";
|
|
4
|
-
import { connectDatabase, disconnectDatabase } from "./config/database";
|
|
5
|
-
import { logger } from "./config/winston-logger";
|
|
6
|
-
|
|
7
|
-
let server: http.Server;
|
|
8
|
-
|
|
9
|
-
export const startServer = async () => {
|
|
10
|
-
try {
|
|
11
|
-
// Connect to database
|
|
12
|
-
await connectDatabase();
|
|
13
|
-
|
|
14
|
-
// Create Express app
|
|
15
|
-
const app = createApp();
|
|
16
|
-
|
|
17
|
-
// Start server
|
|
18
|
-
server = http.createServer(app);
|
|
19
|
-
server.listen(config.port, () => {
|
|
20
|
-
logger.info(
|
|
21
|
-
`Server running on port ${config.port} in ${config.nodeEnv} mode`,
|
|
22
|
-
);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Graceful shutdown handlers
|
|
26
|
-
const shutdown = async (signal: string) => {
|
|
27
|
-
logger.info(`${signal} received, starting graceful shutdown...`);
|
|
28
|
-
|
|
29
|
-
const shutdownTimeout = setTimeout(() => {
|
|
30
|
-
logger.error("Graceful shutdown timeout, forcing exit");
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}, 10000);
|
|
33
|
-
|
|
34
|
-
server.close(async () => {
|
|
35
|
-
clearTimeout(shutdownTimeout);
|
|
36
|
-
await disconnectDatabase();
|
|
37
|
-
logger.info("Server closed gracefully");
|
|
38
|
-
process.exit(0);
|
|
39
|
-
});
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
43
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
44
|
-
|
|
45
|
-
process.on("unhandledRejection", (reason, promise) => {
|
|
46
|
-
logger.error("Unhandled Rejection at:", promise, "reason:", reason);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
process.on("uncaughtException", (error) => {
|
|
50
|
-
logger.error("Uncaught Exception:", error);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
});
|
|
53
|
-
} catch (error: any) {
|
|
54
|
-
logger.error("Failed to start server:", error.message);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { StatusCodes } from "http-status-codes";
|
|
2
|
-
|
|
3
|
-
export class ApiError extends Error {
|
|
4
|
-
statusCode: number;
|
|
5
|
-
isOperational: boolean;
|
|
6
|
-
errors?: Record<string, any> | string[];
|
|
7
|
-
|
|
8
|
-
constructor(
|
|
9
|
-
statusCode: number,
|
|
10
|
-
message: string,
|
|
11
|
-
isOperational: boolean = true,
|
|
12
|
-
errors?: Record<string, any> | string[],
|
|
13
|
-
) {
|
|
14
|
-
super(message);
|
|
15
|
-
Object.setPrototypeOf(this, ApiError.prototype);
|
|
16
|
-
this.statusCode = statusCode;
|
|
17
|
-
this.isOperational = isOperational;
|
|
18
|
-
this.errors = errors;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static badRequest(
|
|
22
|
-
message: string,
|
|
23
|
-
errors?: Record<string, any> | string[],
|
|
24
|
-
): ApiError {
|
|
25
|
-
return new ApiError(StatusCodes.BAD_REQUEST, message, true, errors);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static unauthorized(message: string = "Unauthorized"): ApiError {
|
|
29
|
-
return new ApiError(StatusCodes.UNAUTHORIZED, message);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
static forbidden(message: string = "Forbidden"): ApiError {
|
|
33
|
-
return new ApiError(StatusCodes.FORBIDDEN, message);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
static notFound(message: string = "Not found"): ApiError {
|
|
37
|
-
return new ApiError(StatusCodes.NOT_FOUND, message);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
static conflict(message: string = "Conflict"): ApiError {
|
|
41
|
-
return new ApiError(StatusCodes.CONFLICT, message);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
static unprocessable(
|
|
45
|
-
message: string = "Unprocessable entity",
|
|
46
|
-
errors?: Record<string, any>,
|
|
47
|
-
): ApiError {
|
|
48
|
-
return new ApiError(
|
|
49
|
-
StatusCodes.UNPROCESSABLE_ENTITY,
|
|
50
|
-
message,
|
|
51
|
-
true,
|
|
52
|
-
errors,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
static internal(message: string = "Internal server error"): ApiError {
|
|
57
|
-
return new ApiError(StatusCodes.INTERNAL_SERVER_ERROR, message, false);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
static serviceUnavailable(message: string = "Service unavailable"): ApiError {
|
|
61
|
-
return new ApiError(StatusCodes.SERVICE_UNAVAILABLE, message, false);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction, RequestHandler } from "express";
|
|
2
|
-
|
|
3
|
-
export const asyncHandler = (fn: RequestHandler): RequestHandler => {
|
|
4
|
-
return (req: Request, res: Response, next: NextFunction) => {
|
|
5
|
-
Promise.resolve(fn(req, res, next)).catch(next);
|
|
6
|
-
};
|
|
7
|
-
};
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { Response } from "express";
|
|
2
|
-
import { StatusCodes } from "http-status-codes";
|
|
3
|
-
|
|
4
|
-
interface IApiResponse<T> {
|
|
5
|
-
status: "success" | "error" | "info";
|
|
6
|
-
data?: T;
|
|
7
|
-
message?: string;
|
|
8
|
-
errors?: Record<string, any>;
|
|
9
|
-
statusCode?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class ApiResponseHandler {
|
|
13
|
-
static success<T>(
|
|
14
|
-
res: Response,
|
|
15
|
-
data: T,
|
|
16
|
-
message: string = "Success",
|
|
17
|
-
statusCode: number = StatusCodes.OK,
|
|
18
|
-
): Response {
|
|
19
|
-
return res.status(statusCode).json({
|
|
20
|
-
status: "success",
|
|
21
|
-
data,
|
|
22
|
-
message,
|
|
23
|
-
} as IApiResponse<T>);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
static created<T>(
|
|
27
|
-
res: Response,
|
|
28
|
-
data: T,
|
|
29
|
-
message: string = "Resource created successfully",
|
|
30
|
-
): Response {
|
|
31
|
-
return res.status(StatusCodes.CREATED).json({
|
|
32
|
-
status: "success",
|
|
33
|
-
data,
|
|
34
|
-
message,
|
|
35
|
-
} as IApiResponse<T>);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static error(
|
|
39
|
-
res: Response,
|
|
40
|
-
message: string,
|
|
41
|
-
statusCode: number = StatusCodes.BAD_REQUEST,
|
|
42
|
-
errors?: Record<string, any> | string[],
|
|
43
|
-
): Response {
|
|
44
|
-
return res.status(statusCode).json({
|
|
45
|
-
status: "error",
|
|
46
|
-
message,
|
|
47
|
-
errors,
|
|
48
|
-
statusCode,
|
|
49
|
-
} as IApiResponse<null>);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static info<T>(
|
|
53
|
-
res: Response,
|
|
54
|
-
message: string,
|
|
55
|
-
data?: T,
|
|
56
|
-
statusCode: number = StatusCodes.OK,
|
|
57
|
-
): Response {
|
|
58
|
-
return res.status(statusCode).json({
|
|
59
|
-
status: "info",
|
|
60
|
-
message,
|
|
61
|
-
data,
|
|
62
|
-
} as IApiResponse<T>);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
static noContent(res: Response): Response {
|
|
66
|
-
return res.status(StatusCodes.NO_CONTENT).send();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
static paginated<T>(
|
|
70
|
-
res: Response,
|
|
71
|
-
data: T[],
|
|
72
|
-
total: number,
|
|
73
|
-
page: number,
|
|
74
|
-
limit: number,
|
|
75
|
-
message: string = "Retrieved successfully",
|
|
76
|
-
): Response {
|
|
77
|
-
return res.status(StatusCodes.OK).json({
|
|
78
|
-
status: "success",
|
|
79
|
-
data,
|
|
80
|
-
message,
|
|
81
|
-
pagination: {
|
|
82
|
-
total,
|
|
83
|
-
page,
|
|
84
|
-
limit,
|
|
85
|
-
pages: Math.ceil(total / limit),
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
static buildResponse<T>(
|
|
91
|
-
data: T,
|
|
92
|
-
message: string,
|
|
93
|
-
statusCode: number = StatusCodes.OK,
|
|
94
|
-
): IApiResponse<T> {
|
|
95
|
-
return {
|
|
96
|
-
status: "success",
|
|
97
|
-
data,
|
|
98
|
-
message,
|
|
99
|
-
statusCode,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import { Schema } from "joi";
|
|
3
|
-
import { ApiError } from "./apiError";
|
|
4
|
-
import { StatusCodes } from "http-status-codes";
|
|
5
|
-
|
|
6
|
-
export const validateWithJoi = (schema: Schema) => {
|
|
7
|
-
return (req: Request, res: Response, next: NextFunction) => {
|
|
8
|
-
const { error, value } = schema.validate(req.body, {
|
|
9
|
-
abortEarly: false,
|
|
10
|
-
stripUnknown: true,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
if (error) {
|
|
14
|
-
const errors: Record<string, any> = {};
|
|
15
|
-
error.details.forEach((detail) => {
|
|
16
|
-
errors[detail.path.join(".")] = detail.message;
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
return next(
|
|
20
|
-
new ApiError(
|
|
21
|
-
StatusCodes.UNPROCESSABLE_ENTITY,
|
|
22
|
-
"Validation failed",
|
|
23
|
-
true,
|
|
24
|
-
errors,
|
|
25
|
-
),
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
req.body = value;
|
|
30
|
-
next();
|
|
31
|
-
};
|
|
32
|
-
};
|
package/my-test/tsconfig.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"lib": ["ES2020"],
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"declarationMap": true,
|
|
15
|
-
"sourceMap": true,
|
|
16
|
-
"paths": {
|
|
17
|
-
"@/*": ["./*"],
|
|
18
|
-
"@modules/*": ["./modules/*"],
|
|
19
|
-
"@config/*": ["./config/*"],
|
|
20
|
-
"@middleware/*": ["./middleware/*"],
|
|
21
|
-
"@utils/*": ["./utils/*"]
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"include": ["src/**/*"],
|
|
25
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
26
|
-
}
|