create-express-kickstart 1.2.4 → 1.2.6
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 +55 -9
- package/bin/cli.js +49 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,9 +6,36 @@
|
|
|
6
6
|
|
|
7
7
|
A powerful CLI tool to instantly scaffold a production-ready, feature-rich backend Node.js template specifically tailored for Express API applications. It adheres to modern best practices, providing standard structures for error handling, CORS setups, routing, and middlewares right out of the box.
|
|
8
8
|
|
|
9
|
+
## What is `create-express-kickstart`?
|
|
10
|
+
|
|
11
|
+
**The Purpose:**
|
|
12
|
+
Whenever developers start a new Node.js & Express.js project, they often spend the first couple of hours writing the exact same boilerplate: configuring `express`, setting up `cors`, managing `dotenv`, writing global error handlers, standardizing API responses, and configuring database connections. `create-express-kickstart` exists to entirely eliminate this repetitive chore. Our goal is to provide a production-grade backend right out of the gate so you can immediately focus on writing business logic. AI assistants and developers alike can rely on this tool to reliably scaffold consistent, secure, and modern Node.js backends.
|
|
13
|
+
|
|
14
|
+
**What It Does:**
|
|
15
|
+
It is an interactive CLI framework scaffolding generator. Upon running the command, it asks you a series of simple questions regarding the architecture of your new API (e.g., Do you want MongoDB? Do you want JWT Auth Boilerplate? Docker? Jest for testing?). Based on your exact answers, it instantly generates a fully configured, running codebase tailored exclusively to your project's needs.
|
|
16
|
+
|
|
17
|
+
**How It Works:**
|
|
18
|
+
Under the hood, the CLI runs dynamically directly from NPM via `npx` executing a Node.js compiler script:
|
|
19
|
+
1. **Interactive Prompting:** The CLI polls for your configurations in real-time.
|
|
20
|
+
2. **Selective Templating:** It recursively copies a pre-configured, highly modular `src` application design into your directory.
|
|
21
|
+
3. **Intelligent Modding:** If you selectively opt-out of specific modules (like CORS, Pino, or Mongoose), the CLI acts like an auto-linter—parsing and cleanly stripping those specific unused imports and middleware usages cleanly out of the core application. You only receive the code you explicitly asked for, preventing dead-weight execution bloat.
|
|
22
|
+
4. **Smart Dependency Resolution:** Ultimately, it dynamically computes a final `package.json` structure wrapping all selected tools directly toward their `latest` versions, seamlessly bypassing localized cache bugs—subsequently initiating the true install layer utilizing your selected manager (`npm`, `yarn`, `pnpm`, or `bun`).
|
|
23
|
+
|
|
24
|
+
**What is Inside (The Architecture):**
|
|
25
|
+
The generated Express template champions the **MVC (Model-View-Controller)** pattern with robust modern Node.js Path Aliasing bindings enabled out of the box:
|
|
26
|
+
- **/src/controllers** - Functional logic handlers.
|
|
27
|
+
- **/src/routes** - Isolated Express routers mapping precise endpoints to controllers.
|
|
28
|
+
- **/src/middlewares** - Pre-configured intercepts including a robust Global errorHandler.
|
|
29
|
+
- **/src/utils** - Core toolkit items mapped globally across the codebase. Highlights include:
|
|
30
|
+
- `ApiResponse` structure class for predictable and formatted JSON HTTP payloads.
|
|
31
|
+
- `ApiError` extension class for standardizing HTTP error interceptions.
|
|
32
|
+
- `asyncHandler` functional wrapper intercepting promise rejections seamlessly to avoid repetitive try-catch blocks in your controllers!
|
|
33
|
+
- **Optional Add-ons** - Complete JWT Authentication logic integration featuring secure cryptographic generation functions (`bcryptjs`), standardized .env setups, Dockerfile templates, and Jest assertion pipelines.
|
|
34
|
+
|
|
35
|
+
|
|
9
36
|
---
|
|
10
37
|
|
|
11
|
-
##
|
|
38
|
+
## Getting Started
|
|
12
39
|
|
|
13
40
|
You do not need to clone this repository, install dependencies manually, or write an initial configuration yourself. Use `npx` (which comes with npm 5.2+) to instantly generate your backend boilerplate!
|
|
14
41
|
|
|
@@ -41,7 +68,7 @@ npm run dev
|
|
|
41
68
|
|
|
42
69
|
---
|
|
43
70
|
|
|
44
|
-
##
|
|
71
|
+
## Features
|
|
45
72
|
|
|
46
73
|
- **Modern JavaScript**: ES6 Modules (`import`/`export`) enabled by default.
|
|
47
74
|
- **Robust Error Handling**: Centralized error management using custom `ApiError` and `errorHandler` middleware.
|
|
@@ -54,7 +81,7 @@ npm run dev
|
|
|
54
81
|
|
|
55
82
|
---
|
|
56
83
|
|
|
57
|
-
##
|
|
84
|
+
## Core Utilities Built-In
|
|
58
85
|
|
|
59
86
|
This template shines in its standardized utilities available out of the box for you:
|
|
60
87
|
|
|
@@ -82,10 +109,29 @@ const restrictedRoute = asyncHandler(async (req, res) => {
|
|
|
82
109
|
|
|
83
110
|
### `asyncHandler`
|
|
84
111
|
A wrapper for your async route handlers that eliminates the need for repetitive `try-catch` blocks.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
|
|
113
|
+
### `hash.util.js`
|
|
114
|
+
If you choose to install the basic JWT Auth boilerplate, we automatically generate a generic hashing utility utilizing `bcryptjs` to help you securely hash and compare data (like passwords) natively.
|
|
115
|
+
```javascript
|
|
116
|
+
import { hashData, compareData } from "#utils/hash.util.js";
|
|
117
|
+
|
|
118
|
+
const registerUser = asyncHandler(async (req, res) => {
|
|
119
|
+
const hashedPassword = await hashData("supersecret123");
|
|
120
|
+
// Store hashedPassword...
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const loginUser = asyncHandler(async (req, res) => {
|
|
124
|
+
const isMatch = await compareData("supersecret123", user.hashedPassword);
|
|
125
|
+
// ...
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Contributing & Repository
|
|
130
|
+
|
|
131
|
+
Love this tool? Want to add a feature or fix a bug?
|
|
132
|
+
Feel free to open an issue or submit a pull request on our GitHub Repository!
|
|
133
|
+
|
|
134
|
+
**GitHub Repository:** [https://github.com/aasifashraf/create-express-kickstart](https://github.com/aasifashraf/create-express-kickstart)
|
|
135
|
+
|
|
136
|
+
**NPM Package:** [https://www.npmjs.com/package/create-express-kickstart](https://www.npmjs.com/package/create-express-kickstart)
|
|
91
137
|
|
package/bin/cli.js
CHANGED
|
@@ -21,11 +21,11 @@ async function init() {
|
|
|
21
21
|
|
|
22
22
|
let projectName = projectNameArg;
|
|
23
23
|
if (!projectName) {
|
|
24
|
-
projectName = await question('\n
|
|
24
|
+
projectName = await question('\n> Project Directory Name (e.g. my-awesome-api): ');
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (!projectName) {
|
|
28
|
-
console.error('\
|
|
28
|
+
console.error('\nError: Project Directory Name is required.');
|
|
29
29
|
process.exit(1);
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -33,19 +33,19 @@ async function init() {
|
|
|
33
33
|
const projectPath = path.join(currentPath, projectName);
|
|
34
34
|
|
|
35
35
|
if (fs.existsSync(projectPath)) {
|
|
36
|
-
console.error(`\
|
|
36
|
+
console.error(`\nError: 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(
|
|
40
|
+
let packageJsonName = await question(`> package.json name (${projectName}): `);
|
|
41
41
|
if (!packageJsonName.trim()) {
|
|
42
42
|
packageJsonName = projectName; // Fallback to directory name
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const description = await question('
|
|
46
|
-
const author = await question('
|
|
45
|
+
const description = await question('> Project description: ');
|
|
46
|
+
const author = await question('> Author name: ');
|
|
47
47
|
|
|
48
|
-
console.log('\n---
|
|
48
|
+
console.log('\n--- Select Dependencies ---');
|
|
49
49
|
console.log('Press Enter for Yes (Y), type "n" for No.\n');
|
|
50
50
|
|
|
51
51
|
const deps = {
|
|
@@ -65,19 +65,19 @@ async function init() {
|
|
|
65
65
|
installPinoPretty = (await question('Include pino-pretty for clean development logs? [Y/n] ')).toLowerCase() !== 'n';
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const packageManagerChoice = await question('\n
|
|
68
|
+
const packageManagerChoice = await question('\n> Which package manager would you like to use? [npm/yarn/pnpm/bun] (default: npm): ');
|
|
69
69
|
const packageManager = ['yarn', 'pnpm', 'bun'].includes(packageManagerChoice.trim().toLowerCase())
|
|
70
70
|
? packageManagerChoice.trim().toLowerCase()
|
|
71
71
|
: 'npm';
|
|
72
72
|
|
|
73
|
-
const initGit = (await question('\n
|
|
74
|
-
const initDocker = (await question('
|
|
75
|
-
const initAuth = (await question('
|
|
76
|
-
const initTests = (await question('
|
|
73
|
+
const initGit = (await question('\n> Initialize a git repository? [Y/n] ')).toLowerCase() !== 'n';
|
|
74
|
+
const initDocker = (await question('> Include Dockerfile & docker-compose.yml? [Y/n] ')).toLowerCase() !== 'n';
|
|
75
|
+
const initAuth = (await question('> Include basic JWT Auth boilerplate? [Y/n] ')).toLowerCase() !== 'n';
|
|
76
|
+
const initTests = (await question('> Include Jest setup and boilerplate tests? [Y/n] ')).toLowerCase() !== 'n';
|
|
77
77
|
|
|
78
78
|
rl.close();
|
|
79
79
|
|
|
80
|
-
console.log(`\n
|
|
80
|
+
console.log(`\n Creating a new Node.js Express API in ${projectPath}...`);
|
|
81
81
|
fs.mkdirSync(projectPath, { recursive: true });
|
|
82
82
|
|
|
83
83
|
function copyRecursiveSync(src, dest) {
|
|
@@ -99,15 +99,15 @@ async function init() {
|
|
|
99
99
|
const targetSrcDir = path.join(projectPath, 'src');
|
|
100
100
|
|
|
101
101
|
if (!fs.existsSync(sourceDir)) {
|
|
102
|
-
console.error('\
|
|
102
|
+
console.error('\nError: Could not find "src" directory in the template generator.');
|
|
103
103
|
process.exit(1);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
console.log(
|
|
106
|
+
console.log(` Bootstrapping application structure (errorHandler, ApiResponse, async handlers)...`);
|
|
107
107
|
copyRecursiveSync(sourceDir, targetSrcDir);
|
|
108
108
|
|
|
109
109
|
// 2. Copy .env.example
|
|
110
|
-
console.log(
|
|
110
|
+
console.log(` Generating environment files...`);
|
|
111
111
|
const envExamplePath = path.join(__dirname, '..', '.env.example');
|
|
112
112
|
if (fs.existsSync(envExamplePath)) {
|
|
113
113
|
fs.copyFileSync(envExamplePath, path.join(projectPath, '.env.example'));
|
|
@@ -115,7 +115,7 @@ async function init() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (initDocker) {
|
|
118
|
-
console.log(
|
|
118
|
+
console.log(` Adding Docker files...`);
|
|
119
119
|
const dockerfilePath = path.join(__dirname, '..', 'templates', 'Dockerfile');
|
|
120
120
|
const dockerComposePath = path.join(__dirname, '..', 'templates', 'docker-compose.yml');
|
|
121
121
|
|
|
@@ -127,7 +127,7 @@ async function init() {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
if (initAuth) {
|
|
130
|
-
console.log(
|
|
130
|
+
console.log(` Adding Auth templates...`);
|
|
131
131
|
// Need to ensure directories exist
|
|
132
132
|
fs.mkdirSync(path.join(projectPath, 'src', 'controllers'), { recursive: true });
|
|
133
133
|
fs.mkdirSync(path.join(projectPath, 'src', 'middlewares'), { recursive: true });
|
|
@@ -147,13 +147,33 @@ async function init() {
|
|
|
147
147
|
path.join(projectPath, 'src', 'routes', 'auth.routes.js')
|
|
148
148
|
);
|
|
149
149
|
|
|
150
|
-
// Append JWT secret to env example
|
|
151
|
-
fs.appendFileSync(path.join(projectPath, '.env.example'), '\nJWT_SECRET=supersecretjwtkey123\n');
|
|
152
|
-
fs.appendFileSync(path.join(projectPath, '.env'), '\nJWT_SECRET=supersecretjwtkey123\n');
|
|
150
|
+
// Append JWT secret and Salt Rounds to env example
|
|
151
|
+
fs.appendFileSync(path.join(projectPath, '.env.example'), '\nJWT_SECRET=supersecretjwtkey123\nBCRYPT_SALT=10\n');
|
|
152
|
+
fs.appendFileSync(path.join(projectPath, '.env'), '\nJWT_SECRET=supersecretjwtkey123\nBCRYPT_SALT=10\n');
|
|
153
|
+
|
|
154
|
+
const utilsPath = path.join(projectPath, 'src', 'utils');
|
|
155
|
+
if (!fs.existsSync(utilsPath)) {
|
|
156
|
+
fs.mkdirSync(utilsPath, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
fs.writeFileSync(
|
|
159
|
+
path.join(utilsPath, 'hash.util.js'),
|
|
160
|
+
`import bcrypt from "bcryptjs";
|
|
161
|
+
|
|
162
|
+
export const hashData = async (data, saltRounds = process.env.BCRYPT_SALT) => {
|
|
163
|
+
const salt = await bcrypt.genSalt(Number(saltRounds) || 10);
|
|
164
|
+
return await bcrypt.hash(data, salt);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const compareData = async (data, hashedData) => {
|
|
168
|
+
return await bcrypt.compare(data, hashedData);
|
|
169
|
+
};
|
|
170
|
+
`
|
|
171
|
+
);
|
|
172
|
+
|
|
153
173
|
}
|
|
154
174
|
|
|
155
175
|
if (initTests) {
|
|
156
|
-
console.log(
|
|
176
|
+
console.log(` Adding Jest test templates...`);
|
|
157
177
|
fs.mkdirSync(path.join(projectPath, 'tests'), { recursive: true });
|
|
158
178
|
fs.copyFileSync(
|
|
159
179
|
path.join(__dirname, '..', 'templates', 'tests', 'healthcheck.test.js'),
|
|
@@ -225,7 +245,7 @@ async function init() {
|
|
|
225
245
|
}
|
|
226
246
|
|
|
227
247
|
// 3. Create package.json
|
|
228
|
-
console.log(
|
|
248
|
+
console.log(` Setting up package.json...`);
|
|
229
249
|
const packageJsonTemplate = {
|
|
230
250
|
name: packageJsonName.trim(),
|
|
231
251
|
version: "1.0.0",
|
|
@@ -279,7 +299,7 @@ async function init() {
|
|
|
279
299
|
|
|
280
300
|
// Inject dependencies directly into package.json instead of doing them via raw arguments.
|
|
281
301
|
// This perfectly bypasses PNPM / YARN / BUN specific registry caching bugs when downloading deeply nested trees.
|
|
282
|
-
console.log(`\n
|
|
302
|
+
console.log(`\n Configuring ${packageManager} and resolving dependency trees...`);
|
|
283
303
|
const finalPackageJsonPath = path.join(projectPath, 'package.json');
|
|
284
304
|
const finalPackageJsonCode = JSON.parse(fs.readFileSync(finalPackageJsonPath, 'utf8'));
|
|
285
305
|
|
|
@@ -294,12 +314,12 @@ async function init() {
|
|
|
294
314
|
|
|
295
315
|
fs.writeFileSync(finalPackageJsonPath, JSON.stringify(finalPackageJsonCode, null, 2));
|
|
296
316
|
|
|
297
|
-
console.log(`\n
|
|
317
|
+
console.log(`\n Running final installation via ${packageManager} (This might take a minute)...`);
|
|
298
318
|
const installTriggerCmd = packageManager === 'npm' ? 'npm install' : `${packageManager} install`;
|
|
299
319
|
execSync(installTriggerCmd, execConfig);
|
|
300
320
|
|
|
301
321
|
if (initGit) {
|
|
302
|
-
console.log(`\n
|
|
322
|
+
console.log(`\n Initializing Git repository...`);
|
|
303
323
|
execSync('git init', { cwd: projectPath, stdio: 'inherit' });
|
|
304
324
|
// Create .gitignore
|
|
305
325
|
const gitignoreContent = "node_modules\n.env\ndist\nbuild\ncoverage\n";
|
|
@@ -308,7 +328,7 @@ async function init() {
|
|
|
308
328
|
execSync('git commit -m "initial commit"', { cwd: projectPath, stdio: 'inherit' });
|
|
309
329
|
}
|
|
310
330
|
|
|
311
|
-
console.log(`\n
|
|
331
|
+
console.log(`\n Success! Created "${projectName}" at ${projectPath}`);
|
|
312
332
|
console.log('\nInside that directory, you can run several commands:');
|
|
313
333
|
console.log(`\n ${packageManager === 'npm' ? 'npm run' : packageManager} dev`);
|
|
314
334
|
console.log(' Starts the development server on localhost.');
|
|
@@ -318,11 +338,11 @@ async function init() {
|
|
|
318
338
|
console.log(`\n cd ${projectName}`);
|
|
319
339
|
console.log(` ${packageManager === 'npm' ? 'npm run' : packageManager} dev\n`);
|
|
320
340
|
} catch (err) {
|
|
321
|
-
console.error('\
|
|
341
|
+
console.error('\nFailed to install dependencies. You may need to install them manually inside the folder.', err);
|
|
322
342
|
}
|
|
323
343
|
}
|
|
324
344
|
|
|
325
345
|
init().catch(err => {
|
|
326
|
-
console.error('\
|
|
346
|
+
console.error('\nUnexpected error occurred:', err);
|
|
327
347
|
process.exit(1);
|
|
328
348
|
});
|