nodejs-quickstart-structure 1.1.11 → 1.3.5

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 (24) hide show
  1. package/README.md +2 -2
  2. package/bin/index.js +5 -7
  3. package/lib/generator.js +38 -8
  4. package/lib/prompts.js +6 -5
  5. package/package.json +39 -39
  6. package/templates/clean-architecture/js/src/index.js.ejs +3 -3
  7. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +5 -5
  8. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +10 -7
  9. package/templates/clean-architecture/js/src/interfaces/controllers/UserController.js +6 -1
  10. package/templates/clean-architecture/ts/src/index.ts.ejs +14 -12
  11. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +5 -5
  12. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts +3 -2
  13. package/templates/common/Jenkinsfile.ejs +35 -0
  14. package/templates/common/README.md.ejs +20 -0
  15. package/templates/common/kafka/js/services/{kafkaService.js → kafkaService.js.ejs} +2 -1
  16. package/templates/common/kafka/ts/services/{kafkaService.ts → kafkaService.ts.ejs} +2 -1
  17. package/templates/mvc/js/src/controllers/userController.js.ejs +3 -1
  18. package/templates/mvc/js/src/index.js.ejs +9 -9
  19. package/templates/mvc/js/src/utils/logger.js +5 -5
  20. package/templates/mvc/ts/src/controllers/userController.ts.ejs +3 -0
  21. package/templates/mvc/ts/src/index.ts.ejs +16 -13
  22. package/templates/mvc/ts/src/utils/logger.ts +5 -5
  23. package/templates/mvc/js/src/controllers/userController.js +0 -14
  24. /package/templates/clean-architecture/ts/src/infrastructure/repositories/{UserRepository.ts.ejs → userRepository.ts.ejs} +0 -0
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**: Optional **GitHub Actions** workflow integration.
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**: `Include GitHub Actions Workflow` (Optional).
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('--include-ci', 'Include GitHub Actions CI Workflow')
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.includeCi) {
39
- options.includeCI = options.includeCi;
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!'));
@@ -51,14 +50,13 @@ program
51
50
  await generateProject(answers);
52
51
 
53
52
  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`));
53
+ console.log(chalk.cyan(`\nNext steps:\n cd ${answers.projectName}\n npm install\n docker-compose up\n-----------------------\nStart the app manually:\n cd ${answers.projectName}\n npm install\n npm run dev`));
55
54
 
56
55
  } catch (error) {
57
56
  console.error(chalk.red('Error generating project:'), error);
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, includeCI } = config;
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
 
@@ -125,6 +126,24 @@ export const generateProject = async (config) => {
125
126
  const kafkaSource = path.join(templatesDir, 'common', 'kafka', langExt);
126
127
  await fs.copy(kafkaSource, path.join(targetDir, 'src'));
127
128
 
129
+ // Render Kafka Service with dynamic logger path
130
+ const kafkaServiceFileName = `kafkaService.${langExt}`;
131
+ const kafkaServiceTemplate = path.join(targetDir, 'src', 'services', `${kafkaServiceFileName}.ejs`);
132
+
133
+ if (await fs.pathExists(kafkaServiceTemplate)) {
134
+ const loggerPath = architecture === 'Clean Architecture' ? '../log/logger' : '../utils/logger';
135
+ // Note: For MVC, it's relative to src/services (../utils/logger). Wait, ../utils/logger means src/utils/logger.
136
+ // src/services/kafka.ts -> ../utils/logger -> src/utils/logger. Correct.
137
+ // But wait, generated code for MVC usually puts it in src/services.
138
+ // Let's re-verify MVC structure for logger.
139
+ // In MVC, logger is usually in src/utils/logger.ts?
140
+ // Let's check where logger is copied for MVC.
141
+
142
+ const content = ejs.render(await fs.readFile(kafkaServiceTemplate, 'utf-8'), { loggerPath });
143
+ await fs.writeFile(path.join(targetDir, 'src', 'services', kafkaServiceFileName), content);
144
+ await fs.remove(kafkaServiceTemplate);
145
+ }
146
+
128
147
  if (architecture === 'Clean Architecture') {
129
148
  // Clean Architecture Restructuring
130
149
  await fs.ensureDir(path.join(targetDir, 'src/infrastructure/messaging'));
@@ -239,8 +258,10 @@ export const generateProject = async (config) => {
239
258
  // MVC TS
240
259
  const swaggerMvcTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
241
260
  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);
261
+ if (communication === 'REST APIs') {
262
+ const content = ejs.render(await fs.readFile(swaggerMvcTs, 'utf-8'), { communication });
263
+ await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
264
+ }
244
265
  await fs.remove(swaggerMvcTs);
245
266
  }
246
267
  // Clean Architecture TS
@@ -248,8 +269,10 @@ export const generateProject = async (config) => {
248
269
  // Note: In Clean Arch, it might be in src/infrastructure/webserver or src/config depending on refactor.
249
270
  // Based on previous moves, we saw it in src/config for TS.
250
271
  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);
272
+ if (communication === 'REST APIs') {
273
+ const content = ejs.render(await fs.readFile(swaggerCleanTs, 'utf-8'), { communication });
274
+ await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
275
+ }
253
276
  await fs.remove(swaggerCleanTs);
254
277
  }
255
278
 
@@ -275,9 +298,16 @@ export const generateProject = async (config) => {
275
298
  await fs.writeFile(path.join(targetDir, 'tests', testFileName), healthTestContent);
276
299
 
277
300
  // 11. Copy CI/CD Config (Optional)
278
- if (includeCI) {
301
+ // 11. Copy CI/CD Config
302
+ if (ciProvider === 'GitHub Actions') {
279
303
  await fs.ensureDir(path.join(targetDir, '.github/workflows'));
280
304
  await fs.copy(path.join(templatesDir, 'common', '_github/workflows/ci.yml'), path.join(targetDir, '.github/workflows/ci.yml'));
305
+ } else if (ciProvider === 'Jenkins') {
306
+ const jenkinsTemplate = await fs.readFile(path.join(templatesDir, 'common', 'Jenkinsfile.ejs'), 'utf-8');
307
+ const jenkinsContent = ejs.render(jenkinsTemplate, {
308
+ projectName
309
+ });
310
+ await fs.writeFile(path.join(targetDir, 'Jenkinsfile'), jenkinsContent);
281
311
  }
282
312
 
283
313
  console.log(`
@@ -299,7 +329,7 @@ export const generateProject = async (config) => {
299
329
  ✅ Security: Helmet, CORS, Rate-Limiting added
300
330
  ✅ Testing: Jest setup for Unit/Integration tests
301
331
  ✅ Docker: Production-ready multi-stage build
302
- ${includeCI ? '✅ CI/CD: GitHub Actions Workflow ready' : '❌ CI/CD: Skipped (User preferred)'}
332
+ ${ciProvider !== 'None' ? `✅ CI/CD: ${ciProvider} Workflow ready` : '❌ CI/CD: Skipped (User preferred)'}
303
333
 
304
334
  ----------------------------------------------------
305
335
  👉 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: 'confirm',
67
- name: 'includeCI',
68
- message: 'Include GitHub Actions CI Workflow? (Professional CI/CD pipeline included)',
69
- default: false,
70
- when: !options.includeCI
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,40 +1,40 @@
1
- {
2
- "name": "nodejs-quickstart-structure",
3
- "version": "1.1.11",
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
- }
1
+ {
2
+ "name": "nodejs-quickstart-structure",
3
+ "version": "1.3.5",
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 <phucdangb1400718@gmail.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
40
  }
@@ -26,10 +26,10 @@ const syncDatabase = async () => {
26
26
  });
27
27
  <% } -%>
28
28
  break;
29
- } catch (err) {
30
- logger.error('Database sync failed:', err);
29
+ } catch (error) {
30
+ logger.error('Error syncing database:', error);
31
31
  retries -= 1;
32
- logger.info(`Retries left: ${retries}. Waiting 5s...`);
32
+ logger.info(`Retries left: ${retries}`);
33
33
  await new Promise(res => setTimeout(res, 5000));
34
34
  }
35
35
  }
@@ -13,10 +13,10 @@ const logger = winston.createLogger({
13
13
  ],
14
14
  });
15
15
 
16
- if (process.env.NODE_ENV !== 'production') {
17
- logger.add(new winston.transports.Console({
18
- format: winston.format.simple(),
19
- }));
20
- }
16
+ logger.add(new winston.transports.Console({
17
+ format: process.env.NODE_ENV !== 'production'
18
+ ? winston.format.simple()
19
+ : winston.format.json(),
20
+ }));
21
21
 
22
22
  module.exports = logger;
@@ -1,11 +1,12 @@
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
+ const logger = require('../log/logger');
5
+ <% if (communication === 'REST APIs') { %>const apiRoutes = require('../../interfaces/routes/api');<% } -%>
6
+ <% if (communication === 'REST APIs') { -%>
6
7
  const swaggerUi = require('swagger-ui-express');
7
8
  const swaggerSpecs = require('./swagger');
8
- <% } %>
9
+ <% } -%>
9
10
 
10
11
  const startServer = (port) => {
11
12
  const app = express();
@@ -13,18 +14,20 @@ const startServer = (port) => {
13
14
  app.use(cors());
14
15
  app.use(express.json());
15
16
 
16
- <% if (communication === 'REST APIs') { %>app.use('/api', apiRoutes);<% } %>
17
+ <% if (communication === 'REST APIs') { -%>
18
+ app.use('/api', apiRoutes);
19
+ <% } -%>
17
20
 
18
- <% if (communication === 'REST APIs') { %>
21
+ <% if (communication === 'REST APIs') { -%>
19
22
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
20
- <% } %>
23
+ <% } -%>
21
24
 
22
25
  app.get('/health', (req, res) => {
23
26
  res.json({ status: 'UP' });
24
27
  });
25
28
 
26
29
  app.listen(port, () => {
27
- console.log(`Server running on port ${port}`);
30
+ logger.info(`Server running on port ${port}`);
28
31
  });
29
32
  };
30
33
 
@@ -2,6 +2,7 @@ const CreateUser = require('../../usecases/CreateUser');
2
2
  const GetAllUsers = require('../../usecases/GetAllUsers');
3
3
  const UserRepository = require('../../infrastructure/repositories/UserRepository');
4
4
  const HTTP_STATUS = require('../../utils/httpCodes');
5
+ const logger = require('../../infrastructure/log/logger');
5
6
 
6
7
  class UserController {
7
8
  constructor() {
@@ -13,7 +14,10 @@ class UserController {
13
14
  getUsers(req, res) {
14
15
  this.getAllUsersUseCase.execute()
15
16
  .then(users => res.json(users))
16
- .catch(err => res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: err.message }));
17
+ .catch(err => {
18
+ logger.error('Error getting users:', err);
19
+ res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: err.message });
20
+ });
17
21
  }
18
22
 
19
23
  async createUser(req, res) {
@@ -22,6 +26,7 @@ class UserController {
22
26
  const user = await this.createUserUseCase.execute(name, email);
23
27
  res.status(HTTP_STATUS.CREATED).json(user);
24
28
  } catch (error) {
29
+ logger.error('Error creating user:', error);
25
30
  res.status(HTTP_STATUS.BAD_REQUEST).json({ error: error.message });
26
31
  }
27
32
  }
@@ -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
- <% if (communication === 'Kafka') { %>
51
+ <%_ if (communication === 'Kafka') { -%>
50
52
  try {
51
53
  const kafkaService = new KafkaService();
52
54
  await kafkaService.connect();
@@ -56,13 +58,13 @@ 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
- } catch (err) {
63
- logger.error('Database sync failed:', err);
64
+ } catch (error) {
65
+ logger.error('Error syncing database:', error);
64
66
  retries -= 1;
65
- logger.info(`Retries left: ${retries}. Waiting 5s...`);
67
+ logger.info(`Retries left: ${retries}`);
66
68
  await new Promise(res => setTimeout(res, 5000));
67
69
  }
68
70
  }
@@ -13,10 +13,10 @@ const logger = winston.createLogger({
13
13
  ],
14
14
  });
15
15
 
16
- if (process.env.NODE_ENV !== 'production') {
17
- logger.add(new winston.transports.Console({
18
- format: winston.format.simple(),
19
- }));
20
- }
16
+ logger.add(new winston.transports.Console({
17
+ format: process.env.NODE_ENV !== 'production'
18
+ ? winston.format.simple()
19
+ : winston.format.json(),
20
+ }));
21
21
 
22
22
  export default logger;
@@ -3,6 +3,7 @@ import { UserRepository } from '../../infrastructure/repositories/UserRepository
3
3
  import CreateUser from '../../usecases/createUser';
4
4
  import GetAllUsers from '../../usecases/getAllUsers';
5
5
  import { HTTP_STATUS } from '../../utils/httpCodes';
6
+ import logger from '../../infrastructure/log/logger';
6
7
 
7
8
  export class UserController {
8
9
  private createUserUseCase: CreateUser;
@@ -20,7 +21,7 @@ export class UserController {
20
21
  const user = await this.createUserUseCase.execute(name, email);
21
22
  res.status(HTTP_STATUS.CREATED).json(user);
22
23
  } catch (error) {
23
- console.error('UserController Error:', error);
24
+ logger.error('UserController Error:', error);
24
25
  if (error instanceof Error) {
25
26
  res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
26
27
  } else {
@@ -34,7 +35,7 @@ export class UserController {
34
35
  const users = await this.getAllUsersUseCase.execute();
35
36
  res.json(users);
36
37
  } catch (error) {
37
- console.error('UserController Error:', error);
38
+ logger.error('UserController Error:', error);
38
39
  if (error instanceof Error) {
39
40
  res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
40
41
  } else {
@@ -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,4 +1,5 @@
1
1
  const { kafka } = require('../config/kafka');
2
+ const logger = require('<%= loggerPath %>');
2
3
 
3
4
  const producer = kafka.producer();
4
5
  const consumer = kafka.consumer({ groupId: 'test-group' });
@@ -10,7 +11,7 @@ const connectKafka = async () => {
10
11
 
11
12
  await consumer.run({
12
13
  eachMessage: async ({ topic, partition, message }) => {
13
- console.log({
14
+ logger.info({
14
15
  value: message.value.toString(),
15
16
  });
16
17
  },
@@ -1,5 +1,6 @@
1
1
  import { kafka } from '../config/kafka';
2
2
  import { EachMessagePayload, Producer, Consumer } from 'kafkajs';
3
+ import logger from '<%= loggerPath %>';
3
4
 
4
5
  export class KafkaService {
5
6
  private producer: Producer;
@@ -17,7 +18,7 @@ export class KafkaService {
17
18
 
18
19
  await this.consumer.run({
19
20
  eachMessage: async ({ topic, partition, message }: EachMessagePayload) => {
20
- console.log({
21
+ logger.info({
21
22
  value: message.value?.toString(),
22
23
  });
23
24
  },
@@ -1,12 +1,14 @@
1
1
  const User = require('../models/User');
2
2
  const HTTP_STATUS = require('../utils/httpCodes');
3
+ const logger = require('../utils/logger');
3
4
 
4
5
  const getUsers = async (req, res) => {
5
6
  try {
6
7
  const users = await User.findAll();
7
8
  res.json(users);
8
9
  } catch (error) {
9
- res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
10
+ logger.error('Error fetching users:', error);
11
+ res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Internal Server Error' });
10
12
  }
11
13
  };
12
14
 
@@ -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
- <% if (communication === 'Kafka') { %>
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) {
@@ -21,10 +21,10 @@ const logger = winston.createLogger({
21
21
  // If we're not in production then log to the `console` with the format:
22
22
  // `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
23
23
  //
24
- if (process.env.NODE_ENV !== 'production') {
25
- logger.add(new winston.transports.Console({
26
- format: winston.format.simple(),
27
- }));
28
- }
24
+ logger.add(new winston.transports.Console({
25
+ format: process.env.NODE_ENV !== 'production'
26
+ ? winston.format.simple()
27
+ : winston.format.json(),
28
+ }));
29
29
 
30
30
  module.exports = logger;
@@ -1,6 +1,7 @@
1
1
  import { Request, Response } from 'express';
2
2
  import User from '../models/User';
3
3
  import { HTTP_STATUS } from '../utils/httpCodes';
4
+ import logger from '../utils/logger';
4
5
 
5
6
  export class UserController {
6
7
  async getUsers(req: Request, res: Response) {
@@ -8,6 +9,7 @@ export class UserController {
8
9
  const users = await User.findAll();
9
10
  res.json(users);
10
11
  } catch (error) {
12
+ logger.error('Error fetching users:', error);
11
13
  if (error instanceof Error) {
12
14
  res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
13
15
  } else {
@@ -22,6 +24,7 @@ export class UserController {
22
24
  const user = await User.create({ name, email });
23
25
  res.status(HTTP_STATUS.CREATED).json(user);
24
26
  } catch (error) {
27
+ logger.error('Error creating user:', error);
25
28
  if (error instanceof Error) {
26
29
  res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
27
30
  } else {
@@ -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
- <% if (communication === 'Kafka') { %>
70
+ <%_ if (communication === 'Kafka') { -%>
68
71
  try {
69
72
  const kafkaService = new KafkaService();
70
73
  await kafkaService.connect();
@@ -74,13 +77,13 @@ 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
- } catch (err) {
81
- logger.error('Database sync failed:', err);
83
+ } catch (error) {
84
+ logger.error('Error syncing database:', error);
82
85
  retries -= 1;
83
- logger.info(`Retries left: ${retries}. Waiting 5s...`);
86
+ logger.info(`Retries left: ${retries}`);
84
87
  await new Promise(res => setTimeout(res, 5000));
85
88
  }
86
89
  }
@@ -13,10 +13,10 @@ const logger = winston.createLogger({
13
13
  ],
14
14
  });
15
15
 
16
- if (process.env.NODE_ENV !== 'production') {
17
- logger.add(new winston.transports.Console({
18
- format: winston.format.simple(),
19
- }));
20
- }
16
+ logger.add(new winston.transports.Console({
17
+ format: process.env.NODE_ENV !== 'production'
18
+ ? winston.format.simple()
19
+ : winston.format.json(),
20
+ }));
21
21
 
22
22
  export default logger;
@@ -1,14 +0,0 @@
1
- exports.getUsers = (req, res) => {
2
- res.json([
3
- { id: 1, name: 'John Doe' },
4
- { id: 2, name: 'Jane Doe' }
5
- ]);
6
- };
7
-
8
- exports.createUser = (req, res) => {
9
- const { name, email } = req.body;
10
- res.status(201).json({
11
- message: 'User created successfully',
12
- user: { name, email }
13
- });
14
- };