create-express-kickstart 1.0.1 ā 1.1.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/bin/cli.js +45 -13
- package/package.json +1 -1
- package/nodejs-app/.env.example +0 -8
- package/nodejs-app/package.json +0 -22
- package/nodejs-app/src/app.js +0 -75
- package/nodejs-app/src/controllers/healthcheck.controller.js +0 -17
- package/nodejs-app/src/db/index.js +0 -14
- package/nodejs-app/src/middlewares/errorHandler.middleware.js +0 -37
- package/nodejs-app/src/models/example-model.js +0 -18
- package/nodejs-app/src/routes/healthcheck.routes.js +0 -9
- package/nodejs-app/src/server.js +0 -27
- package/nodejs-app/src/utils/ApiError.js +0 -23
- package/nodejs-app/src/utils/ApiResponse.js +0 -10
- package/nodejs-app/src/utils/asyncHandler.js +0 -7
- package/nodejs-app/src/utils/constants.js +0 -1
package/bin/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
@@ -21,11 +21,11 @@ async function init() {
|
|
|
21
21
|
|
|
22
22
|
let projectName = projectNameArg;
|
|
23
23
|
if (!projectName) {
|
|
24
|
-
projectName = await question('\nš Project
|
|
24
|
+
projectName = await question('\nš Project Directory Name (e.g. my-awesome-api): ');
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (!projectName) {
|
|
28
|
-
console.error('\nā Error: Project
|
|
28
|
+
console.error('\nā Error: Project Directory Name is required.');
|
|
29
29
|
process.exit(1);
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -33,10 +33,15 @@ async function init() {
|
|
|
33
33
|
const projectPath = path.join(currentPath, projectName);
|
|
34
34
|
|
|
35
35
|
if (fs.existsSync(projectPath)) {
|
|
36
|
-
console.error(`\nā Error: Folder ${projectName} already exists. Please choose a different name.\n`);
|
|
36
|
+
console.error(`\nā Error: Folder ${projectName} already exists. Please choose a different directory name.\n`);
|
|
37
37
|
process.exit(1);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
let packageJsonName = await question(`š package.json name (${projectName}): `);
|
|
41
|
+
if (!packageJsonName.trim()) {
|
|
42
|
+
packageJsonName = projectName; // Fallback to directory name
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
const description = await question('š Project description: ');
|
|
41
46
|
const author = await question('š Author name: ');
|
|
42
47
|
|
|
@@ -60,6 +65,13 @@ async function init() {
|
|
|
60
65
|
installPinoPretty = (await question('Include pino-pretty for clean development logs? [Y/n] ')).toLowerCase() !== 'n';
|
|
61
66
|
}
|
|
62
67
|
|
|
68
|
+
const packageManagerChoice = await question('\nš Which package manager would you like to use? [npm/yarn/pnpm/bun] (default: npm): ');
|
|
69
|
+
const packageManager = ['yarn', 'pnpm', 'bun'].includes(packageManagerChoice.trim().toLowerCase())
|
|
70
|
+
? packageManagerChoice.trim().toLowerCase()
|
|
71
|
+
: 'npm';
|
|
72
|
+
|
|
73
|
+
const initGit = (await question('\nš Initialize a git repository? [Y/n] ')).toLowerCase() !== 'n';
|
|
74
|
+
|
|
63
75
|
rl.close();
|
|
64
76
|
|
|
65
77
|
console.log(`\nš Creating a new Node.js Express API in ${projectPath}...`);
|
|
@@ -102,7 +114,7 @@ async function init() {
|
|
|
102
114
|
// 3. Create package.json
|
|
103
115
|
console.log(`š¦ Setting up package.json...`);
|
|
104
116
|
const packageJsonTemplate = {
|
|
105
|
-
name:
|
|
117
|
+
name: packageJsonName.trim(),
|
|
106
118
|
version: "1.0.0",
|
|
107
119
|
description: description || "A production-ready Node.js Express API",
|
|
108
120
|
main: "src/server.js",
|
|
@@ -123,13 +135,13 @@ async function init() {
|
|
|
123
135
|
packageJsonTemplate.scripts.format = "prettier --write \"src/**/*.{js,json}\"";
|
|
124
136
|
}
|
|
125
137
|
|
|
126
|
-
//
|
|
138
|
+
// Write package.json
|
|
127
139
|
fs.writeFileSync(
|
|
128
140
|
path.join(projectPath, 'package.json'),
|
|
129
141
|
JSON.stringify(packageJsonTemplate, null, 2)
|
|
130
142
|
);
|
|
131
143
|
|
|
132
|
-
//
|
|
144
|
+
// Install Dependencies
|
|
133
145
|
const dependenciesToInstall = Object.keys(deps).filter(dep => deps[dep] && dep !== 'prettier');
|
|
134
146
|
if (deps['pino-http']) {
|
|
135
147
|
dependenciesToInstall.push('pino');
|
|
@@ -143,30 +155,50 @@ async function init() {
|
|
|
143
155
|
|
|
144
156
|
console.log(`\nā³ Installing selected core dependencies (${dependenciesToInstall.join(', ')}). This might take a minute...`);
|
|
145
157
|
try {
|
|
158
|
+
let installCmd = packageManager === 'yarn' ? 'yarn add'
|
|
159
|
+
: packageManager === 'pnpm' ? 'pnpm add'
|
|
160
|
+
: packageManager === 'bun' ? 'bun add'
|
|
161
|
+
: 'npm install';
|
|
162
|
+
|
|
163
|
+
let installDevCmd = packageManager === 'yarn' ? 'yarn add -D'
|
|
164
|
+
: packageManager === 'pnpm' ? 'pnpm add -D'
|
|
165
|
+
: packageManager === 'bun' ? 'bun add -d'
|
|
166
|
+
: 'npm install --save-dev';
|
|
167
|
+
|
|
146
168
|
if (depString) {
|
|
147
|
-
execSync(
|
|
169
|
+
execSync(`${installCmd} ${depString}`, {
|
|
148
170
|
cwd: projectPath,
|
|
149
171
|
stdio: 'inherit'
|
|
150
172
|
});
|
|
151
173
|
}
|
|
152
174
|
|
|
153
175
|
console.log(`\nā³ Installing latest dev dependencies (${devDepString})...`);
|
|
154
|
-
execSync(
|
|
176
|
+
execSync(`${installDevCmd} ${devDepString}`, {
|
|
155
177
|
cwd: projectPath,
|
|
156
178
|
stdio: 'inherit'
|
|
157
179
|
});
|
|
158
180
|
|
|
181
|
+
if (initGit) {
|
|
182
|
+
console.log(`\nš± Initializing Git repository...`);
|
|
183
|
+
execSync('git init', { cwd: projectPath, stdio: 'inherit' });
|
|
184
|
+
// Create .gitignore
|
|
185
|
+
const gitignoreContent = "node_modules\n.env\ndist\nbuild\ncoverage\n";
|
|
186
|
+
fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignoreContent);
|
|
187
|
+
execSync('git add .', { cwd: projectPath, stdio: 'inherit' });
|
|
188
|
+
execSync('git commit -m "initial commit"', { cwd: projectPath, stdio: 'inherit' });
|
|
189
|
+
}
|
|
190
|
+
|
|
159
191
|
console.log(`\nā
Success! Created "${projectName}" at ${projectPath}`);
|
|
160
192
|
console.log('\nInside that directory, you can run several commands:');
|
|
161
|
-
console.log(
|
|
193
|
+
console.log(`\n ${packageManager === 'npm' ? 'npm run' : packageManager} dev`);
|
|
162
194
|
console.log(' Starts the development server on localhost.');
|
|
163
|
-
console.log(
|
|
195
|
+
console.log(`\n ${packageManager === 'npm' ? 'npm' : packageManager} start`);
|
|
164
196
|
console.log(' Starts the production server.');
|
|
165
197
|
console.log('\nWe suggest that you begin by typing:');
|
|
166
198
|
console.log(`\n cd ${projectName}`);
|
|
167
|
-
console.log('
|
|
199
|
+
console.log(` ${packageManager === 'npm' ? 'npm run' : packageManager} dev\n`);
|
|
168
200
|
} catch (err) {
|
|
169
|
-
console.error('\nā Failed to install dependencies. You may need to
|
|
201
|
+
console.error('\nā Failed to install dependencies. You may need to install them manually inside the folder.', err);
|
|
170
202
|
}
|
|
171
203
|
}
|
|
172
204
|
|
package/package.json
CHANGED
package/nodejs-app/.env.example
DELETED
package/nodejs-app/package.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
}
|
package/nodejs-app/src/app.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
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 };
|
|
@@ -1,17 +0,0 @@
|
|
|
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 };
|
|
@@ -1,14 +0,0 @@
|
|
|
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;
|
|
@@ -1,37 +0,0 @@
|
|
|
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 };
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
});
|
package/nodejs-app/src/server.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
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 }
|
|
@@ -1,10 +0,0 @@
|
|
|
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 }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const DB_NAME = "my_app_db";
|