nodejs-quickstart-structure 1.1.11 โ 1.3.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 +2 -2
- package/bin/index.js +4 -6
- package/lib/generator.js +12 -4
- package/lib/prompts.js +6 -5
- package/package.json +2 -2
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +8 -6
- package/templates/clean-architecture/ts/src/index.ts.ejs +11 -9
- package/templates/common/Jenkinsfile.ejs +35 -0
- package/templates/common/README.md.ejs +20 -0
- package/templates/mvc/js/src/index.js.ejs +9 -9
- package/templates/mvc/ts/src/index.ts.ejs +13 -10
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ We don't just generate boilerplate; we generate **production-ready** foundations
|
|
|
22
22
|
- **๐ Code Quality**: Pre-configured `Eslint` and `Prettier` for consistent coding standards.
|
|
23
23
|
- **๐ก๏ธ Security**: Built-in `Helmet`, `HPP`, `CORS`, and Rate-Limiting middleware.
|
|
24
24
|
- **๐งช Testing Strategy**: Integrated `Jest` and `Supertest` setup for Unit and Integration testing.
|
|
25
|
-
- **๐ CI/CD Support**:
|
|
25
|
+
- **๐ CI/CD Support**: Choice between **GitHub Actions** and **Jenkins**.
|
|
26
26
|
- **โ Git Hooks**: `Husky` and `Lint-Staged` to ensure no bad code is ever committed.
|
|
27
27
|
- **๐ณ DevOps**: Highly optimized **Multi-Stage Dockerfile** for small, secure production images.
|
|
28
28
|
|
|
@@ -70,7 +70,7 @@ The CLI will guide you through the following steps:
|
|
|
70
70
|
4. **Database**: `MySQL` or `PostgreSQL`.
|
|
71
71
|
5. **Database Name**: The name of the initial database.
|
|
72
72
|
6. **Communication**: `REST APIs` (default) or `Kafka`.
|
|
73
|
-
7. **CI/CD**: `
|
|
73
|
+
7. **CI/CD**: `GitHub Actions`, `Jenkins`, or `None`.
|
|
74
74
|
|
|
75
75
|
## Generated Project Structure
|
|
76
76
|
|
package/bin/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
@@ -32,12 +32,11 @@ program
|
|
|
32
32
|
.option('-d, --database <database>', 'Database (MySQL, PostgreSQL)')
|
|
33
33
|
.option('--db-name <name>', 'Database name')
|
|
34
34
|
.option('-c, --communication <communication>', 'Communication (REST APIs, Kafka)')
|
|
35
|
-
.option('--
|
|
35
|
+
.option('--ci-provider <provider>', 'CI/CD Provider (None, GitHub Actions, Jenkins)')
|
|
36
36
|
.action(async (options) => {
|
|
37
37
|
// Fix for Commander camelCase conversion
|
|
38
|
-
if (options.
|
|
39
|
-
options.
|
|
40
|
-
delete options.includeCi;
|
|
38
|
+
if (options.ciProvider) {
|
|
39
|
+
options.ciProvider = options.ciProvider;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
console.log(chalk.blue('Welcome to the Node.js Quickstart Generator!'));
|
|
@@ -58,7 +57,6 @@ program
|
|
|
58
57
|
process.exit(1);
|
|
59
58
|
}
|
|
60
59
|
});
|
|
61
|
-
|
|
62
60
|
program.parse(process.argv);
|
|
63
61
|
|
|
64
62
|
if (!process.argv.slice(2).length) {
|
package/lib/generator.js
CHANGED
|
@@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
8
8
|
|
|
9
9
|
export const generateProject = async (config) => {
|
|
10
|
-
const { projectName, architecture, database, dbName, communication, language, viewEngine,
|
|
10
|
+
const { projectName, architecture, database, dbName, communication, language, viewEngine, ciProvider } = config;
|
|
11
11
|
const targetDir = path.resolve(process.cwd(), projectName);
|
|
12
12
|
const templatesDir = path.join(__dirname, '../templates');
|
|
13
13
|
|
|
@@ -60,7 +60,8 @@ export const generateProject = async (config) => {
|
|
|
60
60
|
architecture,
|
|
61
61
|
database,
|
|
62
62
|
communication,
|
|
63
|
-
language
|
|
63
|
+
language,
|
|
64
|
+
ciProvider
|
|
64
65
|
});
|
|
65
66
|
await fs.writeFile(readmePath, readmeContent);
|
|
66
67
|
|
|
@@ -275,9 +276,16 @@ export const generateProject = async (config) => {
|
|
|
275
276
|
await fs.writeFile(path.join(targetDir, 'tests', testFileName), healthTestContent);
|
|
276
277
|
|
|
277
278
|
// 11. Copy CI/CD Config (Optional)
|
|
278
|
-
|
|
279
|
+
// 11. Copy CI/CD Config
|
|
280
|
+
if (ciProvider === 'GitHub Actions') {
|
|
279
281
|
await fs.ensureDir(path.join(targetDir, '.github/workflows'));
|
|
280
282
|
await fs.copy(path.join(templatesDir, 'common', '_github/workflows/ci.yml'), path.join(targetDir, '.github/workflows/ci.yml'));
|
|
283
|
+
} else if (ciProvider === 'Jenkins') {
|
|
284
|
+
const jenkinsTemplate = await fs.readFile(path.join(templatesDir, 'common', 'Jenkinsfile.ejs'), 'utf-8');
|
|
285
|
+
const jenkinsContent = ejs.render(jenkinsTemplate, {
|
|
286
|
+
projectName
|
|
287
|
+
});
|
|
288
|
+
await fs.writeFile(path.join(targetDir, 'Jenkinsfile'), jenkinsContent);
|
|
281
289
|
}
|
|
282
290
|
|
|
283
291
|
console.log(`
|
|
@@ -299,7 +307,7 @@ export const generateProject = async (config) => {
|
|
|
299
307
|
โ
Security: Helmet, CORS, Rate-Limiting added
|
|
300
308
|
โ
Testing: Jest setup for Unit/Integration tests
|
|
301
309
|
โ
Docker: Production-ready multi-stage build
|
|
302
|
-
${
|
|
310
|
+
${ciProvider !== 'None' ? `โ
CI/CD: ${ciProvider} Workflow ready` : 'โ CI/CD: Skipped (User preferred)'}
|
|
303
311
|
|
|
304
312
|
----------------------------------------------------
|
|
305
313
|
๐ Next Steps:
|
package/lib/prompts.js
CHANGED
|
@@ -63,11 +63,12 @@ export const getProjectDetails = async (options = {}) => {
|
|
|
63
63
|
when: !options.communication
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
|
-
type: '
|
|
67
|
-
name: '
|
|
68
|
-
message: '
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
type: 'list',
|
|
67
|
+
name: 'ciProvider',
|
|
68
|
+
message: 'Select CI/CD Provider:',
|
|
69
|
+
choices: ['None', 'GitHub Actions', 'Jenkins'],
|
|
70
|
+
default: 'None',
|
|
71
|
+
when: !options.ciProvider
|
|
71
72
|
}
|
|
72
73
|
];
|
|
73
74
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-quickstart-structure",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mvc",
|
|
21
21
|
"clean-architecture"
|
|
22
22
|
],
|
|
23
|
-
"author": "Pau Dang <
|
|
23
|
+
"author": "Pau Dang <phucdangb1400718@gmail.com>",
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
|
26
26
|
"url": "git+https://github.com/paudang/nodejs-quickstart-structure.git"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const cors = require('cors');
|
|
3
3
|
require('dotenv').config();
|
|
4
|
-
<% if (communication === 'REST APIs') { %>const apiRoutes = require('../../interfaces/routes/api');<% }
|
|
5
|
-
<% if (communication === 'REST APIs') {
|
|
4
|
+
<% if (communication === 'REST APIs') { %>const apiRoutes = require('../../interfaces/routes/api');<% } -%>
|
|
5
|
+
<% if (communication === 'REST APIs') { -%>
|
|
6
6
|
const swaggerUi = require('swagger-ui-express');
|
|
7
7
|
const swaggerSpecs = require('./swagger');
|
|
8
|
-
<% }
|
|
8
|
+
<% } -%>
|
|
9
9
|
|
|
10
10
|
const startServer = (port) => {
|
|
11
11
|
const app = express();
|
|
@@ -13,11 +13,13 @@ const startServer = (port) => {
|
|
|
13
13
|
app.use(cors());
|
|
14
14
|
app.use(express.json());
|
|
15
15
|
|
|
16
|
-
<% if (communication === 'REST APIs') {
|
|
16
|
+
<% if (communication === 'REST APIs') { -%>
|
|
17
|
+
app.use('/api', apiRoutes);
|
|
18
|
+
<% } -%>
|
|
17
19
|
|
|
18
|
-
<% if (communication === 'REST APIs') {
|
|
20
|
+
<% if (communication === 'REST APIs') { -%>
|
|
19
21
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
20
|
-
<% }
|
|
22
|
+
<% } -%>
|
|
21
23
|
|
|
22
24
|
app.get('/health', (req, res) => {
|
|
23
25
|
res.json({ status: 'UP' });
|
|
@@ -5,11 +5,11 @@ import hpp from 'hpp';
|
|
|
5
5
|
import rateLimit from 'express-rate-limit';
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
7
|
import logger from './infrastructure/log/logger';
|
|
8
|
-
<% if (communication === 'REST APIs') { %>import userRoutes from './interfaces/routes/userRoutes';<% }
|
|
9
|
-
<% if (communication === 'REST APIs') {
|
|
8
|
+
<% if (communication === 'REST APIs') { %>import userRoutes from './interfaces/routes/userRoutes';<% } -%>
|
|
9
|
+
<% if (communication === 'REST APIs') { -%>
|
|
10
10
|
import swaggerUi from 'swagger-ui-express';
|
|
11
|
-
import swaggerSpecs from './config/swagger';<% }
|
|
12
|
-
<% if (communication === 'Kafka') { %>import { KafkaService } from './infrastructure/messaging/kafkaClient';<% }
|
|
11
|
+
import swaggerSpecs from './config/swagger';<% } -%>
|
|
12
|
+
<% if (communication === 'Kafka') { %>import { KafkaService } from './infrastructure/messaging/kafkaClient';<% } -%>
|
|
13
13
|
|
|
14
14
|
dotenv.config();
|
|
15
15
|
|
|
@@ -26,10 +26,12 @@ app.use(limiter);
|
|
|
26
26
|
app.use(express.json());
|
|
27
27
|
|
|
28
28
|
<% if (communication === 'REST APIs') { -%>
|
|
29
|
-
app.use('/api/users', userRoutes)
|
|
29
|
+
app.use('/api/users', userRoutes);
|
|
30
|
+
<% } -%>
|
|
30
31
|
|
|
31
|
-
<% if (communication === 'REST APIs') {
|
|
32
|
-
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs))
|
|
32
|
+
<% if (communication === 'REST APIs') { -%>
|
|
33
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
34
|
+
<% } -%>
|
|
33
35
|
|
|
34
36
|
app.get('/health', (req: Request, res: Response) => {
|
|
35
37
|
res.json({ status: 'UP' });
|
|
@@ -46,7 +48,7 @@ const syncDatabase = async () => {
|
|
|
46
48
|
|
|
47
49
|
app.listen(port, async () => {
|
|
48
50
|
logger.info(`Server running on port ${port}`);
|
|
49
|
-
|
|
51
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
50
52
|
try {
|
|
51
53
|
const kafkaService = new KafkaService();
|
|
52
54
|
await kafkaService.connect();
|
|
@@ -56,7 +58,7 @@ const syncDatabase = async () => {
|
|
|
56
58
|
} catch (err) {
|
|
57
59
|
logger.error('Failed to connect to Kafka:', err);
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
<%_ } -%>
|
|
60
62
|
});
|
|
61
63
|
break;
|
|
62
64
|
} catch (err) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
pipeline {
|
|
2
|
+
agent any
|
|
3
|
+
|
|
4
|
+
environment {
|
|
5
|
+
CI = 'true'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
stages {
|
|
9
|
+
stage('Install Dependencies') {
|
|
10
|
+
steps {
|
|
11
|
+
// Use npm ci for clean install if package-lock.json exists, else npm install
|
|
12
|
+
sh 'if [ -f package-lock.json ]; then npm ci; else npm install; fi'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
stage('Lint') {
|
|
17
|
+
steps {
|
|
18
|
+
sh 'npm run lint'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
stage('Test') {
|
|
23
|
+
steps {
|
|
24
|
+
sh 'npm test'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
post {
|
|
30
|
+
always {
|
|
31
|
+
// Clean up workspace
|
|
32
|
+
cleanWs()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -16,6 +16,23 @@ This project comes pre-configured with industry-standard tooling for **Code Qual
|
|
|
16
16
|
- **Testing**: Jest (Unit & Integration).
|
|
17
17
|
- **DevOps**: Multi-stage Docker build, CI/CD ready.
|
|
18
18
|
|
|
19
|
+
## ๐ CI/CD Pipeline
|
|
20
|
+
<% if (ciProvider === 'GitHub Actions') { -%>
|
|
21
|
+
This project includes a **GitHub Actions** workflow located in `.github/workflows/ci.yml`.
|
|
22
|
+
It automatically runs:
|
|
23
|
+
- Linting
|
|
24
|
+
- Tests
|
|
25
|
+
- Builds
|
|
26
|
+
<% } else if (ciProvider === 'Jenkins') { -%>
|
|
27
|
+
This project includes a **Jenkinsfile** for comprehensive CI/CD.
|
|
28
|
+
Pipeline stages:
|
|
29
|
+
- Install Dependencies
|
|
30
|
+
- Lint
|
|
31
|
+
- Test
|
|
32
|
+
<% } else { -%>
|
|
33
|
+
CI/CD is not currently configured, but the project is ready for integration.
|
|
34
|
+
<% } -%>
|
|
35
|
+
|
|
19
36
|
## ๐ ๏ธ Getting Started
|
|
20
37
|
|
|
21
38
|
### 1. Prerequisites
|
|
@@ -24,6 +41,9 @@ This project comes pre-configured with industry-standard tooling for **Code Qual
|
|
|
24
41
|
|
|
25
42
|
### 2. Quick Start
|
|
26
43
|
```bash
|
|
44
|
+
# Initialize Git (Required for Husky)
|
|
45
|
+
git init
|
|
46
|
+
|
|
27
47
|
# Install dependencies
|
|
28
48
|
npm install
|
|
29
49
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const cors = require('cors');
|
|
3
3
|
require('dotenv').config();
|
|
4
|
-
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>const apiRoutes = require('./routes/api');<% }
|
|
5
|
-
<% if (communication === 'Kafka') { %>const { connectKafka, sendMessage } = require('./services/kafkaService');<% }
|
|
6
|
-
<% if (communication === 'REST APIs') {
|
|
4
|
+
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>const apiRoutes = require('./routes/api');<% } -%>
|
|
5
|
+
<% if (communication === 'Kafka') { %>const { connectKafka, sendMessage } = require('./services/kafkaService');<% } -%>
|
|
6
|
+
<% if (communication === 'REST APIs') { -%>
|
|
7
7
|
const swaggerUi = require('swagger-ui-express');
|
|
8
8
|
const swaggerSpecs = require('./config/swagger');
|
|
9
|
-
<% }
|
|
9
|
+
<% } -%>
|
|
10
10
|
|
|
11
11
|
const app = express();
|
|
12
12
|
const PORT = process.env.PORT || 3000;
|
|
@@ -15,11 +15,11 @@ const logger = require('./utils/logger');
|
|
|
15
15
|
app.use(cors());
|
|
16
16
|
app.use(express.json());
|
|
17
17
|
|
|
18
|
-
<% if (communication === 'REST APIs') {
|
|
18
|
+
<% if (communication === 'REST APIs') { -%>
|
|
19
19
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
20
|
-
<% }
|
|
20
|
+
<% } -%>
|
|
21
21
|
|
|
22
|
-
<% if (viewEngine === 'EJS' || viewEngine === 'Pug') {
|
|
22
|
+
<% if (viewEngine === 'EJS' || viewEngine === 'Pug') { -%>
|
|
23
23
|
// View Engine Setup
|
|
24
24
|
const path = require('path');
|
|
25
25
|
app.set('views', path.join(__dirname, 'views'));
|
|
@@ -57,7 +57,7 @@ const syncDatabase = async () => {
|
|
|
57
57
|
// Start Server after DB is ready
|
|
58
58
|
app.listen(PORT, async () => {
|
|
59
59
|
logger.info(`Server running on port ${PORT}`);
|
|
60
|
-
|
|
60
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
61
61
|
try {
|
|
62
62
|
await connectKafka();
|
|
63
63
|
logger.info('Kafka connected');
|
|
@@ -66,7 +66,7 @@ const syncDatabase = async () => {
|
|
|
66
66
|
} catch (err) {
|
|
67
67
|
logger.error('Failed to connect to Kafka:', err);
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
<%_ } -%>
|
|
70
70
|
});
|
|
71
71
|
break;
|
|
72
72
|
} catch (err) {
|
|
@@ -5,11 +5,11 @@ import hpp from 'hpp';
|
|
|
5
5
|
import rateLimit from 'express-rate-limit';
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
7
|
import logger from './utils/logger';
|
|
8
|
-
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>import apiRoutes from './routes/api';<% }
|
|
8
|
+
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>import apiRoutes from './routes/api';<% } -%>
|
|
9
9
|
<% if (communication === 'REST APIs') { %>
|
|
10
10
|
import swaggerUi from 'swagger-ui-express';
|
|
11
|
-
import swaggerSpecs from './config/swagger';<% }
|
|
12
|
-
<% if (communication === 'Kafka') { %>import { KafkaService } from './services/kafkaService';<% }
|
|
11
|
+
import swaggerSpecs from './config/swagger';<% } -%>
|
|
12
|
+
<% if (communication === 'Kafka') { %>import { KafkaService } from './services/kafkaService';<% } -%>
|
|
13
13
|
|
|
14
14
|
dotenv.config();
|
|
15
15
|
|
|
@@ -25,18 +25,21 @@ app.use(limiter);
|
|
|
25
25
|
|
|
26
26
|
app.use(express.json());
|
|
27
27
|
|
|
28
|
-
<% if (communication === 'REST APIs') {
|
|
29
|
-
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs))
|
|
28
|
+
<% if (communication === 'REST APIs') { -%>
|
|
29
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
30
|
+
<% } -%>
|
|
30
31
|
|
|
31
|
-
<% if (viewEngine === 'EJS' || viewEngine === 'Pug') {
|
|
32
|
+
<% if (viewEngine === 'EJS' || viewEngine === 'Pug') { -%>
|
|
32
33
|
// View Engine Setup
|
|
33
34
|
import path from 'path';
|
|
34
35
|
app.set('views', path.join(__dirname, 'views'));
|
|
35
|
-
app.set('view engine', '<%= viewEngine.toLowerCase() %>')
|
|
36
|
+
app.set('view engine', '<%= viewEngine.toLowerCase() %>');
|
|
37
|
+
<% } -%>
|
|
36
38
|
|
|
37
39
|
// Routes
|
|
38
40
|
<% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { -%>
|
|
39
|
-
app.use('/api', apiRoutes)
|
|
41
|
+
app.use('/api', apiRoutes);
|
|
42
|
+
<% } -%>
|
|
40
43
|
<% if (viewEngine && viewEngine !== 'None') { -%>
|
|
41
44
|
app.get('/', (req: Request, res: Response) => {
|
|
42
45
|
res.render('index', {
|
|
@@ -64,7 +67,7 @@ const syncDatabase = async () => {
|
|
|
64
67
|
// Start Server after DB is ready
|
|
65
68
|
app.listen(port, async () => {
|
|
66
69
|
logger.info(`Server running on port ${port}`);
|
|
67
|
-
|
|
70
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
68
71
|
try {
|
|
69
72
|
const kafkaService = new KafkaService();
|
|
70
73
|
await kafkaService.connect();
|
|
@@ -74,7 +77,7 @@ const syncDatabase = async () => {
|
|
|
74
77
|
} catch (err) {
|
|
75
78
|
logger.error('Failed to connect to Kafka:', err);
|
|
76
79
|
}
|
|
77
|
-
|
|
80
|
+
<%_ } -%>
|
|
78
81
|
});
|
|
79
82
|
break;
|
|
80
83
|
} catch (err) {
|