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 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 name (e.g. my-awesome-api): ');
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 name is required.');
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: projectName,
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
- // Remove the readline dependency from the generated boilerplate if mistakenly mixed
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
- // 4. Install Dependencies
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(`npm install ${depString}`, {
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(`npm install ${devDepString} --save-dev`, {
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('\n npm run dev');
193
+ console.log(`\n ${packageManager === 'npm' ? 'npm run' : packageManager} dev`);
162
194
  console.log(' Starts the development server on localhost.');
163
- console.log('\n npm start');
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(' npm run dev\n');
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 run npm install manually inside the folder.', err);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-express-kickstart",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Production-ready CLI starter for Express APIs",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -1,8 +0,0 @@
1
- PORT=8000
2
- MONGODB_URI=mongodb://localhost:27017/
3
- CORS_ORIGIN=*
4
- NODE_ENV=development
5
-
6
- # Rate Limiting
7
- RATE_LIMIT_WINDOW_MS=900000 # 15 minutes in milliseconds
8
- RATE_LIMIT_MAX=100 # Maximum requests per windowMs
@@ -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
- }
@@ -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
- });
@@ -1,9 +0,0 @@
1
- import { Router } from 'express';
2
- import { healthcheck, triggerError } from '#controllers/healthcheck.controller.js';
3
-
4
- const router = Router();
5
-
6
- router.route('/').get(healthcheck);
7
- router.route('/error').get(triggerError);
8
-
9
- export default router;
@@ -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,7 +0,0 @@
1
- const asyncHandler = (requestHandler) => {
2
- return (req, res, next) => {
3
- Promise.resolve(requestHandler(req, res, next)).catch((err) => next(err));
4
- };
5
- };
6
-
7
- export { asyncHandler };
@@ -1 +0,0 @@
1
- export const DB_NAME = "my_app_db";