create-express-kickstart 1.0.0 → 1.0.1
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/nodejs-app/.env.example +8 -0
- package/nodejs-app/package.json +22 -0
- package/nodejs-app/src/app.js +75 -0
- package/nodejs-app/src/controllers/healthcheck.controller.js +17 -0
- package/nodejs-app/src/db/index.js +14 -0
- package/nodejs-app/src/middlewares/errorHandler.middleware.js +37 -0
- package/nodejs-app/src/models/example-model.js +18 -0
- package/nodejs-app/src/routes/healthcheck.routes.js +9 -0
- package/nodejs-app/src/server.js +27 -0
- package/nodejs-app/src/utils/ApiError.js +23 -0
- package/nodejs-app/src/utils/ApiResponse.js +10 -0
- package/nodejs-app/src/utils/asyncHandler.js +7 -0
- package/nodejs-app/src/utils/constants.js +1 -0
- package/package.json +3 -4
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nodejs-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "description",
|
|
5
|
+
"main": "src/server.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node src/server.js",
|
|
9
|
+
"dev": "nodemon src/server.js",
|
|
10
|
+
"format": "prettier --write \"src/**/*.{js,json}\""
|
|
11
|
+
},
|
|
12
|
+
"imports": {
|
|
13
|
+
"#*": "./src/*"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"express",
|
|
17
|
+
"node",
|
|
18
|
+
"api"
|
|
19
|
+
],
|
|
20
|
+
"author": "aaaaaaaaaaa",
|
|
21
|
+
"license": "ISC"
|
|
22
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import cookieParser from "cookie-parser";
|
|
4
|
+
import helmet from "helmet";
|
|
5
|
+
import pinoHttp from "pino-http";
|
|
6
|
+
import rateLimit from "express-rate-limit";
|
|
7
|
+
import { errorHandler } from "#middlewares/errorHandler.middleware.js";
|
|
8
|
+
|
|
9
|
+
// Import routers
|
|
10
|
+
import healthcheckRouter from "#routes/healthcheck.routes.js";
|
|
11
|
+
|
|
12
|
+
const app = express();
|
|
13
|
+
|
|
14
|
+
// Security HTTP headers
|
|
15
|
+
app.use(helmet());
|
|
16
|
+
|
|
17
|
+
// Rate Limiting
|
|
18
|
+
const limiter = rateLimit({
|
|
19
|
+
windowMs: process.env.RATE_LIMIT_WINDOW_MS || 15 * 60 * 1000, // Default 15 minutes
|
|
20
|
+
limit: process.env.RATE_LIMIT_MAX || 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
|
21
|
+
standardHeaders: 'draft-7', // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
|
|
22
|
+
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
23
|
+
message: "Too many requests from this IP, please try again later"
|
|
24
|
+
});
|
|
25
|
+
app.use("/api", limiter); // Apply rate limiting to all API routes
|
|
26
|
+
|
|
27
|
+
// Logging
|
|
28
|
+
app.use(pinoHttp({
|
|
29
|
+
customLogLevel: function (req, res, err) {
|
|
30
|
+
if (res.statusCode >= 400 && res.statusCode < 500) {
|
|
31
|
+
return 'warn'
|
|
32
|
+
} else if (res.statusCode >= 500 || err) {
|
|
33
|
+
return 'error'
|
|
34
|
+
} else if (res.statusCode >= 300 && res.statusCode < 400) {
|
|
35
|
+
return 'silent'
|
|
36
|
+
}
|
|
37
|
+
return 'info'
|
|
38
|
+
},
|
|
39
|
+
// Dynamically require pino-pretty if in dev and it exists, else undefined
|
|
40
|
+
transport: process.env.NODE_ENV === "development" ? (function() {
|
|
41
|
+
try {
|
|
42
|
+
import("pino-pretty");
|
|
43
|
+
return {
|
|
44
|
+
target: 'pino-pretty',
|
|
45
|
+
options: { colorize: true }
|
|
46
|
+
};
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
})() : undefined
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// CORS setup
|
|
54
|
+
app.use(
|
|
55
|
+
cors({
|
|
56
|
+
origin: process.env.CORS_ORIGIN || "*", // Fallback to allowing everything
|
|
57
|
+
credentials: true, // Allow cookies with requests
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Payload sizes and forms
|
|
62
|
+
app.use(express.json({ limit: "16kb" }));
|
|
63
|
+
app.use(express.urlencoded({ extended: true, limit: "16kb" }));
|
|
64
|
+
app.use(express.static("public"));
|
|
65
|
+
app.use(cookieParser());
|
|
66
|
+
|
|
67
|
+
// -------- API ROUTES ---------
|
|
68
|
+
// Mount routers
|
|
69
|
+
app.use("/api/v1/healthcheck", healthcheckRouter);
|
|
70
|
+
|
|
71
|
+
// Global Error Handler
|
|
72
|
+
// Always add this as the very last middleware
|
|
73
|
+
app.use(errorHandler);
|
|
74
|
+
|
|
75
|
+
export { app };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ApiError } from "#utils/ApiError.js";
|
|
2
|
+
import { ApiResponse } from "#utils/ApiResponse.js";
|
|
3
|
+
import { asyncHandler } from "#utils/asyncHandler.js";
|
|
4
|
+
|
|
5
|
+
const healthcheck = asyncHandler(async (req, res) => {
|
|
6
|
+
// Basic health check
|
|
7
|
+
return res
|
|
8
|
+
.status(200)
|
|
9
|
+
.json(new ApiResponse(200, { status: "OK", timestamp: Date.now() }, "App is running smoothly"));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const triggerError = asyncHandler(async (req, res) => {
|
|
13
|
+
// Dummy route to test the global error handler
|
|
14
|
+
throw new ApiError(400, "This is a custom error thrown for testing purposes.");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export { healthcheck, triggerError };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
import { DB_NAME } from "#utils/constants.js";
|
|
3
|
+
|
|
4
|
+
const connectDB = async () => {
|
|
5
|
+
try {
|
|
6
|
+
const connectionInstance = await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`);
|
|
7
|
+
console.log(`\n MongoDB connected !! DB HOST: ${connectionInstance.connection.host}`);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
console.error("MONGODB connection FAILED ", error);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default connectDB;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ApiError } from '#utils/ApiError.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global Error Handler Middleware
|
|
5
|
+
* @param {Error} err
|
|
6
|
+
* @param {Request} req
|
|
7
|
+
* @param {Response} res
|
|
8
|
+
* @param {NextFunction} next
|
|
9
|
+
*/
|
|
10
|
+
const errorHandler = (err, req, res, next) => {
|
|
11
|
+
let error = err;
|
|
12
|
+
|
|
13
|
+
// If the error is not an instance of ApiError, transform it into one
|
|
14
|
+
if (!(error instanceof ApiError)) {
|
|
15
|
+
const statusCode = error.statusCode ? error.statusCode : 500;
|
|
16
|
+
const message = error.message || "Internal Server Error";
|
|
17
|
+
|
|
18
|
+
error = new ApiError(
|
|
19
|
+
statusCode,
|
|
20
|
+
message,
|
|
21
|
+
error?.errors || [], // Pass down any validation errors
|
|
22
|
+
err.stack // Keep the original stack trace
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Now format the consistent response
|
|
27
|
+
const response = {
|
|
28
|
+
...error,
|
|
29
|
+
message: error.message,
|
|
30
|
+
...(process.env.NODE_ENV === 'development' ? { stack: error.stack } : {})
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Send the JSON response
|
|
34
|
+
return res.status(error.statusCode).json(response);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { errorHandler };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//example-model.js
|
|
2
|
+
import mongoose from "mongoose";
|
|
3
|
+
|
|
4
|
+
const exampleSchema = new mongoose.Schema({
|
|
5
|
+
name: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: [true, "Name is required"],
|
|
8
|
+
},
|
|
9
|
+
age: {
|
|
10
|
+
type: Number,
|
|
11
|
+
required: [true, "Age is required"],
|
|
12
|
+
},
|
|
13
|
+
email: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: [true, "Email is required"],
|
|
16
|
+
unique: true,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import { app } from "#app.js";
|
|
3
|
+
|
|
4
|
+
// Load environment variables from .env file
|
|
5
|
+
dotenv.config({
|
|
6
|
+
path: './.env'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
import connectDB from "#db/index.js";
|
|
10
|
+
|
|
11
|
+
const PORT = process.env.PORT || 8000;
|
|
12
|
+
|
|
13
|
+
connectDB()
|
|
14
|
+
.then(() => {
|
|
15
|
+
app.listen(PORT, () => {
|
|
16
|
+
console.log(`Server is running at port : ${PORT}`);
|
|
17
|
+
});
|
|
18
|
+
})
|
|
19
|
+
.catch((err) => {
|
|
20
|
+
console.log("MONGO db connection failed !!! ", err);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
process.on("unhandledRejection", (err) => {
|
|
24
|
+
console.log("UNHANDLED REJECTION! Shutting down...");
|
|
25
|
+
console.log(err.name, err.message);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class ApiError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
statusCode,
|
|
4
|
+
message = "Something went wrong",
|
|
5
|
+
errors = [],
|
|
6
|
+
stack = ""
|
|
7
|
+
) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.data = null;
|
|
11
|
+
this.message = message;
|
|
12
|
+
this.success = false;
|
|
13
|
+
this.errors = errors;
|
|
14
|
+
|
|
15
|
+
if (stack) {
|
|
16
|
+
this.stack = stack;
|
|
17
|
+
} else {
|
|
18
|
+
Error.captureStackTrace(this, this.constructor);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { ApiError }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class ApiResponse {
|
|
2
|
+
constructor(statusCode, data, message = "Success") {
|
|
3
|
+
this.statusCode = statusCode;
|
|
4
|
+
this.data = data;
|
|
5
|
+
this.message = message;
|
|
6
|
+
this.success = statusCode < 400; // Success is true if status code is not an error level
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { ApiResponse }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DB_NAME = "my_app_db";
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-express-kickstart",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Production-ready CLI starter for Express APIs",
|
|
5
5
|
"main": "bin/cli.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-express-kickstart": "
|
|
7
|
+
"create-express-kickstart": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -21,6 +21,5 @@
|
|
|
21
21
|
"author": "Your Name",
|
|
22
22
|
"license": "ISC",
|
|
23
23
|
"type": "module",
|
|
24
|
-
"dependencies": {
|
|
25
|
-
}
|
|
24
|
+
"dependencies": {}
|
|
26
25
|
}
|