nodejs-quickstart-structure 1.1.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.
Files changed (65) hide show
  1. package/README.md +98 -0
  2. package/bin/index.js +66 -0
  3. package/docs/demo.gif +0 -0
  4. package/docs/generateCase.md +58 -0
  5. package/lib/generator.js +315 -0
  6. package/lib/prompts.js +76 -0
  7. package/package.json +40 -0
  8. package/templates/clean-architecture/js/src/domain/models/User.js +9 -0
  9. package/templates/clean-architecture/js/src/domain/repositories/UserRepository.js +9 -0
  10. package/templates/clean-architecture/js/src/index.js.ejs +37 -0
  11. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +22 -0
  12. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +19 -0
  13. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +31 -0
  14. package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.js +23 -0
  15. package/templates/clean-architecture/js/src/interfaces/controllers/UserController.js +30 -0
  16. package/templates/clean-architecture/js/src/interfaces/routes/api.js +77 -0
  17. package/templates/clean-architecture/js/src/usecases/CreateUser.js +14 -0
  18. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +12 -0
  19. package/templates/clean-architecture/js/src/utils/httpCodes.js +9 -0
  20. package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +24 -0
  21. package/templates/clean-architecture/ts/src/domain/user.ts +7 -0
  22. package/templates/clean-architecture/ts/src/index.ts.ejs +71 -0
  23. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +22 -0
  24. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +18 -0
  25. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts +45 -0
  26. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +76 -0
  27. package/templates/clean-architecture/ts/src/usecases/createUser.ts +13 -0
  28. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +10 -0
  29. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +7 -0
  30. package/templates/common/.dockerignore +10 -0
  31. package/templates/common/.eslintrc.json.ejs +26 -0
  32. package/templates/common/.lintstagedrc +6 -0
  33. package/templates/common/Dockerfile +49 -0
  34. package/templates/common/README.md.ejs +87 -0
  35. package/templates/common/_gitignore +5 -0
  36. package/templates/common/database/js/database.js.ejs +20 -0
  37. package/templates/common/database/js/models/User.js.ejs +30 -0
  38. package/templates/common/database/ts/database.ts.ejs +22 -0
  39. package/templates/common/database/ts/models/User.ts.ejs +34 -0
  40. package/templates/common/docker-compose.yml.ejs +93 -0
  41. package/templates/common/jest.config.js.ejs +11 -0
  42. package/templates/common/kafka/js/config/kafka.js +8 -0
  43. package/templates/common/kafka/js/services/kafkaService.js +29 -0
  44. package/templates/common/kafka/ts/config/kafka.ts +6 -0
  45. package/templates/common/kafka/ts/services/kafkaService.ts +36 -0
  46. package/templates/common/package.json.ejs +78 -0
  47. package/templates/common/tsconfig.json +19 -0
  48. package/templates/common/views/ejs/index.ejs +31 -0
  49. package/templates/common/views/pug/index.pug +26 -0
  50. package/templates/db/mysql/V1__Initial_Setup.sql +9 -0
  51. package/templates/db/postgres/V1__Initial_Setup.sql +9 -0
  52. package/templates/mvc/js/src/config/database.js +12 -0
  53. package/templates/mvc/js/src/config/swagger.js +23 -0
  54. package/templates/mvc/js/src/controllers/userController.js +14 -0
  55. package/templates/mvc/js/src/controllers/userController.js.ejs +23 -0
  56. package/templates/mvc/js/src/index.js.ejs +81 -0
  57. package/templates/mvc/js/src/routes/api.js +74 -0
  58. package/templates/mvc/js/src/utils/httpCodes.js +9 -0
  59. package/templates/mvc/js/src/utils/logger.js +30 -0
  60. package/templates/mvc/ts/src/config/swagger.ts.ejs +24 -0
  61. package/templates/mvc/ts/src/controllers/userController.ts.ejs +32 -0
  62. package/templates/mvc/ts/src/index.ts.ejs +89 -0
  63. package/templates/mvc/ts/src/routes/api.ts +76 -0
  64. package/templates/mvc/ts/src/utils/httpCodes.ts +7 -0
  65. package/templates/mvc/ts/src/utils/logger.ts +22 -0
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Node.js Quickstart Generator
2
+
3
+ A powerful CLI tool to scaffold production-ready Node.js microservices with built-in best practices, allowing you to choose between **MVC** or **Clean Architecture**, **JavaScript** or **TypeScript**, and your preferred database.
4
+
5
+ ![Demo](docs/demo.gif)
6
+
7
+ ## Features
8
+
9
+ - **Interactive CLI**: Easy-to-use prompts to configure your project.
10
+ - **Multiple Architectures**: Supports both **MVC** (Model-View-Controller) and **Clean Architecture**.
11
+ - **Language Support**: Choose between **JavaScript** and **TypeScript**.
12
+ - **Database Integration**: Pre-configured setup for **MySQL** or **PostgreSQL**.
13
+ - **Microservices Ready**: Optional **Kafka** integration for event-driven communication.
14
+ - **Dockerized**: Automatically generates `docker-compose.yml` for DB, Kafka, and Zookeeper.
15
+ - **Database Migrations**: Integrated **Flyway** support for SQL migrations.
16
+ - **Professional Standards**: Generated projects come with highly professional, industry-standard tooling.
17
+
18
+ ## ๐Ÿ† Professional Standards (New)
19
+
20
+ We don't just generate boilerplate; we generate **production-ready** foundations. Every project includes:
21
+
22
+ - **๐Ÿ” Code Quality**: Pre-configured `Eslint` and `Prettier` for consistent coding standards.
23
+ - **๐Ÿ›ก๏ธ Security**: Built-in `Helmet`, `HPP`, `CORS`, and Rate-Limiting middleware.
24
+ - **๐Ÿงช Testing Strategy**: Integrated `Jest` and `Supertest` setup for Unit and Integration testing.
25
+ - **๐Ÿ”„ CI/CD Support**: Optional **GitHub Actions** workflow integration.
26
+ - **โš“ Git Hooks**: `Husky` and `Lint-Staged` to ensure no bad code is ever committed.
27
+ - **๐Ÿณ DevOps**: Highly optimized **Multi-Stage Dockerfile** for small, secure production images.
28
+
29
+ ## Installation
30
+
31
+ You can install the tool globally directly from GitHub:
32
+
33
+ ```bash
34
+ npm install -g paudang/nodejs-quickstart-structure
35
+ ```
36
+
37
+ ### Manual Installation (For Development)
38
+
39
+ If you want to modify the CLI itself:
40
+
41
+ 1. Clone this repository:
42
+ ```bash
43
+ git clone https://github.com/paudang/nodejs-quickstart-structure.git
44
+ cd nodejs-quickstart-structure
45
+ ```
46
+ 2. Install dependencies:
47
+ ```bash
48
+ npm install
49
+ ```
50
+ 3. Link globally:
51
+ ```bash
52
+ npm link
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ Once installed, simply run the following command in any directory where you want to create a new project:
58
+
59
+ ```bash
60
+ nodejs-quickstart init
61
+ ```
62
+
63
+ ### Configuration Options
64
+
65
+ The CLI will guide you through the following steps:
66
+
67
+ 1. **Project Name**: The name of the folder to create.
68
+ 2. **Language**: `JavaScript` or `TypeScript`.
69
+ 3. **Architecture**: `MVC` or `Clean Architecture`.
70
+ 4. **Database**: `MySQL` or `PostgreSQL`.
71
+ 5. **Database Name**: The name of the initial database.
72
+ 6. **Communication**: `REST APIs` (default) or `Kafka`.
73
+ 7. **CI/CD**: `Include GitHub Actions Workflow` (Optional).
74
+
75
+ ## Generated Project Structure
76
+
77
+ The generated project will include:
78
+
79
+ - `src/`: Source code (controllers, routes, services/use-cases).
80
+ - `flyway/sql/`: SQL migration scripts.
81
+ - `docker-compose.yml`: Services configuration for DB, Flyway, and Kafka.
82
+ - `package.json`: Dependencies and scripts (`start`, `dev`, `build`).
83
+ - `tsconfig.json`: (If TypeScript is selected) Type checking configuration.
84
+
85
+ ### Getting Started with the Generated App
86
+
87
+ ```bash
88
+ cd <your-project-name>
89
+
90
+ # Start infrastructure (DB, etc.)
91
+ npm install
92
+
93
+ docker-compose up
94
+ ```
95
+
96
+ ## License
97
+
98
+ ISC
package/bin/index.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { getProjectDetails } from '../lib/prompts.js';
6
+ import { generateProject } from '../lib/generator.js';
7
+ import { readFileSync } from 'fs';
8
+ import { join, dirname } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
13
+
14
+ const program = new Command();
15
+
16
+ program
17
+ .name('nodejs-quickstart')
18
+ .description('๐Ÿš€ CLI to scaffold production-ready Node.js microservices.\n\nGenerates projects with:\n- MVC or Clean Architecture\n- REST or Kafka\n- MySQL or PostgreSQL\n- Docker & Flyway support')
19
+ .version(pkg.version, '-v, --version', 'Output the current version')
20
+ .addHelpText('after', `
21
+ \n${chalk.yellow('Example:')}
22
+ $ nodejs-quickstart init ${chalk.gray('# Start the interactive setup')}
23
+ `);
24
+
25
+ program
26
+ .command('init')
27
+ .description('Initialize a new Node.js project')
28
+ .option('-n, --project-name <name>', 'Project name')
29
+ .option('-l, --language <language>', 'Language (JavaScript, TypeScript)')
30
+ .option('-a, --architecture <architecture>', 'Architecture (MVC, Clean Architecture)')
31
+ .option('--view-engine <view>', 'View Engine (None, EJS, Pug) - MVC only')
32
+ .option('-d, --database <database>', 'Database (MySQL, PostgreSQL)')
33
+ .option('--db-name <name>', 'Database name')
34
+ .option('-c, --communication <communication>', 'Communication (REST APIs, Kafka)')
35
+ .option('--include-ci', 'Include GitHub Actions CI Workflow')
36
+ .action(async (options) => {
37
+ // Fix for Commander camelCase conversion
38
+ if (options.includeCi) {
39
+ options.includeCI = options.includeCi;
40
+ delete options.includeCi;
41
+ }
42
+
43
+ console.log(chalk.blue('Welcome to the Node.js Quickstart Generator!'));
44
+
45
+ try {
46
+ const answers = await getProjectDetails(options);
47
+ console.log(chalk.green('\nConfiguration received:'));
48
+ console.log(JSON.stringify(answers, null, 2));
49
+
50
+ console.log(chalk.yellow('\nGenerating project...'));
51
+ await generateProject(answers);
52
+
53
+ console.log(chalk.green('\nโœ” Project generated successfully!'));
54
+ console.log(chalk.cyan(`\nNext steps:\n cd ${answers.projectName}\n npm install\n docker-compose up\n-----------------------\nStart the app manually:\n npm install\n npm run dev`));
55
+
56
+ } catch (error) {
57
+ console.error(chalk.red('Error generating project:'), error);
58
+ process.exit(1);
59
+ }
60
+ });
61
+
62
+ program.parse(process.argv);
63
+
64
+ if (!process.argv.slice(2).length) {
65
+ program.outputHelp();
66
+ }
package/docs/demo.gif ADDED
Binary file
@@ -0,0 +1,58 @@
1
+ # NodeJS Quickstart Generator - Test Cases
2
+
3
+ This document lists the **32 possible project combinations** supported by the `nodejs-quickstart` CLI. These combinations cover all supported languages, architectures, databases, and communication patterns.
4
+
5
+ ## Summary
6
+ - **MVC Architecture**: 24 Combinations
7
+ - (2 Languages ร— 3 View Engines ร— 2 Databases ร— 2 Patterns)
8
+ - **Clean Architecture**: 8 Combinations
9
+ - (2 Languages ร— 1 View Engine (None) ร— 2 Databases ร— 2 Patterns)
10
+
11
+ **Total Core Combinations: 32**
12
+
13
+ > **Note on CI/CD**: Each of these 32 combinations can be generated with or without the **GitHub Actions CI Workflow** (`--include-ci`). This effectively creates **64 possible project states**. The validation script currently defaults to *including* CI to verify the full "Professional Standards" feature set.
14
+
15
+ ---
16
+
17
+ ## 1. MVC Architecture (24 Cases)
18
+
19
+ | # | Language | Architecture | View Engine | Database | Communication |
20
+ | :--- | :--- | :--- | :--- | :--- | :--- |
21
+ | 1 | JavaScript | MVC | None | MySQL | REST APIs |
22
+ | 2 | JavaScript | MVC | None | MySQL | Kafka |
23
+ | 3 | JavaScript | MVC | None | PostgreSQL | REST APIs |
24
+ | 4 | JavaScript | MVC | None | PostgreSQL | Kafka |
25
+ | 5 | JavaScript | MVC | EJS | MySQL | REST APIs |
26
+ | 6 | JavaScript | MVC | EJS | MySQL | Kafka |
27
+ | 7 | JavaScript | MVC | EJS | PostgreSQL | REST APIs |
28
+ | 8 | JavaScript | MVC | EJS | PostgreSQL | Kafka |
29
+ | 9 | JavaScript | MVC | Pug | MySQL | REST APIs |
30
+ | 10 | JavaScript | MVC | Pug | MySQL | Kafka |
31
+ | 11 | JavaScript | MVC | Pug | PostgreSQL | REST APIs |
32
+ | 12 | JavaScript | MVC | Pug | PostgreSQL | Kafka |
33
+ | 13 | TypeScript | MVC | None | MySQL | REST APIs |
34
+ | 14 | TypeScript | MVC | None | MySQL | Kafka |
35
+ | 15 | TypeScript | MVC | None | PostgreSQL | REST APIs |
36
+ | 16 | TypeScript | MVC | None | PostgreSQL | Kafka |
37
+ | 17 | TypeScript | MVC | EJS | MySQL | REST APIs |
38
+ | 18 | TypeScript | MVC | EJS | MySQL | Kafka |
39
+ | 19 | TypeScript | MVC | EJS | PostgreSQL | REST APIs |
40
+ | 20 | TypeScript | MVC | EJS | PostgreSQL | Kafka |
41
+ | 21 | TypeScript | MVC | Pug | MySQL | REST APIs |
42
+ | 22 | TypeScript | MVC | Pug | MySQL | Kafka |
43
+ | 23 | TypeScript | MVC | Pug | PostgreSQL | REST APIs |
44
+ | 24 | TypeScript | MVC | Pug | PostgreSQL | Kafka |
45
+
46
+ ## 2. Clean Architecture (8 Cases)
47
+ *Note: Clean Architecture does not use server-side view engines (EJS/Pug).*
48
+
49
+ | # | Language | Architecture | View Engine | Database | Communication |
50
+ | :--- | :--- | :--- | :--- | :--- | :--- |
51
+ | 25 | JavaScript | Clean Architecture | N/A | MySQL | REST APIs |
52
+ | 26 | JavaScript | Clean Architecture | N/A | MySQL | Kafka |
53
+ | 27 | JavaScript | Clean Architecture | N/A | PostgreSQL | REST APIs |
54
+ | 28 | JavaScript | Clean Architecture | N/A | PostgreSQL | Kafka |
55
+ | 29 | TypeScript | Clean Architecture | N/A | MySQL | REST APIs |
56
+ | 30 | TypeScript | Clean Architecture | N/A | MySQL | Kafka |
57
+ | 31 | TypeScript | Clean Architecture | N/A | PostgreSQL | REST APIs |
58
+ | 32 | TypeScript | Clean Architecture | N/A | PostgreSQL | Kafka |
@@ -0,0 +1,315 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import ejs from 'ejs';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ export const generateProject = async (config) => {
10
+ const { projectName, architecture, database, dbName, communication, language, viewEngine, includeCI } = config;
11
+ const targetDir = path.resolve(process.cwd(), projectName);
12
+ const templatesDir = path.join(__dirname, '../templates');
13
+
14
+ // 1. Create project directory
15
+ if (await fs.pathExists(targetDir)) {
16
+ throw new Error(`Directory ${projectName} already exists.`);
17
+ }
18
+ await fs.ensureDir(targetDir);
19
+
20
+ // 2. Select Structure Template
21
+ const structureMap = {
22
+ 'MVC': 'mvc',
23
+ 'Clean Architecture': 'clean-architecture'
24
+ };
25
+ const archTemplate = structureMap[architecture];
26
+ const langExt = language === 'TypeScript' ? 'ts' : 'js';
27
+ const templatePath = path.join(templatesDir, archTemplate, langExt);
28
+
29
+ // Copy base structure
30
+ await fs.copy(templatePath, targetDir);
31
+
32
+ // 3. Render package.json
33
+ const packageJsonPath = path.join(targetDir, 'package.json');
34
+ const packageTemplate = await fs.readFile(path.join(templatesDir, 'common', 'package.json.ejs'), 'utf-8');
35
+ const packageContent = ejs.render(packageTemplate, {
36
+ projectName,
37
+ database,
38
+ communication,
39
+ language,
40
+ viewEngine
41
+ });
42
+ await fs.writeFile(packageJsonPath, packageContent);
43
+
44
+ // 4. Render docker-compose.yml
45
+ const dockerComposePath = path.join(targetDir, 'docker-compose.yml');
46
+ const dockerTemplate = await fs.readFile(path.join(templatesDir, 'common', 'docker-compose.yml.ejs'), 'utf-8');
47
+ const dockerContent = ejs.render(dockerTemplate, {
48
+ projectName,
49
+ database,
50
+ dbName,
51
+ communication
52
+ });
53
+ await fs.writeFile(dockerComposePath, dockerContent);
54
+
55
+ // Render README.md
56
+ const readmePath = path.join(targetDir, 'README.md');
57
+ const readmeTemplate = await fs.readFile(path.join(templatesDir, 'common', 'README.md.ejs'), 'utf-8');
58
+ const readmeContent = ejs.render(readmeTemplate, {
59
+ projectName,
60
+ architecture,
61
+ database,
62
+ communication,
63
+ language
64
+ });
65
+ await fs.writeFile(readmePath, readmeContent);
66
+
67
+ // Render index file (ts/js)
68
+ const indexFileName = language === 'TypeScript' ? 'index.ts' : 'index.js';
69
+ const indexPath = path.join(targetDir, 'src', indexFileName);
70
+ const indexTemplateSource = path.join(templatePath, 'src', `${indexFileName}.ejs`);
71
+
72
+ if (await fs.pathExists(indexTemplateSource)) {
73
+ const indexTemplate = await fs.readFile(indexTemplateSource, 'utf-8');
74
+ const indexContent = ejs.render(indexTemplate, {
75
+ communication,
76
+ viewEngine,
77
+ database,
78
+ architecture,
79
+ projectName
80
+ });
81
+ await fs.writeFile(indexPath, indexContent);
82
+ await fs.remove(path.join(targetDir, 'src', `${indexFileName}.ejs`));
83
+ }
84
+
85
+ // Render Dynamic Controllers/Repositories (User) because they depend on DB type
86
+ // MVC Controller
87
+ if (architecture === 'MVC') {
88
+ const userControllerName = language === 'TypeScript' ? 'userController.ts' : 'userController.js';
89
+ const userControllerPath = path.join(targetDir, 'src/controllers', userControllerName);
90
+ const userControllerTemplate = path.join(templatePath, 'src/controllers', `${userControllerName}.ejs`);
91
+
92
+ if (await fs.pathExists(userControllerTemplate)) {
93
+ const content = ejs.render(await fs.readFile(userControllerTemplate, 'utf-8'), { database });
94
+ await fs.writeFile(userControllerPath, content);
95
+ await fs.remove(path.join(targetDir, 'src/controllers', `${userControllerName}.ejs`));
96
+ }
97
+ }
98
+ // Clean Architecture Repo
99
+ else if (architecture === 'Clean Architecture') {
100
+ const repoName = language === 'TypeScript' ? 'UserRepository.ts' : 'UserRepository.js';
101
+ const repoPath = path.join(targetDir, 'src/infrastructure/repositories', repoName);
102
+ const repoTemplate = path.join(templatePath, 'src/infrastructure/repositories', `${repoName}.ejs`);
103
+
104
+ if (await fs.pathExists(repoTemplate)) {
105
+ const content = ejs.render(await fs.readFile(repoTemplate, 'utf-8'), { database });
106
+ await fs.writeFile(repoPath, content);
107
+ await fs.remove(path.join(targetDir, 'src/infrastructure/repositories', `${repoName}.ejs`));
108
+ }
109
+ }
110
+ // Render Server (Clean Arch JS only)
111
+ if (architecture === 'Clean Architecture' && language === 'JavaScript') {
112
+ const serverName = 'server.js';
113
+ const serverPath = path.join(targetDir, 'src/infrastructure/webserver', serverName);
114
+ const serverTemplate = path.join(templatePath, 'src/infrastructure/webserver', `${serverName}.ejs`);
115
+
116
+ if (await fs.pathExists(serverTemplate)) {
117
+ const content = ejs.render(await fs.readFile(serverTemplate, 'utf-8'), { communication });
118
+ await fs.writeFile(serverPath, content);
119
+ await fs.remove(path.join(targetDir, 'src/infrastructure/webserver', `${serverName}.ejs`));
120
+ }
121
+ }
122
+
123
+ // Copy Kafka files if selected
124
+ if (communication === 'Kafka') {
125
+ const kafkaSource = path.join(templatesDir, 'common', 'kafka', langExt);
126
+ await fs.copy(kafkaSource, path.join(targetDir, 'src'));
127
+
128
+ if (architecture === 'Clean Architecture') {
129
+ // Clean Architecture Restructuring
130
+ await fs.ensureDir(path.join(targetDir, 'src/infrastructure/messaging'));
131
+ await fs.ensureDir(path.join(targetDir, 'src/infrastructure/config'));
132
+
133
+ const serviceExt = language === 'TypeScript' ? 'ts' : 'js';
134
+
135
+ // Move Service to Infrastructure/Messaging
136
+ await fs.move(
137
+ path.join(targetDir, `src/services/kafkaService.${serviceExt}`),
138
+ path.join(targetDir, `src/infrastructure/messaging/kafkaClient.${serviceExt}`),
139
+ { overwrite: true }
140
+ );
141
+
142
+ // Move Config to Infrastructure/Config
143
+ await fs.move(
144
+ path.join(targetDir, `src/config/kafka.${serviceExt}`),
145
+ path.join(targetDir, `src/infrastructure/config/kafka.${serviceExt}`),
146
+ { overwrite: true }
147
+ );
148
+
149
+ // Cleanup old folders
150
+ await fs.remove(path.join(targetDir, 'src/services'));
151
+ // Only remove src/config if empty? But src/config came from kafka copy.
152
+ // However, other parts might use src/config?
153
+ // In Clean Arch, config is usually in infrastructure?
154
+ // Only Kafka adds src/config. Base template doesn't have src/config for Clean Arch (it uses src/infrastructure/database).
155
+ await fs.remove(path.join(targetDir, 'src/config'));
156
+
157
+ // Remove REST-specific folders (Interfaces)
158
+ await fs.remove(path.join(targetDir, 'src/interfaces/routes'));
159
+ await fs.remove(path.join(targetDir, 'src/interfaces/controllers'));
160
+ } else if (architecture === 'MVC' && (!viewEngine || viewEngine === 'None')) {
161
+ // MVC Cleanup
162
+ await fs.remove(path.join(targetDir, 'src/controllers'));
163
+ await fs.remove(path.join(targetDir, 'src/routes'));
164
+ }
165
+ }
166
+
167
+ // 5. Copy Common Files (.gitignore, Dockerfile, etc.)
168
+ await fs.copy(path.join(templatesDir, 'common', '_gitignore'), path.join(targetDir, '.gitignore'));
169
+ await fs.copy(path.join(templatesDir, 'common', '.dockerignore'), path.join(targetDir, '.dockerignore'));
170
+ // await fs.copy(path.join(templatesDir, 'common', 'Dockerfile'), path.join(targetDir, 'Dockerfile'));
171
+ const dockerfileTemplate = await fs.readFile(path.join(templatesDir, 'common', 'Dockerfile'), 'utf-8');
172
+ const dockerfileContent = ejs.render(dockerfileTemplate, {
173
+ language,
174
+ viewEngine
175
+ });
176
+ await fs.writeFile(path.join(targetDir, 'Dockerfile'), dockerfileContent);
177
+
178
+ if (language === 'TypeScript') {
179
+ await fs.copy(path.join(templatesDir, 'common', 'tsconfig.json'), path.join(targetDir, 'tsconfig.json'));
180
+ }
181
+
182
+ // 6. Database Migrations (Flyway)
183
+ await fs.ensureDir(path.join(targetDir, 'flyway/sql'));
184
+ const dbType = database === 'PostgreSQL' ? 'postgres' : 'mysql';
185
+ await fs.copy(path.join(templatesDir, 'db', dbType), path.join(targetDir, 'flyway/sql'));
186
+
187
+ // 7. Database Config
188
+ const dbConfigFileName = language === 'TypeScript' ? 'database.ts' : 'database.js';
189
+ const dbConfigTemplateSource = path.join(templatesDir, 'common', 'database', langExt, `${dbConfigFileName}.ejs`);
190
+
191
+ let dbConfigTarget;
192
+
193
+
194
+ // Copy configurations
195
+ if (architecture === 'MVC') {
196
+ // Copy Views
197
+ if (viewEngine && viewEngine !== 'None') {
198
+ await fs.copy(path.join(templatesDir, 'common', 'views', viewEngine.toLowerCase()), path.join(targetDir, 'src/views'));
199
+ }
200
+ await fs.ensureDir(path.join(targetDir, 'src/config'));
201
+ dbConfigTarget = path.join(targetDir, 'src/config', dbConfigFileName);
202
+ } else {
203
+ // Clean Architecture
204
+ await fs.ensureDir(path.join(targetDir, 'src/infrastructure/database'));
205
+ dbConfigTarget = path.join(targetDir, 'src/infrastructure/database', dbConfigFileName);
206
+ }
207
+
208
+ if (await fs.pathExists(dbConfigTemplateSource)) {
209
+ const dbTemplate = await fs.readFile(dbConfigTemplateSource, 'utf-8');
210
+ const dbContent = ejs.render(dbTemplate, { database, dbName });
211
+ await fs.writeFile(dbConfigTarget, dbContent);
212
+ }
213
+
214
+ // Render Models
215
+ const modelFileName = language === 'TypeScript' ? 'User.ts' : 'User.js';
216
+ const modelTemplateSource = path.join(templatesDir, 'common', 'database', langExt, 'models', `${modelFileName}.ejs`);
217
+ let modelTarget;
218
+
219
+ if (architecture === 'MVC') {
220
+ await fs.ensureDir(path.join(targetDir, 'src/models'));
221
+ modelTarget = path.join(targetDir, 'src/models', modelFileName);
222
+ } else {
223
+ await fs.ensureDir(path.join(targetDir, 'src/infrastructure/database/models'));
224
+ modelTarget = path.join(targetDir, 'src/infrastructure/database/models', modelFileName);
225
+ }
226
+
227
+ if (await fs.pathExists(modelTemplateSource)) {
228
+ // Models need architecture to decide import path
229
+ const modelTemplate = await fs.readFile(modelTemplateSource, 'utf-8');
230
+ const modelContent = ejs.render(modelTemplate, { architecture });
231
+ await fs.writeFile(modelTarget, modelContent);
232
+ }
233
+
234
+ // 8. View Engine (MVC)
235
+ if (architecture === 'MVC' && viewEngine && viewEngine !== 'None') {
236
+ }
237
+
238
+ // 9. Render Swagger Config (if .ejs exists)
239
+ // MVC TS
240
+ const swaggerMvcTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
241
+ if (await fs.pathExists(swaggerMvcTs)) {
242
+ const content = ejs.render(await fs.readFile(swaggerMvcTs, 'utf-8'), { communication });
243
+ await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
244
+ await fs.remove(swaggerMvcTs);
245
+ }
246
+ // Clean Architecture TS
247
+ const swaggerCleanTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
248
+ // Note: In Clean Arch, it might be in src/infrastructure/webserver or src/config depending on refactor.
249
+ // Based on previous moves, we saw it in src/config for TS.
250
+ if (await fs.pathExists(swaggerCleanTs)) {
251
+ const content = ejs.render(await fs.readFile(swaggerCleanTs, 'utf-8'), { communication });
252
+ await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
253
+ await fs.remove(swaggerCleanTs);
254
+ }
255
+
256
+ // 10. Copy Professional Config Files (Eslint, Prettier, Husky)
257
+ const eslintTemplate = await fs.readFile(path.join(templatesDir, 'common', '.eslintrc.json.ejs'), 'utf-8');
258
+ const eslintContent = ejs.render(eslintTemplate, { language });
259
+ await fs.writeFile(path.join(targetDir, '.eslintrc.json'), eslintContent);
260
+
261
+ await fs.copy(path.join(templatesDir, 'common', '.prettierrc'), path.join(targetDir, '.prettierrc'));
262
+ await fs.copy(path.join(templatesDir, 'common', '.lintstagedrc'), path.join(targetDir, '.lintstagedrc'));
263
+
264
+ // 10. Copy Test Config & Samples
265
+ const jestTemplate = await fs.readFile(path.join(templatesDir, 'common', 'jest.config.js.ejs'), 'utf-8');
266
+ const jestContent = ejs.render(jestTemplate, { language });
267
+ await fs.writeFile(path.join(targetDir, 'jest.config.js'), jestContent);
268
+
269
+ // Create tests directory
270
+ await fs.ensureDir(path.join(targetDir, 'tests'));
271
+ const healthTestTemplate = await fs.readFile(path.join(templatesDir, 'common', 'tests', 'health.test.ts.ejs'), 'utf-8');
272
+ // For now, the sample test is simple and doesn't explicitly depend on projectName, but we render it just in case we add more dynamic content later
273
+ const healthTestContent = ejs.render(healthTestTemplate, { language });
274
+ const testFileName = language === 'TypeScript' ? 'health.test.ts' : 'health.test.js';
275
+ await fs.writeFile(path.join(targetDir, 'tests', testFileName), healthTestContent);
276
+
277
+ // 11. Copy CI/CD Config (Optional)
278
+ if (includeCI) {
279
+ await fs.ensureDir(path.join(targetDir, '.github/workflows'));
280
+ await fs.copy(path.join(templatesDir, 'common', '.github/workflows/ci.yml'), path.join(targetDir, '.github/workflows/ci.yml'));
281
+ }
282
+
283
+ console.log(`
284
+ ====================================================
285
+ Node.js Project Created Successfully!
286
+ ====================================================
287
+
288
+ Project: ${projectName}
289
+ Architecture: ${architecture}
290
+ Language: ${language}
291
+ Database: ${database}
292
+ Communication: ${communication}
293
+
294
+ ----------------------------------------------------
295
+ โœจ High-Quality Standards Applied:
296
+ ----------------------------------------------------
297
+ โœ… Linting & Formatting: Eslint + Prettier configured
298
+ โœ… Git Hooks: Husky + Lint-Staged ready
299
+ โœ… Security: Helmet, CORS, Rate-Limiting added
300
+ โœ… Testing: Jest setup for Unit/Integration tests
301
+ โœ… Docker: Production-ready multi-stage build
302
+ ${includeCI ? 'โœ… CI/CD: GitHub Actions Workflow ready' : 'โŒ CI/CD: Skipped (User preferred)'}
303
+
304
+ ----------------------------------------------------
305
+ ๐Ÿ‘‰ Next Steps:
306
+ ----------------------------------------------------
307
+ 1. cd ${projectName}
308
+ 2. git init
309
+ 3. npm install
310
+ 4. npm run prepare (To setup Husky hooks)
311
+ 5. docker-compose up -d (To start DB/Infrastructure)
312
+ 6. npm run dev (To start development server)
313
+ 7. npm test (To run tests)
314
+ `);
315
+ };
package/lib/prompts.js ADDED
@@ -0,0 +1,76 @@
1
+ import inquirer from 'inquirer';
2
+
3
+ const validateName = (name) => {
4
+ return /^[a-zA-Z0-9-_]+$/.test(name) ? true : 'Project name may only include letters, numbers, underscores and hashes.';
5
+ };
6
+
7
+ export const getProjectDetails = async (options = {}) => {
8
+ const questions = [
9
+ {
10
+ type: 'input',
11
+ name: 'projectName',
12
+ message: 'Project name:',
13
+ default: 'nodejs-service',
14
+ validate: validateName,
15
+ when: !options.projectName
16
+ },
17
+ {
18
+ type: 'list',
19
+ name: 'language',
20
+ message: 'Select Language:',
21
+ choices: ['JavaScript', 'TypeScript'],
22
+ default: 'TypeScript',
23
+ when: !options.language
24
+ },
25
+ {
26
+ type: 'list',
27
+ name: 'architecture',
28
+ message: 'Select Architecture:',
29
+ choices: ['MVC', 'Clean Architecture'],
30
+ default: 'MVC',
31
+ when: !options.architecture
32
+ },
33
+ {
34
+ type: 'list',
35
+ name: 'viewEngine',
36
+ message: 'Select View Engine:',
37
+ choices: ['None', 'EJS', 'Pug'],
38
+ when: (answers) => (options.architecture || answers.architecture) === 'MVC' && !options.viewEngine,
39
+ default: 'None'
40
+ },
41
+ {
42
+ type: 'list',
43
+ name: 'database',
44
+ message: 'Select Database:',
45
+ choices: ['MySQL', 'PostgreSQL'],
46
+ default: 'MySQL',
47
+ when: !options.database
48
+ },
49
+ {
50
+ type: 'input',
51
+ name: 'dbName',
52
+ message: 'Database Name:',
53
+ default: 'demo',
54
+ validate: validateName,
55
+ when: !options.dbName
56
+ },
57
+ {
58
+ type: 'list',
59
+ name: 'communication',
60
+ message: 'Microservices Communication:',
61
+ choices: ['REST APIs', 'Kafka'],
62
+ default: 'REST APIs',
63
+ when: !options.communication
64
+ },
65
+ {
66
+ type: 'confirm',
67
+ name: 'includeCI',
68
+ message: 'Include GitHub Actions CI Workflow? (Professional CI/CD pipeline included)',
69
+ default: false,
70
+ when: !options.includeCI
71
+ }
72
+ ];
73
+
74
+ const answers = await inquirer.prompt(questions);
75
+ return { ...options, ...answers };
76
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "nodejs-quickstart-structure",
3
+ "version": "1.1.6",
4
+ "type": "module",
5
+ "description": "A CLI to scaffold Node.js microservices with MVC or Clean Architecture",
6
+ "main": "bin/index.js",
7
+ "bin": {
8
+ "nodejs-quickstart": "./bin/index.js"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "test:e2e": "npm run test:e2e:windows",
13
+ "test:e2e:windows": "node scripts/validate-windows.js",
14
+ "test:e2e:linux": "node scripts/validate-linux.js"
15
+ },
16
+ "keywords": [
17
+ "nodejs",
18
+ "cli",
19
+ "scaffold",
20
+ "mvc",
21
+ "clean-architecture"
22
+ ],
23
+ "author": "Pau Dang <paudang@example.com>",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/paudang/nodejs-quickstart-structure.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/paudang/nodejs-quickstart-structure/issues"
30
+ },
31
+ "homepage": "https://github.com/paudang/nodejs-quickstart-structure#readme",
32
+ "license": "ISC",
33
+ "dependencies": {
34
+ "chalk": "^5.4.1",
35
+ "commander": "^13.1.0",
36
+ "ejs": "^3.1.10",
37
+ "fs-extra": "^11.3.0",
38
+ "inquirer": "^12.4.1"
39
+ }
40
+ }
@@ -0,0 +1,9 @@
1
+ class User {
2
+ constructor(id, name, email) {
3
+ this.id = id;
4
+ this.name = name;
5
+ this.email = email;
6
+ }
7
+ }
8
+
9
+ module.exports = User;
@@ -0,0 +1,9 @@
1
+ class UserRepository {
2
+ async save(user) {
3
+ // Database logic would go here
4
+ user.id = Math.floor(Math.random() * 1000);
5
+ return user;
6
+ }
7
+ }
8
+
9
+ module.exports = UserRepository;