nodejs-quickstart-structure 1.3.9 → 1.4.2

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 CHANGED
@@ -9,10 +9,10 @@ A powerful CLI tool to scaffold production-ready Node.js microservices with buil
9
9
  - **Interactive CLI**: Easy-to-use prompts to configure your project.
10
10
  - **Multiple Architectures**: Supports both **MVC** (Model-View-Controller) and **Clean Architecture**.
11
11
  - **Language Support**: Choose between **JavaScript** and **TypeScript**.
12
- - **Database Integration**: Pre-configured setup for **MySQL** or **PostgreSQL**.
12
+ - **Database Integration**: Pre-configured setup for **MySQL**, **PostgreSQL**, or **MongoDB**.
13
13
  - **Microservices Ready**: Optional **Kafka** integration for event-driven communication.
14
14
  - **Dockerized**: Automatically generates `docker-compose.yml` for DB, Kafka, and Zookeeper.
15
- - **Database Migrations**: Integrated **Flyway** support for SQL migrations.
15
+ - **Database Migrations/Schemas**: Integrated **Flyway** for SQL migrations or **Mongoose** schemas for MongoDB.
16
16
  - **Professional Standards**: Generated projects come with highly professional, industry-standard tooling.
17
17
 
18
18
  ## 🏆 Professional Standards (New)
@@ -67,7 +67,7 @@ The CLI will guide you through the following steps:
67
67
  1. **Project Name**: The name of the folder to create.
68
68
  2. **Language**: `JavaScript` or `TypeScript`.
69
69
  3. **Architecture**: `MVC` or `Clean Architecture`.
70
- 4. **Database**: `MySQL` or `PostgreSQL`.
70
+ 4. **Database**: `MySQL`, `PostgreSQL`, or `MongoDB`.
71
71
  5. **Database Name**: The name of the initial database.
72
72
  6. **Communication**: `REST APIs` (default) or `Kafka`.
73
73
  7. **CI/CD**: `GitHub Actions`, `Jenkins`, or `None`.
@@ -77,7 +77,7 @@ The CLI will guide you through the following steps:
77
77
  The generated project will include:
78
78
 
79
79
  - `src/`: Source code (controllers, routes, services/use-cases).
80
- - `flyway/sql/`: SQL migration scripts.
80
+ - `flyway/sql/`: SQL migration scripts (if SQL database selected).
81
81
  - `docker-compose.yml`: Services configuration for DB, Flyway, and Kafka.
82
82
  - `package.json`: Dependencies and scripts (`start`, `dev`, `build`).
83
83
  - `tsconfig.json`: (If TypeScript is selected) Type checking configuration.
@@ -1,20 +1,20 @@
1
1
  # NodeJS Quickstart Generator - Test Cases
2
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.
3
+ This document lists the **48 possible project combinations** supported by the `nodejs-quickstart` CLI. These combinations cover all supported languages, architectures, databases (including MongoDB), and communication patterns.
4
4
 
5
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)
6
+ - **MVC Architecture**: 36 Combinations
7
+ - (2 Languages × 3 View Engines × 3 Databases × 2 Patterns)
8
+ - **Clean Architecture**: 12 Combinations
9
+ - (2 Languages × 1 View Engine (None) × 3 Databases × 2 Patterns)
10
10
 
11
- **Total Core Combinations: 32**
11
+ **Total Core Combinations: 48**
12
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.
13
+ > **Note on CI/CD**: Each of these 48 combinations can be generated with or without the **GitHub Actions CI Workflow** (`--include-ci`). This effectively creates **96 possible project states**. The validation script currently defaults to *including* CI to verify the full "Professional Standards" feature set.
14
14
 
15
15
  ---
16
16
 
17
- ## 1. MVC Architecture (24 Cases)
17
+ ## 1. MVC Architecture (36 Cases)
18
18
 
19
19
  | # | Language | Architecture | View Engine | Database | Communication |
20
20
  | :--- | :--- | :--- | :--- | :--- | :--- |
@@ -22,37 +22,53 @@ This document lists the **32 possible project combinations** supported by the `n
22
22
  | 2 | JavaScript | MVC | None | MySQL | Kafka |
23
23
  | 3 | JavaScript | MVC | None | PostgreSQL | REST APIs |
24
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)
25
+ | 5 | JavaScript | MVC | None | MongoDB | REST APIs |
26
+ | 6 | JavaScript | MVC | None | MongoDB | Kafka |
27
+ | 7 | JavaScript | MVC | EJS | MySQL | REST APIs |
28
+ | 8 | JavaScript | MVC | EJS | MySQL | Kafka |
29
+ | 9 | JavaScript | MVC | EJS | PostgreSQL | REST APIs |
30
+ | 10 | JavaScript | MVC | EJS | PostgreSQL | Kafka |
31
+ | 11 | JavaScript | MVC | EJS | MongoDB | REST APIs |
32
+ | 12 | JavaScript | MVC | EJS | MongoDB | Kafka |
33
+ | 13 | JavaScript | MVC | Pug | MySQL | REST APIs |
34
+ | 14 | JavaScript | MVC | Pug | MySQL | Kafka |
35
+ | 15 | JavaScript | MVC | Pug | PostgreSQL | REST APIs |
36
+ | 16 | JavaScript | MVC | Pug | PostgreSQL | Kafka |
37
+ | 17 | JavaScript | MVC | Pug | MongoDB | REST APIs |
38
+ | 18 | JavaScript | MVC | Pug | MongoDB | Kafka |
39
+ | 19 | TypeScript | MVC | None | MySQL | REST APIs |
40
+ | 20 | TypeScript | MVC | None | MySQL | Kafka |
41
+ | 21 | TypeScript | MVC | None | PostgreSQL | REST APIs |
42
+ | 22 | TypeScript | MVC | None | PostgreSQL | Kafka |
43
+ | 23 | TypeScript | MVC | None | MongoDB | REST APIs |
44
+ | 24 | TypeScript | MVC | None | MongoDB | Kafka |
45
+ | 25 | TypeScript | MVC | EJS | MySQL | REST APIs |
46
+ | 26 | TypeScript | MVC | EJS | MySQL | Kafka |
47
+ | 27 | TypeScript | MVC | EJS | PostgreSQL | REST APIs |
48
+ | 28 | TypeScript | MVC | EJS | PostgreSQL | Kafka |
49
+ | 29 | TypeScript | MVC | EJS | MongoDB | REST APIs |
50
+ | 30 | TypeScript | MVC | EJS | MongoDB | Kafka |
51
+ | 31 | TypeScript | MVC | Pug | MySQL | REST APIs |
52
+ | 32 | TypeScript | MVC | Pug | MySQL | Kafka |
53
+ | 33 | TypeScript | MVC | Pug | PostgreSQL | REST APIs |
54
+ | 34 | TypeScript | MVC | Pug | PostgreSQL | Kafka |
55
+ | 35 | TypeScript | MVC | Pug | MongoDB | REST APIs |
56
+ | 36 | TypeScript | MVC | Pug | MongoDB | Kafka |
57
+
58
+ ## 2. Clean Architecture (12 Cases)
47
59
  *Note: Clean Architecture does not use server-side view engines (EJS/Pug).*
48
60
 
49
61
  | # | Language | Architecture | View Engine | Database | Communication |
50
62
  | :--- | :--- | :--- | :--- | :--- | :--- |
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 |
63
+ | 37 | JavaScript | Clean Architecture | N/A | MySQL | REST APIs |
64
+ | 38 | JavaScript | Clean Architecture | N/A | MySQL | Kafka |
65
+ | 39 | JavaScript | Clean Architecture | N/A | PostgreSQL | REST APIs |
66
+ | 40 | JavaScript | Clean Architecture | N/A | PostgreSQL | Kafka |
67
+ | 41 | JavaScript | Clean Architecture | N/A | MongoDB | REST APIs |
68
+ | 42 | JavaScript | Clean Architecture | N/A | MongoDB | Kafka |
69
+ | 43 | TypeScript | Clean Architecture | N/A | MySQL | REST APIs |
70
+ | 44 | TypeScript | Clean Architecture | N/A | MySQL | Kafka |
71
+ | 45 | TypeScript | Clean Architecture | N/A | PostgreSQL | REST APIs |
72
+ | 46 | TypeScript | Clean Architecture | N/A | PostgreSQL | Kafka |
73
+ | 47 | TypeScript | Clean Architecture | N/A | MongoDB | REST APIs |
74
+ | 48 | TypeScript | Clean Architecture | N/A | MongoDB | Kafka |
@@ -0,0 +1,171 @@
1
+ # Generator Flow Documentation
2
+
3
+ This document outlines the internal flow of the `nodejs-quickstart-structure` generator, including user options, the step-by-step generation process, and the resulting codebase structures.
4
+
5
+ ## 1. Technologies Used
6
+
7
+ The `nodejs-quickstart-structure` CLI is built using the following key technologies:
8
+
9
+ * **[Node.js](https://nodejs.org/)**: The runtime environment.
10
+ * **[Commander.js](https://github.com/tj/commander.js)** (`^13.1.0`): For parsing command-line arguments and options.
11
+ * **[Inquirer.js](https://github.com/SBoudrias/Inquirer.js)** (`^12.4.1`): For interactive command-line user interface (prompts).
12
+ * **[Chalk](https://github.com/chalk/chalk)** (`^5.4.1`): For terminal string styling (color output).
13
+ * **[EJS (Embedded JavaScript templates)](https://github.com/mde/ejs)** (`^3.1.10`): For templating files (dynamic content generation).
14
+ * **[fs-extra](https://github.com/jprichardson/node-fs-extra)** (`^11.3.0`): For enhanced file system methods (copy, ensureDir, etc.).
15
+
16
+ ## 2. User Choices (Cases)
17
+
18
+ The generator prompts the user for the following configurations. These determine the logic and structure of the generated project.
19
+
20
+ | Option | Choices | Default | Description |
21
+ | :--- | :--- | :--- | :--- |
22
+ | **Project Name** | Input String | `nodejs-service` | Name of the project directory. |
23
+ | **Language** | `JavaScript`, `TypeScript` | `TypeScript` | The programming language to use. |
24
+ | **Architecture** | `MVC`, `Clean Architecture` | `MVC` | The architectural pattern. |
25
+ | **View Engine** | `None`, `EJS`, `Pug` | `None` | (MVC Only) Template engine for server-side rendering. |
26
+ | **Database** | `MySQL`, `PostgreSQL`, `MongoDB` | `MySQL` | The primary database. |
27
+ | **Database Name** | Input String | `demo` | The name of the database to use/create. |
28
+ | **Communication**| `REST APIs`, `Kafka` | `REST APIs` | The primary communication method. |
29
+ | **CI/CD Provider**| `None`, `GitHub Actions`, `Jenkins`| `None` | Setup for Continuous Integration/Deployment. |
30
+
31
+ ## 3. Main Generator Flow
32
+
33
+ The `generateProject` function in `lib/generator.js` executes the following steps:
34
+
35
+ 1. **Create Project Directory**: Ensures the directory does not already exist and creates it.
36
+ 2. **Select & Copy Base Structure**:
37
+ * Based on **Architecture** (`mvc`/`clean-architecture`) and **Language** (`js`/`ts`), it selects the appropriate template from `templates/{arch}/{lang}`.
38
+ * Copies the entire base template to the target directory.
39
+ 3. **Render `package.json`**:
40
+ * Uses `templates/common/package.json.ejs`.
41
+ * Injects dependencies based on User Choices (DB drivers, view engines, etc.).
42
+ 4. **Render `docker-compose.yml`**:
43
+ * Uses `templates/common/docker-compose.yml.ejs`.
44
+ * Configures services (DB, Zookeeper/Kafka) based on selection.
45
+ 5. **Render `README.md`**:
46
+ * Generates custom documentation specific to the selected stack.
47
+ 6. **Render `src/index.{js|ts}`**:
48
+ * Processes the entry point file to wire up the selected DB and Architecture.
49
+ 7. **Dynamic Component Generation**:
50
+ * **MVC**: Generates `userController` (imports specific DB service).
51
+ * **Clean Architecture**: Generates `UserRepository` (infrastructure layer implementation).
52
+ * **Clean Architecture (JS only)**: Generates `server.js` (webserver setup).
53
+ 8. **Communication Setup (Kafka)**:
54
+ * If **Kafka** is selected:
55
+ * Copies Kafka client/service templates.
56
+ * **Clean Architecture Restructuring**:
57
+ * Moves service to `src/infrastructure/messaging`.
58
+ * Moves config to `src/infrastructure/config`.
59
+ * Removes REST-specific folders (`interfaces/routes`, `interfaces/controllers`).
60
+ * **MVC Cleanup**:
61
+ * If no View Engine is selected, removes `src/controllers` and `src/routes` (assumes pure worker).
62
+ 9. **Common Configuration**:
63
+ * Copies `.gitignore`, `.dockerignore`, `Dockerfile`.
64
+ * Copies `tsconfig.json` (if TypeScript).
65
+ 10. **Database Setup**:
66
+ * **MongoDB**: Sets up `migrate-mongo-config.js` and initial migration script.
67
+ * **SQL (MySQL/Postgres)**: Sets up `flyway/sql` directory and copies initial SQL migration files.
68
+ 11. **Database Connection Config**:
69
+ * Renders `database.{js|ts}` or `mongoose.{js|ts}` based on DB selection.
70
+ * Places it in `src/config` (MVC) or `src/infrastructure/database` (Clean Arch).
71
+ 12. **Model Generation**:
72
+ * Renders `User` model (Mongoose schema or Sequelize/TypeORM model) in the appropriate directory.
73
+ 13. **View Engine Setup (MVC)**:
74
+ * If selected, copies views (`views/ejs` or `views/pug`) and `public` assets.
75
+ 14. **Swagger Config**:
76
+ * If **REST APIs** is selected, generates Swagger configuration.
77
+ 15. **Code Quality Setup**:
78
+ * Generates `.eslintrc.json`, `.prettierrc`, `.lintstagedrc`.
79
+ 16. **Test Setup**:
80
+ * Generates `jest.config.js` and a sample `health.test.{js|ts}`.
81
+ 17. **CI/CD Setup**:
82
+ * Copies GitHub Actions workflow or renders `Jenkinsfile` if selected.
83
+
84
+ ## 4. TypeScript vs JavaScript Generation Steps
85
+
86
+ The logic handles language differences via conditional checks and file extensions (`langExt` variable).
87
+
88
+ | Step | JavaScript (`js`) | TypeScript (`ts`) |
89
+ | :--- | :--- | :--- |
90
+ | **Base Template** | `templates/{arch}/js` | `templates/{arch}/ts` |
91
+ | **Entry Point** | `src/index.js` | `src/index.ts` |
92
+ | **tsconfig.json** | Skipped | Copied from `templates/common/tsconfig.json` |
93
+ | **Linting** | Standard JS config | TS-specific parser and plugins in `.eslintrc` |
94
+ | **Database Config** | `mongoose.js` / `database.js` | `mongoose.ts` / `database.ts` |
95
+ | **Models/Controllers**| `.js` extension | `.ts` extension |
96
+ | **Build Step** | No compilation needed | Compilation typically handled by `tsc` or `ts-node` in dev |
97
+
98
+ ## 5. Possible Codebase Structures
99
+
100
+ The final structure depends heavily on **Architecture**, **Communication**, and **View Engine**.
101
+
102
+ ### Case A: MVC (REST API)
103
+ Standard architecture for web APIs.
104
+
105
+ ```text
106
+ project-name/
107
+ ├── src/
108
+ │ ├── config/ # Database, Swagger, etc.
109
+ │ ├── controllers/ # Request handlers
110
+ │ ├── models/ # Database models
111
+ │ ├── routes/ # Express routes
112
+ │ └── index.js|ts # Entry point
113
+ ├── tests/ # Jest tests
114
+ ├── package.json
115
+ ├── Dockerfile
116
+ └── docker-compose.yml
117
+ ```
118
+
119
+ ### Case B: MVC (Web App with Views)
120
+ Includes frontend views rendered on the server.
121
+
122
+ ```text
123
+ project-name/
124
+ ├── public/ # CSS, JS, Images
125
+ ├── src/
126
+ │ ├── config/
127
+ │ ├── controllers/
128
+ │ ├── models/
129
+ │ ├── routes/
130
+ │ ├── views/ # EJS or Pug templates
131
+ │ └── index.js|ts
132
+ └── ...
133
+ ```
134
+
135
+ ### Case C: Clean Architecture (REST API)
136
+ Separation of concerns with Domain, Use Cases, and Infrastructure.
137
+
138
+ ```text
139
+ project-name/
140
+ ├── src/
141
+ │ ├── domain/ # Entities (Enterprise rules)
142
+ │ ├── use_cases/ # Application business rules
143
+ │ ├── interfaces/ # Adapters
144
+ │ │ ├── controllers/
145
+ │ │ └── routes/
146
+ │ ├── infrastructure/ # Frameworks & Drivers
147
+ │ │ ├── config/ # Environment config
148
+ │ │ ├── database/ # DB connection & models
149
+ │ │ ├── repositories/ # Data access implementation
150
+ │ │ └── webserver/ # Express server setup
151
+ │ └── index.js|ts
152
+ └── ...
153
+ ```
154
+
155
+ ### Case D: Clean Architecture (Kafka Worker)
156
+ Optimized for event-driven microservices. HTTP routes are removed.
157
+
158
+ ```text
159
+ project-name/
160
+ ├── src/
161
+ │ ├── domain/
162
+ │ ├── use_cases/
163
+ │ ├── infrastructure/
164
+ │ │ ├── config/ # Includes Kafka config
165
+ │ │ ├── database/
166
+ │ │ ├── messaging/ # Kafka Client/Consumer
167
+ │ │ ├── repositories/
168
+ │ │ └── webserver/ # (Optional/Minimal)
169
+ │ └── index.js|ts
170
+ └── ...
171
+ ```
package/lib/generator.js CHANGED
@@ -198,14 +198,33 @@ export const generateProject = async (config) => {
198
198
  await fs.copy(path.join(templatesDir, 'common', 'tsconfig.json'), path.join(targetDir, 'tsconfig.json'));
199
199
  }
200
200
 
201
- // 6. Database Migrations (Flyway)
202
- await fs.ensureDir(path.join(targetDir, 'flyway/sql'));
203
- const dbType = database === 'PostgreSQL' ? 'postgres' : 'mysql';
204
- await fs.copy(path.join(templatesDir, 'db', dbType), path.join(targetDir, 'flyway/sql'));
201
+ // 6. Database Migrations
202
+ if (database === 'MongoDB') {
203
+ // Copy migrate-mongo config
204
+ const migrateConfigTemplate = await fs.readFile(path.join(templatesDir, 'common', 'migrate-mongo-config.js.ejs'), 'utf-8');
205
+ const migrateConfigContent = ejs.render(migrateConfigTemplate, { dbName });
206
+ await fs.writeFile(path.join(targetDir, 'migrate-mongo-config.js'), migrateConfigContent);
207
+
208
+ // Setup migrations directory
209
+ await fs.ensureDir(path.join(targetDir, 'migrations'));
210
+
211
+ // Create initial migration file with timestamp
212
+ const timestamp = new Date().toISOString().replace(/[-T:.Z]/g, '').slice(0, 14); // YYYYMMDDHHMMSS
213
+ const migrationTemplate = await fs.readFile(path.join(templatesDir, 'common', 'migrations', 'init.js.ejs'), 'utf-8');
214
+ await fs.writeFile(path.join(targetDir, 'migrations', `${timestamp}-initial-setup.js`), migrationTemplate);
215
+
216
+ } else {
217
+ // Flyway for SQL
218
+ await fs.ensureDir(path.join(targetDir, 'flyway/sql'));
219
+ const dbType = database === 'PostgreSQL' ? 'postgres' : 'mysql';
220
+ await fs.copy(path.join(templatesDir, 'db', dbType), path.join(targetDir, 'flyway/sql'));
221
+ }
205
222
 
206
223
  // 7. Database Config
207
- const dbConfigFileName = language === 'TypeScript' ? 'database.ts' : 'database.js';
208
- const dbConfigTemplateSource = path.join(templatesDir, 'common', 'database', langExt, `${dbConfigFileName}.ejs`);
224
+ const dbConfigFileName = language === 'TypeScript' ? (database === 'MongoDB' ? 'mongoose.ts' : 'database.ts') : (database === 'MongoDB' ? 'mongoose.js' : 'database.js');
225
+ const dbConfigTemplateSource = database === 'MongoDB'
226
+ ? path.join(templatesDir, 'common', 'database', langExt, `${dbConfigFileName}.ejs`)
227
+ : path.join(templatesDir, 'common', 'database', langExt, `${dbConfigFileName}.ejs`);
209
228
 
210
229
  let dbConfigTarget;
211
230
 
@@ -217,22 +236,27 @@ export const generateProject = async (config) => {
217
236
  await fs.copy(path.join(templatesDir, 'common', 'views', viewEngine.toLowerCase()), path.join(targetDir, 'src/views'));
218
237
  }
219
238
  await fs.ensureDir(path.join(targetDir, 'src/config'));
220
- dbConfigTarget = path.join(targetDir, 'src/config', dbConfigFileName);
239
+ dbConfigTarget = path.join(targetDir, 'src/config', database === 'MongoDB' ? (language === 'TypeScript' ? 'database.ts' : 'database.js') : dbConfigFileName);
221
240
  } else {
222
241
  // Clean Architecture
223
242
  await fs.ensureDir(path.join(targetDir, 'src/infrastructure/database'));
224
- dbConfigTarget = path.join(targetDir, 'src/infrastructure/database', dbConfigFileName);
243
+ dbConfigTarget = path.join(targetDir, 'src/infrastructure/database', language === 'TypeScript' ? 'database.ts' : 'database.js');
225
244
  }
226
245
 
227
246
  if (await fs.pathExists(dbConfigTemplateSource)) {
228
247
  const dbTemplate = await fs.readFile(dbConfigTemplateSource, 'utf-8');
229
- const dbContent = ejs.render(dbTemplate, { database, dbName });
248
+ const dbContent = ejs.render(dbTemplate, { database, dbName, architecture });
249
+ // Ensure consistent naming for imports in other files
250
+ // For MVC, we might want to rename mongoose.js to database.js to minimize refactoring in index.js?
251
+ // Actually, let's keep it consistent. If MVC, we typically call it 'database.js' in require.
252
+ // So we should save it as 'database.js' even if source is mongoose.js.ejs
230
253
  await fs.writeFile(dbConfigTarget, dbContent);
231
254
  }
232
255
 
233
256
  // Render Models
234
257
  const modelFileName = language === 'TypeScript' ? 'User.ts' : 'User.js';
235
- const modelTemplateSource = path.join(templatesDir, 'common', 'database', langExt, 'models', `${modelFileName}.ejs`);
258
+ const sourceModelName = database === 'MongoDB' ? `${modelFileName}.mongoose.ejs` : `${modelFileName}.ejs`;
259
+ const modelTemplateSource = path.join(templatesDir, 'common', 'database', langExt, 'models', sourceModelName);
236
260
  let modelTarget;
237
261
 
238
262
  if (architecture === 'MVC') {
package/lib/prompts.js CHANGED
@@ -42,7 +42,7 @@ export const getProjectDetails = async (options = {}) => {
42
42
  type: 'list',
43
43
  name: 'database',
44
44
  message: 'Select Database:',
45
- choices: ['MySQL', 'PostgreSQL'],
45
+ choices: ['MySQL', 'PostgreSQL', 'MongoDB'],
46
46
  default: 'MySQL',
47
47
  when: !options.database
48
48
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-quickstart-structure",
3
- "version": "1.3.9",
3
+ "version": "1.4.2",
4
4
  "type": "module",
5
5
  "description": "A CLI to scaffold Node.js microservices with MVC or Clean Architecture",
6
6
  "main": "bin/index.js",
@@ -11,7 +11,8 @@
11
11
  "test": "echo \"Error: no test specified\" && exit 1",
12
12
  "test:e2e": "npm run test:e2e:windows",
13
13
  "test:e2e:windows": "node scripts/validate-windows.js",
14
- "test:e2e:linux": "node scripts/validate-linux.js"
14
+ "test:e2e:linux": "node scripts/validate-linux.js",
15
+ "test:verify:mongo": "node scripts/verify-migration.js"
15
16
  },
16
17
  "keywords": [
17
18
  "nodejs",
@@ -20,7 +21,7 @@
20
21
  "mvc",
21
22
  "clean-architecture"
22
23
  ],
23
- "author": "Pau Dang <phucdangb1400718@gmail.com>",
24
+ "author": "Pau Dang <[EMAIL_ADDRESS]>",
24
25
  "repository": {
25
26
  "type": "git",
26
27
  "url": "git+https://github.com/paudang/nodejs-quickstart-structure.git"
@@ -36,5 +37,12 @@
36
37
  "ejs": "^3.1.10",
37
38
  "fs-extra": "^11.3.0",
38
39
  "inquirer": "^12.4.1"
39
- }
40
+ },
41
+ "files": [
42
+ "bin",
43
+ "lib",
44
+ "templates",
45
+ "docs",
46
+ "README.md"
47
+ ]
40
48
  }
@@ -11,8 +11,13 @@ const syncDatabase = async () => {
11
11
  let retries = 30;
12
12
  while (retries) {
13
13
  try {
14
+ <% if (database === 'MongoDB') { %>
15
+ const connectDB = require('./infrastructure/database/database');
16
+ await connectDB();
17
+ <% } else { %>
14
18
  const sequelize = require('./infrastructure/database/database');
15
19
  await sequelize.sync();
20
+ <% } %>
16
21
  logger.info('Database synced');
17
22
  // Start the web server after DB sync
18
23
  startServer(PORT);
@@ -3,16 +3,25 @@ const UserModel = require('../database/models/User');
3
3
  class UserRepository {
4
4
  async save(user) {
5
5
  const newUser = await UserModel.create({ name: user.name, email: user.email });
6
- return { ...user, id: newUser.id };
6
+ <% if (database === 'MongoDB') { %> return { ...user, id: newUser._id.toString() };
7
+ <% } else { %> return { ...user, id: newUser.id };
8
+ <% } -%>
7
9
  }
8
10
 
9
11
  async getUsers() {
10
- const users = await UserModel.findAll();
12
+ <% if (database === 'MongoDB') { %> const users = await UserModel.find();
13
+ return users.map(user => ({
14
+ id: user._id.toString(),
15
+ name: user.name,
16
+ email: user.email
17
+ }));
18
+ <% } else { %> const users = await UserModel.findAll();
11
19
  return users.map(user => ({
12
20
  id: user.id,
13
21
  name: user.name,
14
22
  email: user.email
15
23
  }));
24
+ <% } -%>
16
25
  }
17
26
  }
18
27
 
@@ -1,6 +1,6 @@
1
1
  export class User {
2
2
  constructor(
3
- public id: number | null,
3
+ public id: number | string | null,
4
4
  public name: string,
5
5
  public email: string
6
6
  ) { }
@@ -42,8 +42,13 @@ const syncDatabase = async () => {
42
42
  let retries = 30;
43
43
  while (retries) {
44
44
  try {
45
+ <% if (database === 'MongoDB') { %>
46
+ const connectDB = (await import('@/infrastructure/database/database')).default;
47
+ await connectDB();
48
+ <% } else { %>
45
49
  const sequelize = (await import('@/infrastructure/database/database')).default;
46
50
  await sequelize.sync();
51
+ <% } %>
47
52
  logger.info('Database synced');
48
53
 
49
54
  app.listen(port, async () => {
@@ -4,15 +4,24 @@ import UserModel from '@/infrastructure/database/models/User';
4
4
  export class UserRepository {
5
5
  async save(user: UserEntity): Promise<UserEntity> {
6
6
  const newUser = await UserModel.create({ name: user.name, email: user.email });
7
- return { id: newUser.id, name: newUser.name, email: newUser.email };
7
+ <% if (database === 'MongoDB') { %> return { id: newUser._id.toString(), name: newUser.name, email: newUser.email };
8
+ <% } else { %> return { id: newUser.id, name: newUser.name, email: newUser.email };
9
+ <% } -%>
8
10
  }
9
11
 
10
12
  async getUsers(): Promise<UserEntity[]> {
11
- const users = await UserModel.findAll();
13
+ <% if (database === 'MongoDB') { %> const users = await UserModel.find();
14
+ return users.map(user => ({
15
+ id: user._id.toString(),
16
+ name: user.name,
17
+ email: user.email
18
+ }));
19
+ <% } else { %> const users = await UserModel.findAll();
12
20
  return users.map(user => ({
13
21
  id: user.id,
14
22
  name: user.name,
15
23
  email: user.email
16
24
  }));
25
+ <% } -%>
17
26
  }
18
27
  }
@@ -10,7 +10,7 @@ This project comes pre-configured with industry-standard tooling for **Code Qual
10
10
  ## 🚀 Key Features
11
11
 
12
12
  - **Architecture**: <%= architecture %> (<% if (architecture === 'Clean Architecture') { %>Domain, UseCases, Infrastructure<% } else { %>MVC Pattern<% } %>).
13
- - **Database**: <%= database %> with **Flyway** migrations.
13
+ - **Database**: <%= database %> <% if (database !== 'MongoDB') { %>with **Flyway** migrations<% } else { %>with **Mongoose** schemas<% } %>.
14
14
  - **Security**: Helmet, CORS, Rate Limiting, HPP.
15
15
  - **Quality**: Eslint, Prettier, Husky, Lint-Staged.
16
16
  - **Testing**: Jest (Unit & Integration).
@@ -0,0 +1,19 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const UserSchema = new mongoose.Schema({
4
+ name: {
5
+ type: String,
6
+ required: true
7
+ },
8
+ email: {
9
+ type: String,
10
+ required: true,
11
+ unique: true
12
+ },
13
+ createdAt: {
14
+ type: Date,
15
+ default: Date.now
16
+ }
17
+ });
18
+
19
+ module.exports = mongoose.model('User', UserSchema);
@@ -0,0 +1,32 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ let logger;
4
+ <% if (architecture === 'MVC') { %>
5
+ logger = require('../utils/logger');
6
+ <% } else { %>
7
+ logger = require('../log/logger');
8
+ <% } %>
9
+
10
+ const connectDB = async () => {
11
+ const dbHost = process.env.DB_HOST || 'localhost';
12
+ const mongoURI = process.env.MONGO_URI || `mongodb://${dbHost}:27017/<%= dbName %>`;
13
+
14
+ let retries = 5;
15
+ while (retries) {
16
+ try {
17
+ await mongoose.connect(mongoURI, {
18
+ useNewUrlParser: true,
19
+ useUnifiedTopology: true
20
+ });
21
+ logger.info('MongoDB Connected...');
22
+ break;
23
+ } catch (err) {
24
+ logger.error('MongoDB connection failed:', err);
25
+ retries -= 1;
26
+ logger.info(`Retries left: ${retries}. Waiting 5s...`);
27
+ await new Promise(res => setTimeout(res, 5000));
28
+ }
29
+ }
30
+ };
31
+
32
+ module.exports = connectDB; // Export function to call in index.js
@@ -0,0 +1,25 @@
1
+ import mongoose, { Schema, Document } from 'mongoose';
2
+
3
+ export interface IUser extends Document {
4
+ name: string;
5
+ email: string;
6
+ createdAt: Date;
7
+ }
8
+
9
+ const UserSchema: Schema = new Schema({
10
+ name: {
11
+ type: String,
12
+ required: true
13
+ },
14
+ email: {
15
+ type: String,
16
+ required: true,
17
+ unique: true
18
+ },
19
+ createdAt: {
20
+ type: Date,
21
+ default: Date.now
22
+ }
23
+ });
24
+
25
+ export default mongoose.model<IUser>('User', UserSchema);
@@ -0,0 +1,27 @@
1
+ import mongoose from 'mongoose';
2
+ <% if (architecture === 'MVC') { %>
3
+ import logger from '@/utils/logger';
4
+ <% } else { %>
5
+ import logger from '@/infrastructure/log/logger';
6
+ <% } %>
7
+
8
+ const connectDB = async (): Promise<void> => {
9
+ const dbHost = process.env.DB_HOST || 'localhost';
10
+ const mongoURI = process.env.MONGO_URI || `mongodb://${dbHost}:27017/<%= dbName %>`;
11
+
12
+ let retries = 5;
13
+ while (retries) {
14
+ try {
15
+ await mongoose.connect(mongoURI);
16
+ logger.info('MongoDB Connected...');
17
+ break;
18
+ } catch (err) {
19
+ logger.error('MongoDB connection failed:', err);
20
+ retries -= 1;
21
+ logger.info(`Retries left: ${retries}. Waiting 5s...`);
22
+ await new Promise(res => setTimeout(res, 5000));
23
+ }
24
+ }
25
+ };
26
+
27
+ export default connectDB;
@@ -18,7 +18,7 @@ services:
18
18
  - DB_PASSWORD=root
19
19
  - DB_NAME=<%= dbName %>
20
20
  <% } %><% if (database === 'PostgreSQL') { %> - DB_USER=postgres
21
- - DB_PASSWORD=postgres
21
+ - DB_PASSWORD=root
22
22
  - DB_NAME=<%= dbName %>
23
23
  <% } -%>
24
24
  <% } else { %>
@@ -29,30 +29,52 @@ services:
29
29
  - DB_PASSWORD=root
30
30
  - DB_NAME=<%= dbName %>
31
31
  <% } %><% if (database === 'PostgreSQL') { %> - DB_USER=postgres
32
- - DB_PASSWORD=postgres
32
+ - DB_PASSWORD=root
33
33
  - DB_NAME=<%= dbName %>
34
34
  <% } -%>
35
35
  <% } %>
36
36
  db:
37
37
  <% if (database === 'MySQL') { %> image: mysql:8.0
38
- command: --default-authentication-plugin=mysql_native_password
39
38
  restart: always
40
39
  environment:
41
40
  MYSQL_ROOT_PASSWORD: root
42
41
  MYSQL_DATABASE: <%= dbName %>
43
42
  ports:
44
43
  - "${DB_PORT:-3306}:3306"
45
- <% } %><% if (database === 'PostgreSQL') { %> image: postgres:15
44
+ volumes:
45
+ - ./flyway/sql:/docker-entrypoint-initdb.d
46
+ <% } else if (database === 'PostgreSQL') { %> image: postgres:15
46
47
  restart: always
47
48
  environment:
48
49
  POSTGRES_USER: postgres
49
- POSTGRES_PASSWORD: postgres
50
+ POSTGRES_PASSWORD: root
50
51
  POSTGRES_DB: <%= dbName %>
51
52
  ports:
52
53
  - "${DB_PORT:-5432}:5432"
53
- <% } %> volumes:
54
- - <%= database.toLowerCase() %>_data:/var/lib/<% if (database === 'MySQL') { %>mysql<% } else { %>postgresql/data<% } %>
54
+ volumes:
55
+ - ./flyway/sql:/docker-entrypoint-initdb.d
56
+ <% } else if (database === 'MongoDB') { %> image: mongo:latest
57
+ restart: always
58
+ environment:
59
+ MONGO_INITDB_DATABASE: <%= dbName %>
60
+ ports:
61
+ - "${DB_PORT:-27017}:27017"
62
+ volumes:
63
+ - mongodb_data:/data/db
55
64
 
65
+ mongo-migrate:
66
+ image: node:18-alpine
67
+ working_dir: /app
68
+ volumes:
69
+ - .:/app
70
+ command: sh -c "npm install migrate-mongo && npm run migrate"
71
+ environment:
72
+ - DB_HOST=db
73
+ - DB_NAME=<%= dbName %>
74
+ depends_on:
75
+ - db
76
+ <% } %>
77
+ <% if (database !== 'MongoDB') { %>
56
78
  flyway:
57
79
  image: flyway/flyway
58
80
  command: -connectRetries=60 migrate
@@ -64,9 +86,10 @@ services:
64
86
  FLYWAY_PASSWORD: root
65
87
  <% } %><% if (database === 'PostgreSQL') { %> FLYWAY_URL: jdbc:postgresql://db:5432/<%= dbName %>
66
88
  FLYWAY_USER: postgres
67
- FLYWAY_PASSWORD: postgres
89
+ FLYWAY_PASSWORD: root
68
90
  <% } %> depends_on:
69
91
  - db
92
+ <% } %>
70
93
  <% if (communication === 'Kafka') { %> zookeeper:
71
94
  image: confluentinc/cp-zookeeper:7.4.0
72
95
  environment:
@@ -0,0 +1,31 @@
1
+ const config = {
2
+ mongodb: {
3
+ url: process.env.MONGO_URI || `mongodb://${process.env.DB_HOST || 'localhost'}:27017`,
4
+ databaseName: process.env.DB_NAME || '<%= dbName %>',
5
+
6
+ options: {
7
+ // useNewUrlParser: true, // No longer needed in Node.js driver v4+
8
+ // useUnifiedTopology: true, // No longer needed in Node.js driver v4+
9
+ // connectTimeoutMS: 3600000, // increase connection timeout to 1 hour
10
+ // socketTimeoutMS: 3600000, // increase socket timeout to 1 hour
11
+ }
12
+ },
13
+
14
+ // The migrations dir, can be an relative or absolute path. Only edit this when really necessary.
15
+ migrationsDir: "migrations",
16
+
17
+ // The mongodb collection where the applied changes are stored. Only edit this when really necessary.
18
+ changelogCollectionName: "changelog",
19
+
20
+ // The file extension to create migrations and search for in migration dir
21
+ migrationFileExtension: ".js",
22
+
23
+ // Enable the algorithm to create a checksum of the file contents and use that in the comparison to determine
24
+ // if the file should be run. Requires that scripts are coded to be run multiple times.
25
+ useFileHash: false,
26
+
27
+ // Don't change this, unless you know what you are doing
28
+ moduleSystem: 'commonjs',
29
+ };
30
+
31
+ module.exports = config;
@@ -0,0 +1,23 @@
1
+ module.exports = {
2
+ async up(db, client) {
3
+ const adminEmail = 'admin@example.com';
4
+ const existingAdmin = await db.collection('users').findOne({ email: adminEmail });
5
+
6
+ if (!existingAdmin) {
7
+ await db.collection('users').insertOne({
8
+ name: 'Admin User',
9
+ email: adminEmail,
10
+ createdAt: new Date(),
11
+ updatedAt: new Date()
12
+ });
13
+ console.log('Admin User seeded successfully');
14
+ } else {
15
+ console.log('Admin User already exists, skipping seed');
16
+ }
17
+ },
18
+
19
+ async down(db, client) {
20
+ // Optional: Undo the seed. Usually for seeds we might want to keep data, but strictly speaking 'down' should reverse 'up'.
21
+ // await db.collection('users').deleteOne({ email: 'admin@example.com' });
22
+ }
23
+ };
@@ -13,16 +13,21 @@
13
13
  "prepare": "husky install",
14
14
  "test": "jest",
15
15
  "test:watch": "jest --watch",
16
- "test:coverage": "jest --coverage"
16
+ "test:coverage": "jest --coverage",
17
+ "migrate": "migrate-mongo up"
17
18
  },
18
19
  "dependencies": {
19
20
  "express": "^4.18.2",
20
21
  "dotenv": "^16.3.1",
21
22
  <% if (database === 'MySQL') { %> "mysql2": "^3.6.5",
23
+ "sequelize": "^6.35.2",
22
24
  <% } -%>
23
25
  <% if (database === 'PostgreSQL') { %> "pg": "^8.11.3",
24
- <% } -%>
25
26
  "sequelize": "^6.35.2",
27
+ <% } -%>
28
+ <% if (database === 'MongoDB') { %> "mongoose": "^8.0.3",
29
+ "migrate-mongo": "^11.0.0",
30
+ <% } -%>
26
31
  <% if (communication === 'Kafka') { %> "kafkajs": "^2.2.4",
27
32
  <% } -%>
28
33
  <% if (viewEngine === 'EJS') { %> "ejs": "^3.1.9",
@@ -35,8 +40,7 @@
35
40
  "express-rate-limit": "^7.1.5",
36
41
  "winston": "^3.11.0"<% if (communication === 'REST APIs') { %>,
37
42
  "swagger-ui-express": "^5.0.0",
38
- "swagger-jsdoc": "^6.2.8"
39
- <% } %>
43
+ "swagger-jsdoc": "^6.2.8"<% } %>
40
44
  },
41
45
  "devDependencies": {
42
46
  "nodemon": "^3.0.2"<% if (language === 'TypeScript') { %>,
@@ -4,7 +4,9 @@ const logger = require('../utils/logger');
4
4
 
5
5
  const getUsers = async (req, res) => {
6
6
  try {
7
- const users = await User.findAll();
7
+ <% if (database === 'MongoDB') { %> const users = await User.find();
8
+ <% } else { %> const users = await User.findAll();
9
+ <% } -%>
8
10
  res.json(users);
9
11
  } catch (error) {
10
12
  logger.error('Error fetching users:', error);
@@ -50,8 +50,13 @@ const syncDatabase = async () => {
50
50
  let retries = 30;
51
51
  while (retries) {
52
52
  try {
53
+ <% if (database === 'MongoDB') { %>
54
+ const connectDB = require('./config/database');
55
+ await connectDB();
56
+ <% } else { %>
53
57
  const sequelize = require('./config/database');
54
58
  await sequelize.sync();
59
+ <% } %>
55
60
  logger.info('Database synced');
56
61
 
57
62
  // Start Server after DB is ready
@@ -6,7 +6,9 @@ import logger from '@/utils/logger';
6
6
  export class UserController {
7
7
  async getUsers(req: Request, res: Response) {
8
8
  try {
9
- const users = await User.findAll();
9
+ <% if (database === 'MongoDB') { %> const users = await User.find();
10
+ <% } else { %> const users = await User.findAll();
11
+ <% } -%>
10
12
  res.json(users);
11
13
  } catch (error) {
12
14
  logger.error('Error fetching users:', error);
@@ -60,10 +60,14 @@ const syncDatabase = async () => {
60
60
  let retries = 30;
61
61
  while (retries) {
62
62
  try {
63
+ <% if (database === 'MongoDB') { %>
64
+ const connectDB = (await import('@/config/database')).default;
65
+ await connectDB();
66
+ <% } else { %>
63
67
  const sequelize = (await import('@/config/database')).default;
64
68
  await sequelize.sync();
69
+ <% } %>
65
70
  logger.info('Database synced');
66
-
67
71
  // Start Server after DB is ready
68
72
  app.listen(port, async () => {
69
73
  logger.info(`Server running on port ${port}`);