nodejs-quickstart-structure 1.3.2 → 1.3.9

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 (32) hide show
  1. package/bin/index.js +1 -1
  2. package/lib/generator.js +30 -4
  3. package/package.json +39 -39
  4. package/templates/clean-architecture/js/src/index.js.ejs +3 -3
  5. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +5 -5
  6. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +2 -1
  7. package/templates/clean-architecture/js/src/interfaces/controllers/UserController.js +6 -1
  8. package/templates/clean-architecture/ts/src/index.ts.ejs +8 -8
  9. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +5 -5
  10. package/templates/clean-architecture/ts/src/infrastructure/repositories/{UserRepository.ts.ejs → userRepository.ts.ejs} +2 -2
  11. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts +7 -6
  12. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +1 -1
  13. package/templates/clean-architecture/ts/src/usecases/createUser.ts +2 -2
  14. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +1 -1
  15. package/templates/common/Dockerfile +1 -0
  16. package/templates/common/database/ts/models/User.ts.ejs +1 -1
  17. package/templates/common/jest.config.js.ejs +4 -1
  18. package/templates/common/kafka/js/services/{kafkaService.js → kafkaService.js.ejs} +2 -1
  19. package/templates/common/kafka/ts/services/{kafkaService.ts → kafkaService.ts.ejs} +3 -2
  20. package/templates/common/package.json.ejs +4 -2
  21. package/templates/common/public/css/style.css +147 -0
  22. package/templates/common/tsconfig.json +5 -1
  23. package/templates/common/views/ejs/index.ejs +44 -20
  24. package/templates/common/views/pug/index.pug +32 -18
  25. package/templates/mvc/js/src/controllers/userController.js.ejs +3 -1
  26. package/templates/mvc/js/src/index.js.ejs +1 -1
  27. package/templates/mvc/js/src/utils/logger.js +5 -5
  28. package/templates/mvc/ts/src/controllers/userController.ts.ejs +5 -2
  29. package/templates/mvc/ts/src/index.ts.ejs +9 -9
  30. package/templates/mvc/ts/src/routes/api.ts +1 -1
  31. package/templates/mvc/ts/src/utils/logger.ts +5 -5
  32. package/templates/mvc/js/src/controllers/userController.js +0 -14
package/bin/index.js CHANGED
@@ -50,7 +50,7 @@ program
50
50
  await generateProject(answers);
51
51
 
52
52
  console.log(chalk.green('\n✔ Project generated successfully!'));
53
- 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`));
54
54
 
55
55
  } catch (error) {
56
56
  console.error(chalk.red('Error generating project:'), error);
package/lib/generator.js CHANGED
@@ -126,6 +126,24 @@ export const generateProject = async (config) => {
126
126
  const kafkaSource = path.join(templatesDir, 'common', 'kafka', langExt);
127
127
  await fs.copy(kafkaSource, path.join(targetDir, 'src'));
128
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
+ let loggerPath = architecture === 'Clean Architecture' ? '../log/logger' : '../utils/logger';
135
+ let configPath = '../config/kafka';
136
+
137
+ if (language === 'TypeScript') {
138
+ loggerPath = architecture === 'Clean Architecture' ? '@/infrastructure/log/logger' : '@/utils/logger';
139
+ configPath = architecture === 'Clean Architecture' ? '@/infrastructure/config/kafka' : '@/config/kafka';
140
+ }
141
+
142
+ const content = ejs.render(await fs.readFile(kafkaServiceTemplate, 'utf-8'), { loggerPath, configPath });
143
+ await fs.writeFile(path.join(targetDir, 'src', 'services', kafkaServiceFileName), content);
144
+ await fs.remove(kafkaServiceTemplate);
145
+ }
146
+
129
147
  if (architecture === 'Clean Architecture') {
130
148
  // Clean Architecture Restructuring
131
149
  await fs.ensureDir(path.join(targetDir, 'src/infrastructure/messaging'));
@@ -234,14 +252,20 @@ export const generateProject = async (config) => {
234
252
 
235
253
  // 8. View Engine (MVC)
236
254
  if (architecture === 'MVC' && viewEngine && viewEngine !== 'None') {
255
+ const publicDir = path.join(templatesDir, 'common', 'public');
256
+ if (await fs.pathExists(publicDir)) {
257
+ await fs.copy(publicDir, path.join(targetDir, 'public'));
258
+ }
237
259
  }
238
260
 
239
261
  // 9. Render Swagger Config (if .ejs exists)
240
262
  // MVC TS
241
263
  const swaggerMvcTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
242
264
  if (await fs.pathExists(swaggerMvcTs)) {
243
- const content = ejs.render(await fs.readFile(swaggerMvcTs, 'utf-8'), { communication });
244
- await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
265
+ if (communication === 'REST APIs') {
266
+ const content = ejs.render(await fs.readFile(swaggerMvcTs, 'utf-8'), { communication });
267
+ await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
268
+ }
245
269
  await fs.remove(swaggerMvcTs);
246
270
  }
247
271
  // Clean Architecture TS
@@ -249,8 +273,10 @@ export const generateProject = async (config) => {
249
273
  // Note: In Clean Arch, it might be in src/infrastructure/webserver or src/config depending on refactor.
250
274
  // Based on previous moves, we saw it in src/config for TS.
251
275
  if (await fs.pathExists(swaggerCleanTs)) {
252
- const content = ejs.render(await fs.readFile(swaggerCleanTs, 'utf-8'), { communication });
253
- await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
276
+ if (communication === 'REST APIs') {
277
+ const content = ejs.render(await fs.readFile(swaggerCleanTs, 'utf-8'), { communication });
278
+ await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
279
+ }
254
280
  await fs.remove(swaggerCleanTs);
255
281
  }
256
282
 
package/package.json CHANGED
@@ -1,40 +1,40 @@
1
- {
2
- "name": "nodejs-quickstart-structure",
3
- "version": "1.3.2",
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
- }
1
+ {
2
+ "name": "nodejs-quickstart-structure",
3
+ "version": "1.3.9",
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,6 +1,7 @@
1
1
  const express = require('express');
2
2
  const cors = require('cors');
3
3
  require('dotenv').config();
4
+ const logger = require('../log/logger');
4
5
  <% if (communication === 'REST APIs') { %>const apiRoutes = require('../../interfaces/routes/api');<% } -%>
5
6
  <% if (communication === 'REST APIs') { -%>
6
7
  const swaggerUi = require('swagger-ui-express');
@@ -26,7 +27,7 @@ const startServer = (port) => {
26
27
  });
27
28
 
28
29
  app.listen(port, () => {
29
- console.log(`Server running on port ${port}`);
30
+ logger.info(`Server running on port ${port}`);
30
31
  });
31
32
  };
32
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
  }
@@ -4,12 +4,12 @@ import helmet from 'helmet';
4
4
  import hpp from 'hpp';
5
5
  import rateLimit from 'express-rate-limit';
6
6
  import dotenv from 'dotenv';
7
- import logger from './infrastructure/log/logger';
8
- <% if (communication === 'REST APIs') { %>import userRoutes from './interfaces/routes/userRoutes';<% } -%>
7
+ import logger from '@/infrastructure/log/logger';
8
+ <% if (communication === 'REST APIs') { %>import userRoutes from '@/interfaces/routes/userRoutes';<% } -%>
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 './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
 
@@ -42,7 +42,7 @@ const syncDatabase = async () => {
42
42
  let retries = 30;
43
43
  while (retries) {
44
44
  try {
45
- const sequelize = (await import('./infrastructure/database/database')).default;
45
+ const sequelize = (await import('@/infrastructure/database/database')).default;
46
46
  await sequelize.sync();
47
47
  logger.info('Database synced');
48
48
 
@@ -61,10 +61,10 @@ const syncDatabase = async () => {
61
61
  <%_ } -%>
62
62
  });
63
63
  break;
64
- } catch (err) {
65
- logger.error('Database sync failed:', err);
64
+ } catch (error) {
65
+ logger.error('Error syncing database:', error);
66
66
  retries -= 1;
67
- logger.info(`Retries left: ${retries}. Waiting 5s...`);
67
+ logger.info(`Retries left: ${retries}`);
68
68
  await new Promise(res => setTimeout(res, 5000));
69
69
  }
70
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;
@@ -1,5 +1,5 @@
1
- import { User as UserEntity } from '../../domain/user';
2
- import UserModel from '../database/models/User';
1
+ import { User as UserEntity } from '@/domain/user';
2
+ import UserModel from '@/infrastructure/database/models/User';
3
3
 
4
4
  export class UserRepository {
5
5
  async save(user: UserEntity): Promise<UserEntity> {
@@ -1,8 +1,9 @@
1
1
  import { Request, Response } from 'express';
2
- import { UserRepository } from '../../infrastructure/repositories/UserRepository';
3
- import CreateUser from '../../usecases/createUser';
4
- import GetAllUsers from '../../usecases/getAllUsers';
5
- import { HTTP_STATUS } from '../../utils/httpCodes';
2
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
3
+ import CreateUser from '@/usecases/createUser';
4
+ import GetAllUsers from '@/usecases/getAllUsers';
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 {
@@ -1,5 +1,5 @@
1
1
  import { Router, Request, Response } from 'express';
2
- import { UserController } from '../controllers/userController';
2
+ import { UserController } from '@/interfaces/controllers/userController';
3
3
 
4
4
  const router = Router();
5
5
  const userController = new UserController();
@@ -1,6 +1,6 @@
1
- import { User } from '../domain/user';
1
+ import { User } from '@/domain/user';
2
2
 
3
- import { UserRepository } from '../infrastructure/repositories/UserRepository';
3
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
4
4
 
5
5
  export default class CreateUser {
6
6
  constructor(private userRepository: UserRepository) {}
@@ -1,4 +1,4 @@
1
- import { UserRepository } from '../infrastructure/repositories/UserRepository';
1
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
2
2
 
3
3
  export default class GetAllUsers {
4
4
  constructor(private userRepository: UserRepository) {}
@@ -40,6 +40,7 @@ COPY --from=builder /app/src ./src
40
40
  # Copy other necessary files (like views if MVC)
41
41
  <% if (viewEngine && viewEngine !== 'None') { %>
42
42
  COPY --from=builder /app/src/views ./dist/views
43
+ <% if (viewEngine && viewEngine !== 'None') { %>COPY --from=builder /app/public ./public<% } %>
43
44
  <% } %>
44
45
 
45
46
  EXPOSE 3000
@@ -1,5 +1,5 @@
1
1
  import { DataTypes, Model } from 'sequelize';
2
- <% if (architecture === 'MVC') { %>import sequelize from '../config/database';<% } else { %>import sequelize from '../database';<% } %>
2
+ <% if (architecture === 'MVC') { %>import sequelize from '@/config/database';<% } else { %>import sequelize from '@/infrastructure/database/database';<% } %>
3
3
 
4
4
  class User extends Model {
5
5
  public id!: number;
@@ -3,7 +3,10 @@ module.exports = {
3
3
  coverageDirectory: 'coverage',
4
4
  collectCoverageFrom: ['src/**/*.{js,ts}'],
5
5
  testMatch: ['**/*.test.ts', '**/*.test.js'],
6
- <% if (language === 'TypeScript') { %>preset: 'ts-jest',<% } %>
6
+ <% if (language === 'TypeScript') { %>preset: 'ts-jest',
7
+ moduleNameMapper: {
8
+ '^@/(.*)$': '<rootDir>/src/$1',
9
+ },<% } %>
7
10
  coveragePathIgnorePatterns: [
8
11
  "/node_modules/",
9
12
  "/dist/"
@@ -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
- import { kafka } from '../config/kafka';
1
+ import { kafka } from '<%= configPath %>';
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
  },
@@ -5,8 +5,8 @@
5
5
  "main": "<% if (language === 'TypeScript') { %>dist/index.js<% } else { %>src/index.js<% } %>",
6
6
  "scripts": {
7
7
  "start": "<% if (language === 'TypeScript') { %>node dist/index.js<% } else { %>node src/index.js<% } %>",
8
- "dev": "<% if (language === 'TypeScript') { %>nodemon --exec ts-node src/index.ts<% } else { %>nodemon src/index.js<% } %>"<% if (language === 'TypeScript') { %>,
9
- "build": "rimraf dist && tsc<% if (viewEngine && viewEngine !== 'None') { %> && copyfiles -u 1 \"src/views/**/*\" dist/<% } %>"<% } %>,
8
+ "dev": "<% if (language === 'TypeScript') { %>nodemon --exec ts-node -r tsconfig-paths/register src/index.ts<% } else { %>nodemon src/index.js<% } %>"<% if (language === 'TypeScript') { %>,
9
+ "build": "rimraf dist && tsc && tsc-alias<% if (viewEngine && viewEngine !== 'None') { %> && copyfiles -u 1 \"src/views/**/*\" dist/<% } %>"<% } %>,
10
10
  "lint": "eslint . --ext .ts,.js",
11
11
  "lint:fix": "eslint . --ext .ts,.js --fix",
12
12
  "format": "prettier --write .",
@@ -65,6 +65,8 @@
65
65
  "ts-jest": "^29.1.1",
66
66
  "@types/jest": "^29.5.11",
67
67
  "supertest": "^6.3.3",
68
+ "tsconfig-paths": "^4.2.0",
69
+ "tsc-alias": "^1.8.8",
68
70
  "@types/supertest": "^6.0.2"<% } else { %>,
69
71
  "jest": "^29.7.0",
70
72
  "supertest": "^6.3.3"<% } %>
@@ -0,0 +1,147 @@
1
+ :root {
2
+ --primary-color: #4f46e5;
3
+ --primary-hover: #4338ca;
4
+ --bg-color: #f9fafb;
5
+ --card-bg: #ffffff;
6
+ --text-main: #111827;
7
+ --text-muted: #6b7280;
8
+ --success-color: #10b981;
9
+ --border-color: #e5e7eb;
10
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
11
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
12
+ }
13
+
14
+ body {
15
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
16
+ background-color: var(--bg-color);
17
+ color: var(--text-main);
18
+ margin: 0;
19
+ padding: 0;
20
+ line-height: 1.5;
21
+ display: flex;
22
+ justify-content: center;
23
+ min-height: 100vh;
24
+ }
25
+
26
+ .container {
27
+ width: 100%;
28
+ max-width: 800px;
29
+ padding: 40px 20px;
30
+ }
31
+
32
+ .header {
33
+ text-align: center;
34
+ margin-bottom: 40px;
35
+ }
36
+
37
+ .logo {
38
+ font-size: 3rem;
39
+ margin-bottom: 10px;
40
+ }
41
+
42
+ h1 {
43
+ font-size: 2.5rem;
44
+ font-weight: 800;
45
+ color: var(--text-main);
46
+ margin: 0 0 10px 0;
47
+ letter-spacing: -0.025em;
48
+ }
49
+
50
+ .subtitle {
51
+ color: var(--text-muted);
52
+ font-size: 1.125rem;
53
+ }
54
+
55
+ .card-grid {
56
+ display: grid;
57
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
58
+ gap: 20px;
59
+ margin-bottom: 40px;
60
+ }
61
+
62
+ .card {
63
+ background: var(--card-bg);
64
+ padding: 24px;
65
+ border-radius: 12px;
66
+ box-shadow: var(--shadow-sm);
67
+ border: 1px solid var(--border-color);
68
+ transition: transform 0.2s, box-shadow 0.2s;
69
+ }
70
+
71
+ .card:hover {
72
+ transform: translateY(-2px);
73
+ box-shadow: var(--shadow-md);
74
+ }
75
+
76
+ .card h3 {
77
+ margin-top: 0;
78
+ font-size: 0.875rem;
79
+ text-transform: uppercase;
80
+ letter-spacing: 0.05em;
81
+ color: var(--text-muted);
82
+ font-weight: 600;
83
+ }
84
+
85
+ .card p {
86
+ font-size: 1.25rem;
87
+ font-weight: 600;
88
+ color: var(--text-main);
89
+ margin: 5px 0 0 0;
90
+ }
91
+
92
+ .status-card {
93
+ background: var(--card-bg);
94
+ border-radius: 12px;
95
+ padding: 24px;
96
+ box-shadow: var(--shadow-md);
97
+ border-left: 6px solid var(--success-color);
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 20px;
101
+ }
102
+
103
+ .status-icon {
104
+ background: #d1fae5;
105
+ color: var(--success-color);
106
+ width: 48px;
107
+ height: 48px;
108
+ border-radius: 50%;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ font-size: 1.5rem;
113
+ }
114
+
115
+ .status-content h3 {
116
+ margin: 0;
117
+ font-size: 1.25rem;
118
+ font-weight: 700;
119
+ }
120
+
121
+ .status-content p {
122
+ margin: 5px 0 0 0;
123
+ color: var(--text-muted);
124
+ }
125
+
126
+ .action-btn {
127
+ display: inline-block;
128
+ margin-top: 40px;
129
+ background-color: var(--primary-color);
130
+ color: white;
131
+ padding: 12px 24px;
132
+ border-radius: 8px;
133
+ text-decoration: none;
134
+ font-weight: 600;
135
+ transition: background-color 0.2s;
136
+ }
137
+
138
+ .action-btn:hover {
139
+ background-color: var(--primary-hover);
140
+ }
141
+
142
+ footer {
143
+ margin-top: 60px;
144
+ text-align: center;
145
+ color: var(--text-muted);
146
+ font-size: 0.875rem;
147
+ }
@@ -7,7 +7,11 @@
7
7
  "strict": true,
8
8
  "esModuleInterop": true,
9
9
  "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true
10
+ "forceConsistentCasingInFileNames": true,
11
+ "baseUrl": ".",
12
+ "paths": {
13
+ "@/*": ["src/*"]
14
+ }
11
15
  },
12
16
  "include": [
13
17
  "src/**/*"
@@ -4,28 +4,52 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title><%= projectName %></title>
7
- <style>
8
- body { font-family: sans-serif; padding: 20px; }
9
- h1 { color: #333; }
10
- .status { margin-top: 20px; padding: 10px; background: #e0f7fa; border-left: 5px solid #00acc1; }
11
- </style>
7
+ <link rel="stylesheet" href="/css/style.css">
12
8
  </head>
13
9
  <body>
14
- <h1>Welcome to <%= projectName %></h1>
15
- <p>Architecture: <strong><%= architecture %></strong></p>
16
- <p>Database: <strong><%= database %></strong></p>
17
-
18
- <% if (communication === 'Kafka') { %>
19
- <div class="status">
20
- <h3>Kafka Status</h3>
21
- <p>Connected to Kafka Broker.</p>
22
- <p>Check console for messages.</p>
10
+ <div class="container">
11
+ <header class="header">
12
+ <div class="logo">🚀</div>
13
+ <h1>Welcome to <%= projectName %></h1>
14
+ <p class="subtitle">A production-ready Node.js microservice starter.</p>
15
+ </header>
16
+
17
+ <div class="card-grid">
18
+ <div class="card">
19
+ <h3>Architecture</h3>
20
+ <p><%= architecture %></p>
21
+ </div>
22
+ <div class="card">
23
+ <h3>Database</h3>
24
+ <p><%= database %></p>
25
+ </div>
26
+ <div class="card">
27
+ <h3>Communication</h3>
28
+ <p><%= communication %></p>
29
+ </div>
30
+ </div>
31
+
32
+ <% if (communication === 'Kafka') { %>
33
+ <div class="status-card">
34
+ <div class="status-icon">🔄</div>
35
+ <div class="status-content">
36
+ <h3>Kafka Connected</h3>
37
+ <p>Connection to broker established successfully.</p>
38
+ </div>
39
+ </div>
40
+ <% } else { %>
41
+ <div class="status-card">
42
+ <div class="status-icon">✅</div>
43
+ <div class="status-content">
44
+ <h3>API Active</h3>
45
+ <p>REST API is running and ready to accept requests.</p>
46
+ </div>
47
+ </div>
48
+ <% } %>
49
+
50
+ <footer>
51
+ <p>Generated with ❤️ by Node.js Quickstart Generator</p>
52
+ </footer>
23
53
  </div>
24
- <% } else { %>
25
- <div class="status">
26
- <h3>API Status</h3>
27
- <p>REST API is active.</p>
28
- </div>
29
- <% } %>
30
54
  </body>
31
55
  </html>
@@ -4,23 +4,37 @@ html(lang="en")
4
4
  meta(charset="UTF-8")
5
5
  meta(name="viewport", content="width=device-width, initial-scale=1.0")
6
6
  title= projectName
7
- style.
8
- body { font-family: sans-serif; padding: 20px; }
9
- h1 { color: #333; }
10
- .status { margin-top: 20px; padding: 10px; background: #e0f7fa; border-left: 5px solid #00acc1; }
7
+ link(rel="stylesheet", href="/css/style.css")
11
8
  body
12
- h1 Welcome to #{projectName}
13
- p Architecture:
14
- strong #{architecture}
15
- p Database:
16
- strong #{database}
9
+ .container
10
+ header.header
11
+ .logo 🚀
12
+ h1 Welcome to #{projectName}
13
+ p.subtitle A production-ready Node.js microservice starter.
17
14
 
18
- if communication === 'Kafka'
19
- .status
20
- h3 Kafka Status
21
- p Connected to Kafka Broker.
22
- p Check console for messages.
23
- else
24
- .status
25
- h3 API Status
26
- p REST API is active.
15
+ .card-grid
16
+ .card
17
+ h3 Architecture
18
+ p #{architecture}
19
+ .card
20
+ h3 Database
21
+ p #{database}
22
+ .card
23
+ h3 Communication
24
+ p #{communication}
25
+
26
+ if communication === 'Kafka'
27
+ .status-card
28
+ .status-icon 🔄
29
+ .status-content
30
+ h3 Kafka Connected
31
+ p Connection to broker established successfully.
32
+ else
33
+ .status-card
34
+ .status-icon ✅
35
+ .status-content
36
+ h3 API Active
37
+ p REST API is running and ready to accept requests.
38
+
39
+ footer
40
+ p Generated with ❤️ by Node.js Quickstart Generator
@@ -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
 
@@ -24,7 +24,7 @@ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
24
24
  const path = require('path');
25
25
  app.set('views', path.join(__dirname, 'views'));
26
26
  app.set('view engine', '<%= viewEngine.toLowerCase() %>');
27
- <% } -%>
27
+ app.use(express.static(path.join(__dirname, '../public')));<% } -%>
28
28
 
29
29
  // Routes
30
30
  <% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { -%>
@@ -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
- import User from '../models/User';
3
- import { HTTP_STATUS } from '../utils/httpCodes';
2
+ import User from '@/models/User';
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 {
@@ -4,12 +4,12 @@ import helmet from 'helmet';
4
4
  import hpp from 'hpp';
5
5
  import rateLimit from 'express-rate-limit';
6
6
  import dotenv from 'dotenv';
7
- import logger from './utils/logger';
8
- <% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>import apiRoutes from './routes/api';<% } -%>
7
+ import logger from '@/utils/logger';
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
 
@@ -34,7 +34,7 @@ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
34
34
  import path from 'path';
35
35
  app.set('views', path.join(__dirname, 'views'));
36
36
  app.set('view engine', '<%= viewEngine.toLowerCase() %>');
37
- <% } -%>
37
+ app.use(express.static(path.join(__dirname, '../public')));<% } -%>
38
38
 
39
39
  // Routes
40
40
  <% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { -%>
@@ -60,7 +60,7 @@ const syncDatabase = async () => {
60
60
  let retries = 30;
61
61
  while (retries) {
62
62
  try {
63
- const sequelize = (await import('./config/database')).default;
63
+ const sequelize = (await import('@/config/database')).default;
64
64
  await sequelize.sync();
65
65
  logger.info('Database synced');
66
66
 
@@ -80,10 +80,10 @@ const syncDatabase = async () => {
80
80
  <%_ } -%>
81
81
  });
82
82
  break;
83
- } catch (err) {
84
- logger.error('Database sync failed:', err);
83
+ } catch (error) {
84
+ logger.error('Error syncing database:', error);
85
85
  retries -= 1;
86
- logger.info(`Retries left: ${retries}. Waiting 5s...`);
86
+ logger.info(`Retries left: ${retries}`);
87
87
  await new Promise(res => setTimeout(res, 5000));
88
88
  }
89
89
  }
@@ -1,5 +1,5 @@
1
1
  import { Router, Request, Response } from 'express';
2
- import { UserController } from '../controllers/userController';
2
+ import { UserController } from '@/controllers/userController';
3
3
 
4
4
  const router = Router();
5
5
  const userController = new UserController();
@@ -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
- };