nodejs-quickstart-structure 2.0.0 → 2.1.0

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 (161) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +44 -40
  3. package/bin/index.js +6 -3
  4. package/lib/generator.js +10 -4
  5. package/lib/modules/app-setup.js +76 -6
  6. package/lib/modules/auth-setup.js +143 -0
  7. package/lib/modules/caching-setup.js +8 -1
  8. package/lib/modules/config-files.js +10 -0
  9. package/lib/modules/database-setup.js +2 -1
  10. package/lib/modules/project-setup.js +1 -0
  11. package/lib/prompts.js +40 -1
  12. package/package.json +5 -4
  13. package/templates/clean-architecture/js/src/domain/models/User.js +3 -1
  14. package/templates/clean-architecture/js/src/index.js.ejs +2 -0
  15. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +12 -3
  16. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +25 -2
  17. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +27 -0
  18. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +3 -0
  19. package/templates/clean-architecture/js/src/infrastructure/webserver/server.spec.js.ejs +49 -0
  20. package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.spec.js.ejs +14 -0
  21. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +41 -4
  22. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +69 -4
  23. package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -6
  24. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +38 -21
  25. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +10 -5
  26. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
  27. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +1 -1
  28. package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +15 -0
  29. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +4 -0
  30. package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +34 -0
  31. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +3 -2
  32. package/templates/clean-architecture/js/src/usecases/DeleteUser.js.ejs +27 -0
  33. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js.ejs +36 -0
  34. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +14 -0
  35. package/templates/clean-architecture/js/src/usecases/GetUserById.js.ejs +36 -0
  36. package/templates/clean-architecture/js/src/usecases/GetUserById.spec.js.ejs +48 -0
  37. package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +28 -0
  38. package/templates/clean-architecture/js/src/utils/errorMessages.js +1 -0
  39. package/templates/clean-architecture/js/src/utils/httpCodes.js +2 -0
  40. package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -3
  41. package/templates/clean-architecture/ts/src/domain/user.ts +3 -1
  42. package/templates/clean-architecture/ts/src/index.ts.ejs +4 -0
  43. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +55 -9
  44. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +32 -3
  45. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +26 -6
  46. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +57 -15
  47. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +38 -23
  48. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +14 -8
  49. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +33 -10
  50. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  51. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +1 -1
  52. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +9 -1
  53. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +16 -0
  54. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +3 -2
  55. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +35 -0
  56. package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +1 -0
  57. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts.ejs +24 -0
  58. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts.ejs +21 -0
  59. package/templates/clean-architecture/ts/src/usecases/getUserById.spec.ts.ejs +47 -0
  60. package/templates/clean-architecture/ts/src/usecases/getUserById.ts.ejs +23 -0
  61. package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +1 -0
  62. package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +25 -0
  63. package/templates/clean-architecture/ts/src/utils/errorMessages.ts +1 -0
  64. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
  65. package/templates/common/.cursorrules.ejs +9 -0
  66. package/templates/common/.env.example.ejs +17 -10
  67. package/templates/common/.gitlab-ci.yml.ejs +3 -1
  68. package/templates/common/Jenkinsfile.ejs +10 -1
  69. package/templates/common/README.md.ejs +64 -19
  70. package/templates/common/_circleci/config.yml.ejs +96 -0
  71. package/templates/common/_github/workflows/ci.yml.ejs +1 -1
  72. package/templates/common/auth/js/controllers/authController.js.ejs +168 -0
  73. package/templates/common/auth/js/controllers/authController.spec.js.ejs +148 -0
  74. package/templates/common/auth/js/middleware/authMiddleware.js.ejs +58 -0
  75. package/templates/common/auth/js/middleware/authMiddleware.spec.js.ejs +108 -0
  76. package/templates/common/auth/js/routes/authRoutes.js.ejs +16 -0
  77. package/templates/common/auth/js/services/jwtService.js.ejs +54 -0
  78. package/templates/common/auth/js/services/jwtService.spec.js.ejs +84 -0
  79. package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +161 -0
  80. package/templates/common/auth/ts/controllers/authController.ts.ejs +165 -0
  81. package/templates/common/auth/ts/middleware/authMiddleware.spec.ts.ejs +128 -0
  82. package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +59 -0
  83. package/templates/common/auth/ts/routes/authRoutes.ts.ejs +20 -0
  84. package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +89 -0
  85. package/templates/common/auth/ts/services/jwtService.ts.ejs +60 -0
  86. package/templates/common/bitbucket-pipelines.yml.ejs +60 -0
  87. package/templates/common/caching/clean/js/CreateUser.js.ejs +14 -5
  88. package/templates/common/caching/clean/js/DeleteUser.js.ejs +2 -1
  89. package/templates/common/caching/clean/js/GetUserById.js.ejs +39 -0
  90. package/templates/common/caching/clean/js/UpdateUser.js.ejs +2 -1
  91. package/templates/common/caching/clean/ts/createUser.ts.ejs +14 -6
  92. package/templates/common/caching/clean/ts/deleteUser.ts.ejs +2 -1
  93. package/templates/common/caching/clean/ts/getUserById.ts.ejs +32 -0
  94. package/templates/common/caching/clean/ts/updateUser.ts.ejs +2 -2
  95. package/templates/common/database/js/models/User.js.ejs +14 -1
  96. package/templates/common/database/js/models/User.js.mongoose.ejs +7 -0
  97. package/templates/common/database/js/models/User.spec.js.ejs +12 -0
  98. package/templates/common/database/ts/models/User.spec.ts.ejs +10 -0
  99. package/templates/common/database/ts/models/User.ts.ejs +17 -0
  100. package/templates/common/database/ts/models/User.ts.mongoose.ejs +8 -0
  101. package/templates/common/docker-compose.yml.ejs +14 -0
  102. package/templates/common/ecosystem.config.js.ejs +9 -3
  103. package/templates/common/eslint.config.mjs.ejs +3 -0
  104. package/templates/common/jest.config.js.ejs +11 -9
  105. package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +1 -1
  106. package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
  107. package/templates/common/migrations/init.js.ejs +5 -4
  108. package/templates/common/package.json.ejs +10 -2
  109. package/templates/common/prompts/project-context.md.ejs +8 -1
  110. package/templates/common/scripts/run-e2e.js.ejs +26 -10
  111. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +149 -107
  112. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +88 -47
  113. package/templates/common/swagger.yml.ejs +148 -0
  114. package/templates/common/tsconfig.eslint.json +15 -0
  115. package/templates/common/tsconfig.json +2 -1
  116. package/templates/common/views/ejs/index.ejs +264 -30
  117. package/templates/common/views/ejs/login.ejs.ejs +244 -0
  118. package/templates/common/views/ejs/signup.ejs.ejs +282 -0
  119. package/templates/common/views/pug/index.pug +269 -38
  120. package/templates/common/views/pug/login.pug.ejs +195 -0
  121. package/templates/common/views/pug/signup.pug.ejs +241 -0
  122. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +6 -0
  123. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +6 -0
  124. package/templates/mvc/js/src/config/env.js.ejs +12 -3
  125. package/templates/mvc/js/src/controllers/userController.js.ejs +29 -5
  126. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +27 -12
  127. package/templates/mvc/js/src/graphql/context.js.ejs +14 -3
  128. package/templates/mvc/js/src/graphql/context.spec.js.ejs +36 -21
  129. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +10 -5
  130. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
  131. package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +1 -1
  132. package/templates/mvc/js/src/index.js.ejs +16 -3
  133. package/templates/mvc/js/src/routes/api.js.ejs +14 -0
  134. package/templates/mvc/js/src/routes/api.spec.js.ejs +3 -0
  135. package/templates/mvc/js/src/utils/errorMessages.js +1 -0
  136. package/templates/mvc/js/src/utils/httpCodes.js +1 -0
  137. package/templates/mvc/ts/src/config/env.ts.ejs +12 -3
  138. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +95 -7
  139. package/templates/mvc/ts/src/controllers/userController.ts.ejs +68 -11
  140. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +36 -23
  141. package/templates/mvc/ts/src/graphql/context.ts.ejs +15 -6
  142. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +32 -10
  143. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  144. package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +1 -1
  145. package/templates/mvc/ts/src/index.ts.ejs +15 -3
  146. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +6 -0
  147. package/templates/mvc/ts/src/routes/api.ts.ejs +15 -0
  148. package/templates/mvc/ts/src/utils/errorMessages.ts +1 -0
  149. package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
  150. package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -12
  151. package/templates/clean-architecture/js/src/usecases/CreateUser.js +0 -14
  152. package/templates/clean-architecture/js/src/usecases/DeleteUser.js +0 -11
  153. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +0 -12
  154. package/templates/clean-architecture/js/src/usecases/UpdateUser.js +0 -11
  155. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -13
  156. package/templates/clean-architecture/ts/src/usecases/createUser.ts +0 -13
  157. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +0 -9
  158. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +0 -10
  159. package/templates/clean-architecture/ts/src/usecases/updateUser.ts +0 -9
  160. package/templates/mvc/js/src/routes/api.js +0 -10
  161. package/templates/mvc/ts/src/routes/api.ts +0 -12
package/lib/prompts.js CHANGED
@@ -74,7 +74,7 @@ export const getProjectDetails = async (options = {}) => {
74
74
  type: 'select',
75
75
  name: 'ciProvider',
76
76
  message: 'Select CI/CD Provider:',
77
- choices: ['None', 'GitHub Actions', 'Jenkins', 'GitLab CI'],
77
+ choices: ['None', 'GitHub Actions', 'Jenkins', 'GitLab CI', 'CircleCI', 'Bitbucket Pipelines'],
78
78
  default: 'None',
79
79
  when: !options.ciProvider
80
80
  },
@@ -85,6 +85,25 @@ export const getProjectDetails = async (options = {}) => {
85
85
  choices: ['No', 'Yes'],
86
86
  default: "No",
87
87
  when: (answers) => options.includeSecurity === undefined && (options.ciProvider || answers.ciProvider) !== 'None'
88
+ },
89
+ {
90
+ type: 'select',
91
+ name: 'advancedOptions',
92
+ message: 'Enable Advanced Options (Authentication, etc.)?',
93
+ choices: ['No', 'Yes'],
94
+ default: 'No',
95
+ when: options.advancedOptions === undefined
96
+ },
97
+ {
98
+ type: 'select',
99
+ name: 'auth',
100
+ message: 'Select Authentication Mode:',
101
+ choices: ['None', 'JWT'],
102
+ default: 'None',
103
+ when: (answers) => {
104
+ const advanced = options.advancedOptions !== undefined ? options.advancedOptions : answers.advancedOptions;
105
+ return (advanced === 'Yes' || advanced === true) && !options.auth;
106
+ }
88
107
  }
89
108
  ];
90
109
 
@@ -96,5 +115,25 @@ export const getProjectDetails = async (options = {}) => {
96
115
  result.includeSecurity = result.includeSecurity === 'Yes';
97
116
  }
98
117
 
118
+ if (typeof result.advancedOptions === 'string') {
119
+ result.advancedOptions = result.advancedOptions === 'Yes';
120
+ }
121
+
122
+ // Normalize auth to array if it's a string from the list prompt
123
+ if (typeof result.auth === 'string') {
124
+ result.auth = [result.auth];
125
+ }
126
+
127
+ // Default auth if not provided
128
+ if (!result.auth) {
129
+ result.auth = ['None'];
130
+ }
131
+
132
+ // Filter out 'None' if 'JWT' is selected
133
+ if (result.auth.includes('JWT')) {
134
+ result.auth = result.auth.filter(a => a !== 'None');
135
+ }
136
+
99
137
  return result;
100
138
  };
139
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-quickstart-structure",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "The ultimate nodejs quickstart structure CLI to scaffold Node.js microservices with MVC or Clean Architecture",
6
6
  "main": "bin/index.js",
@@ -63,11 +63,12 @@
63
63
  "inquirer": "^13.3.2"
64
64
  },
65
65
  "overrides": {
66
- "esbuild": "^0.25.0"
66
+ "esbuild": "^0.25.0",
67
+ "vite": "^6.4.2"
67
68
  },
68
69
  "devDependencies": {
69
70
  "snyk": "^1.1303.2",
70
- "vitepress": "^1.0.0-rc.45"
71
+ "vitepress": "^1.6.4"
71
72
  },
72
73
  "files": [
73
74
  "bin",
@@ -76,4 +77,4 @@
76
77
  "README.md",
77
78
  "CHANGELOG.md"
78
79
  ]
79
- }
80
+ }
@@ -1,9 +1,11 @@
1
1
  class User {
2
- constructor(id, name, email) {
2
+ constructor(id, name, email, password) {
3
3
  this.id = id;
4
4
  this.name = name;
5
5
  this.email = email;
6
+ this.password = password;
6
7
  }
8
+
7
9
  }
8
10
 
9
11
  module.exports = User;
@@ -1,5 +1,7 @@
1
1
  const startServer = require('./infrastructure/webserver/server');
2
+ <% if (database !== 'None' || communication === 'Kafka') { -%>
2
3
  const logger = require('./infrastructure/log/logger');
4
+ <% } -%>
3
5
  <% if (communication === 'Kafka') { -%>
4
6
  const { connectKafka } = require('./infrastructure/messaging/kafkaClient');
5
7
  <% } -%>
@@ -33,15 +33,24 @@ const envSchema = z.object({
33
33
  <%_ if (communication === 'Kafka') { -%>
34
34
  KAFKA_BROKER: z.string(),
35
35
  <%_ } -%>
36
+ <%_ if (auth.includes('JWT')) { -%>
37
+ JWT_SECRET: z.string(),
38
+ JWT_EXPIRES_IN: z.string().default('15m'),
39
+ JWT_REFRESH_SECRET: z.string().default('your-secret-refresh-key'),
40
+ JWT_REFRESH_EXPIRES_IN: z.string().default('7d'),
41
+ <%_ } -%>
36
42
  });
37
43
 
38
44
  const _env = envSchema.safeParse(process.env);
39
45
 
40
46
  if (!_env.success) {
41
- logger.error('❌ Invalid environment variables:', _env.error.format());
42
- process.exit(1);
47
+ if (process.env.NODE_ENV !== 'test') {
48
+ logger.error('❌ Invalid environment variables:', _env.error.format());
49
+ process.exit(1);
50
+ }
51
+ logger.warn('⚠️ Environment validation failed. Continuing in test mode.');
43
52
  }
44
53
 
45
- const env = _env.data;
54
+ const env = _env.success ? _env.data : process.env;
46
55
 
47
56
  module.exports = { env };
@@ -6,8 +6,17 @@ class UserRepository {
6
6
  const newUser = await UserModel.create(user);
7
7
  return newUser;
8
8
  <%_ } else { -%>
9
- const newUser = await UserModel.create({ name: user.name, email: user.email });
10
- <%_ if (database === 'MongoDB') { -%>
9
+ const newUser = await UserModel.create({
10
+ name: user.name,
11
+ email: user.email,
12
+ <% if (auth.includes('JWT')) { %>password: user.password<% } %>
13
+ });
14
+ <%_ if (auth.includes('JWT')) { -%>
15
+ // Ensure password is not returned in the save result
16
+ const result = { ...user, id: newUser.id || newUser._id.toString() };
17
+ delete result.password;
18
+ return result;
19
+ <%_ } else if (database === 'MongoDB') { -%>
11
20
  return { ...user, id: newUser._id.toString() };
12
21
  <%_ } else { -%>
13
22
  return { ...user, id: newUser.id };
@@ -15,6 +24,20 @@ class UserRepository {
15
24
  <%_ } -%>
16
25
  }
17
26
 
27
+ async findById(id) {
28
+ <%_ if (database === 'None') { -%>
29
+ return await UserModel.findByPk(id);
30
+ <%_ } else if (database === 'MongoDB') { -%>
31
+ const user = await UserModel.findById(id);
32
+ if (!user) return null;
33
+ return { id: user._id.toString(), name: user.name, email: user.email };
34
+ <%_ } else { -%>
35
+ const user = await UserModel.findByPk(id);
36
+ if (!user) return null;
37
+ return { id: user.id || 0, name: user.name, email: user.email };
38
+ <%_ } -%>
39
+ }
40
+
18
41
  async getUsers() {
19
42
  <%_ if (database === 'None') { -%>
20
43
  return await UserModel.find();
@@ -46,6 +46,33 @@ describe('UserRepository', () => {
46
46
  });
47
47
  });
48
48
 
49
+ describe('findById', () => {
50
+ it('should return a user if found (Happy Path)', async () => {
51
+ const id = '1';
52
+ const userData = { id, name: 'Test User', email: 'test@example.com' };
53
+ <%_ if (database === 'MongoDB') { -%>
54
+ UserModel.findById.mockResolvedValue({ _id: id, ...userData });
55
+ <%_ } else if (database === 'None') { -%>
56
+ UserModel.findByPk.mockResolvedValue(userData);
57
+ <%_ } else { -%>
58
+ UserModel.findByPk.mockResolvedValue(userData);
59
+ <%_ } -%>
60
+ const result = await userRepository.findById(id);
61
+ expect(result).toEqual(userData);
62
+ });
63
+
64
+ it('should return null if user not found (Error Handling)', async () => {
65
+ const id = '999';
66
+ <%_ if (database === 'MongoDB') { -%>
67
+ UserModel.findById.mockResolvedValue(null);
68
+ <%_ } else { -%>
69
+ UserModel.findByPk.mockResolvedValue(null);
70
+ <%_ } -%>
71
+ const result = await userRepository.findById(id);
72
+ expect(result).toBeNull();
73
+ });
74
+ });
75
+
49
76
  describe('getUsers', () => {
50
77
  it('should return a list of mapped UserEntities (Happy Path)', async () => {
51
78
  <%_ if (database === 'MongoDB') { -%>
@@ -10,6 +10,8 @@ const apiRoutes = require('../../interfaces/routes/api');
10
10
  const swaggerUi = require('swagger-ui-express');
11
11
  const swaggerSpecs = require('./swagger');
12
12
  <%_ } -%>
13
+ <%_ if (auth.includes('JWT')) { -%>const authRoutes = require('../../interfaces/routes/authRoutes');<%_ } -%>
14
+
13
15
  <%_ if (communication === 'GraphQL') { -%>
14
16
  const { ApolloServer } = require('@apollo/server');
15
17
  const { expressMiddleware } = require('@as-integrations/express4');
@@ -33,6 +35,7 @@ const startServer = async () => {
33
35
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
34
36
  app.use('/api', apiRoutes);
35
37
  <%_ } -%>
38
+ <%_ if (auth.includes('JWT')) { -%>app.use('/api/auth', authRoutes);<%_ } -%>
36
39
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
37
40
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
38
41
  <%_ } -%>
@@ -0,0 +1,49 @@
1
+ const startServer = require('@/infrastructure/webserver/server');
2
+ const express = require('express');
3
+ const logger = require('@/infrastructure/log/logger');
4
+
5
+ jest.mock('express', () => {
6
+ const mRouter = {
7
+ get: jest.fn(),
8
+ post: jest.fn(),
9
+ patch: jest.fn(),
10
+ delete: jest.fn(),
11
+ use: jest.fn(),
12
+ };
13
+ const mApp = {
14
+ use: jest.fn(),
15
+ listen: jest.fn((port, cb) => {
16
+ if (cb) cb();
17
+ return { close: jest.fn() };
18
+ }),
19
+ };
20
+ const mExpress = jest.fn(() => mApp);
21
+ mExpress.Router = jest.fn(() => mRouter);
22
+ mExpress.json = jest.fn(() => (req, res, next) => next());
23
+ mExpress.static = jest.fn(() => (req, res, next) => next());
24
+ mExpress.urlencoded = jest.fn(() => (req, res, next) => next());
25
+ return mExpress;
26
+ });
27
+
28
+ jest.mock('@/infrastructure/log/logger', () => ({
29
+ info: jest.fn(),
30
+ error: jest.fn(),
31
+ }));
32
+
33
+ jest.mock('@/utils/gracefulShutdown', () => jest.fn());
34
+
35
+ // Mock standard middlewares as pass-through
36
+ jest.mock('cors', () => () => (req, res, next) => next());
37
+ jest.mock('helmet', () => () => (req, res, next) => next());
38
+ jest.mock('hpp', () => () => (req, res, next) => next());
39
+ jest.mock('express-rate-limit', () => () => (req, res, next) => next());
40
+ jest.mock('morgan', () => () => (req, res, next) => next());
41
+
42
+ describe('Server initialization', () => {
43
+ it('should start the server successfully', async () => {
44
+ await expect(startServer()).resolves.not.toThrow();
45
+ const app = express();
46
+ expect(app.listen).toHaveBeenCalled();
47
+ expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('Server running on port'));
48
+ });
49
+ });
@@ -0,0 +1,14 @@
1
+ <% if (communication === 'REST APIs' || communication === 'Kafka') { -%>
2
+ const swaggerDocument = require('@/infrastructure/webserver/swagger');
3
+
4
+ jest.mock('yamljs', () => ({
5
+ load: jest.fn().mockReturnValue({ swagger: '2.0', info: { title: 'Test API' } }),
6
+ }));
7
+
8
+ describe('Swagger Documentation', () => {
9
+ it('should load the swagger document', () => {
10
+ expect(swaggerDocument).toBeDefined();
11
+ expect(swaggerDocument.swagger).toBe('2.0');
12
+ });
13
+ });
14
+ <% } -%>
@@ -2,6 +2,7 @@ const CreateUser = require('../../usecases/CreateUser');
2
2
  const GetAllUsers = require('../../usecases/GetAllUsers');
3
3
  const UpdateUser = require('../../usecases/UpdateUser');
4
4
  const DeleteUser = require('../../usecases/DeleteUser');
5
+ const GetUserById = require('../../usecases/GetUserById');
5
6
  const UserRepository = require('../../infrastructure/repositories/UserRepository');
6
7
  const ERROR_MESSAGES = require('../../utils/errorMessages');
7
8
  <% if (communication !== 'GraphQL') { -%>
@@ -20,6 +21,7 @@ class UserController {
20
21
  this.getAllUsersUseCase = new GetAllUsers(this.userRepository);
21
22
  this.updateUserUseCase = new UpdateUser(this.userRepository);
22
23
  this.deleteUserUseCase = new DeleteUser(this.userRepository);
24
+ this.getUserByIdUseCase = new GetUserById(this.userRepository);
23
25
  }
24
26
 
25
27
  <% if (communication === 'GraphQL') { -%>
@@ -33,9 +35,14 @@ class UserController {
33
35
  }
34
36
 
35
37
  async createUser(data) {
36
- const { name, email } = data;
38
+ const { name, email, password } = data;
37
39
  try {
38
- const user = await this.createUserUseCase.execute(name, email);
40
+ <% if (auth.includes('JWT')) { %>
41
+ if (!password) {
42
+ throw new Error('Password is required');
43
+ }
44
+ <% } %>
45
+ const user = await this.createUserUseCase.execute(name, email, password);
39
46
  <%_ if (communication === 'Kafka') { -%>
40
47
  await sendMessage('user-topic', JSON.stringify({
41
48
  action: KAFKA_ACTIONS.USER_CREATED,
@@ -49,6 +56,17 @@ class UserController {
49
56
  }
50
57
  }
51
58
 
59
+ async getUserById(id) {
60
+ try {
61
+ const user = await this.getUserByIdUseCase.execute(id);
62
+ if (!user) throw new Error(ERROR_MESSAGES.USER_NOT_FOUND);
63
+ return user;
64
+ } catch (error) {
65
+ logger.error(`${ERROR_MESSAGES.FETCH_USER_ERROR}:`, error);
66
+ throw error;
67
+ }
68
+ }
69
+
52
70
  async updateUser(id, data) {
53
71
  try {
54
72
  const user = await this.updateUserUseCase.execute(id, data);
@@ -94,9 +112,14 @@ class UserController {
94
112
  }
95
113
 
96
114
  async createUser(req, res, next) {
97
- const { name, email } = req.body || {};
115
+ const { name, email, password } = req.body || {};
98
116
  try {
99
- const user = await this.createUserUseCase.execute(name, email);
117
+ <% if (auth.includes('JWT')) { %>
118
+ if (!password) {
119
+ return res.status(HTTP_STATUS.BAD_REQUEST).json({ error: 'Password is required' });
120
+ }
121
+ <% } %>
122
+ const user = await this.createUserUseCase.execute(name, email, password);
100
123
  <%_ if (communication === 'Kafka') { -%>
101
124
  await sendMessage('user-topic', JSON.stringify({
102
125
  action: KAFKA_ACTIONS.USER_CREATED,
@@ -150,6 +173,20 @@ class UserController {
150
173
  next(error);
151
174
  }
152
175
  }
176
+
177
+ async getUserById(req, res, next) {
178
+ const { id } = req.params;
179
+ try {
180
+ const user = await this.getUserByIdUseCase.execute(id);
181
+ if (!user) {
182
+ return res.status(HTTP_STATUS.NOT_FOUND).json({ error: ERROR_MESSAGES.USER_NOT_FOUND });
183
+ }
184
+ res.json(user);
185
+ } catch (error) {
186
+ logger.error(`${ERROR_MESSAGES.FETCH_USER_ERROR}:`, error);
187
+ next(error);
188
+ }
189
+ }
153
190
  <% } -%>
154
191
  }
155
192
 
@@ -1,5 +1,7 @@
1
1
  const UserController = require('@/interfaces/controllers/userController');
2
+ <% if (communication === 'GraphQL') { -%>
2
3
  const ERROR_MESSAGES = require('@/utils/errorMessages');
4
+ <% } -%>
3
5
  const CreateUser = require('@/usecases/CreateUser');
4
6
  const GetAllUsers = require('@/usecases/GetAllUsers');
5
7
  const UpdateUser = require('@/usecases/UpdateUser');
@@ -9,6 +11,7 @@ jest.mock('@/usecases/CreateUser');
9
11
  jest.mock('@/usecases/GetAllUsers');
10
12
  jest.mock('@/usecases/UpdateUser');
11
13
  jest.mock('@/usecases/DeleteUser');
14
+ jest.mock('@/usecases/GetUserById');
12
15
  <%_ if (communication === 'Kafka') { -%>
13
16
  jest.mock('@/infrastructure/messaging/kafkaClient', () => ({
14
17
  sendMessage: jest.fn().mockResolvedValue(undefined)
@@ -22,6 +25,7 @@ describe('UserController (Clean Architecture)', () => {
22
25
  let mockGetAllUsersUseCase;
23
26
  let mockUpdateUserUseCase;
24
27
  let mockDeleteUserUseCase;
28
+ let mockGetUserByIdUseCase;
25
29
  <%_ if (communication !== 'GraphQL') { -%>
26
30
  let mockRequest;
27
31
  let mockResponse;
@@ -38,6 +42,8 @@ describe('UserController (Clean Architecture)', () => {
38
42
  mockGetAllUsersUseCase = GetAllUsers.mock.instances[0];
39
43
  mockUpdateUserUseCase = UpdateUser.mock.instances[0];
40
44
  mockDeleteUserUseCase = DeleteUser.mock.instances[0];
45
+ const GetUserById = require('@/usecases/GetUserById');
46
+ mockGetUserByIdUseCase = GetUserById.mock.instances[0];
41
47
 
42
48
  <%_ if (communication !== 'GraphQL') { -%>
43
49
  mockRequest = {
@@ -82,13 +88,14 @@ describe('UserController (Clean Architecture)', () => {
82
88
 
83
89
  describe('createUser', () => {
84
90
  it('should successfully create a new user (Happy Path)', async () => {
85
- const payload = { name: 'Alice', email: 'alice@example.com' };
91
+ const payload = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
86
92
  <%_ if (communication === 'GraphQL') { -%>
87
93
  const dataArg = payload;
88
94
  <%_ } else { -%>
89
95
  mockRequest.body = payload;
90
96
  <%_ } -%>
91
97
  const expectedUser = { id: '1', ...payload };
98
+ if (expectedUser.password) delete expectedUser.password;
92
99
 
93
100
  mockCreateUserUseCase.execute.mockResolvedValue(expectedUser);
94
101
 
@@ -105,7 +112,51 @@ describe('UserController (Clean Architecture)', () => {
105
112
 
106
113
  expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
107
114
  <%_ } -%>
108
- expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email);
115
+ expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email, payload.password);
116
+ });
117
+
118
+ <% if (auth.includes('JWT')) { %>
119
+ it('should fail to create a user when password is missing and JWT is enabled', async () => {
120
+ const payload = { name: 'Alice', email: 'alice@example.com' };
121
+ <%_ if (communication === 'GraphQL') { -%>
122
+ await expect(userController.createUser(payload)).rejects.toThrow('Password is required');
123
+ <%_ } else { -%>
124
+ mockRequest.body = payload;
125
+ await userController.createUser(mockRequest, mockResponse, mockNext);
126
+ expect(mockResponse.status).toHaveBeenCalledWith(400);
127
+ expect(mockResponse.json).toHaveBeenCalledWith({ error: 'Password is required' });
128
+ <%_ } -%>
129
+ });
130
+ <% } %>
131
+ });
132
+
133
+ describe('getUserById', () => {
134
+ it('should return a user if found (Happy Path)', async () => {
135
+ const id = '1';
136
+ const userMock = { id, name: 'Test', email: 'test@example.com' };
137
+ mockGetUserByIdUseCase.execute.mockResolvedValue(userMock);
138
+
139
+ <%_ if (communication === 'GraphQL') { -%>
140
+ const result = await userController.getUserById(id);
141
+ expect(result).toEqual(userMock);
142
+ <%_ } else { -%>
143
+ mockRequest.params = { id };
144
+ await userController.getUserById(mockRequest, mockResponse, mockNext);
145
+ expect(mockResponse.json).toHaveBeenCalledWith(userMock);
146
+ <%_ } -%>
147
+ });
148
+
149
+ it('should return 404 if user not found (Error Handling)', async () => {
150
+ const id = '999';
151
+ mockGetUserByIdUseCase.execute.mockResolvedValue(null);
152
+
153
+ <%_ if (communication === 'GraphQL') { -%>
154
+ await expect(userController.getUserById(id)).rejects.toThrow(ERROR_MESSAGES.USER_NOT_FOUND);
155
+ <%_ } else { -%>
156
+ mockRequest.params = { id };
157
+ await userController.getUserById(mockRequest, mockResponse, mockNext);
158
+ expect(mockResponse.status).toHaveBeenCalledWith(404);
159
+ <%_ } -%>
109
160
  });
110
161
  });
111
162
 
@@ -138,6 +189,19 @@ describe('UserController (Clean Architecture)', () => {
138
189
  expect(mockUpdateUserUseCase.execute).toHaveBeenCalledWith(id, payload);
139
190
  });
140
191
 
192
+ it('should return 404 if user to update is not found (Error Handling)', async () => {
193
+ const id = '999';
194
+ mockUpdateUserUseCase.execute.mockResolvedValue(null);
195
+
196
+ <%_ if (communication === 'GraphQL') { -%>
197
+ await expect(userController.updateUser(id, { name: 'Fail' })).rejects.toThrow(ERROR_MESSAGES.USER_NOT_FOUND);
198
+ <%_ } else { -%>
199
+ mockRequest.params = { id };
200
+ await userController.updateUser(mockRequest, mockResponse, mockNext);
201
+ expect(mockResponse.status).toHaveBeenCalledWith(404);
202
+ <%_ } -%>
203
+ });
204
+
141
205
  it('should handle database errors during update (Error Handling)', async () => {
142
206
  const id = '1';
143
207
  const error = new Error('Database Error');
@@ -145,6 +209,7 @@ describe('UserController (Clean Architecture)', () => {
145
209
  <%_ if (communication === 'GraphQL') { -%>
146
210
  await expect(userController.updateUser(id, { name: 'Fail' })).rejects.toThrow(error);
147
211
  <%_ } else { -%>
212
+ mockRequest.params = { id };
148
213
  await userController.updateUser(mockRequest, mockResponse, mockNext);
149
214
  expect(mockNext).toHaveBeenCalledWith(error);
150
215
  <%_ } -%>
@@ -207,9 +272,9 @@ describe('UserController (Clean Architecture)', () => {
207
272
  const error = new Error('Database Error');
208
273
  mockCreateUserUseCase.execute.mockRejectedValue(error);
209
274
  <%_ if (communication === 'GraphQL') { -%>
210
- await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com' })).rejects.toThrow(error);
275
+ await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> })).rejects.toThrow(error);
211
276
  <%_ } else { -%>
212
- mockRequest.body = { name: 'Alice', email: 'alice@example.com' };
277
+ mockRequest.body = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
213
278
  await userController.createUser(mockRequest, mockResponse, mockNext);
214
279
  expect(mockNext).toHaveBeenCalledWith(error);
215
280
  <%_ } -%>
@@ -1,13 +1,20 @@
1
+ <% if (auth.includes('JWT')) { %>const JwtService = require('../../infrastructure/auth/jwtService');<% } %>
1
2
  const UserRepository = require('../../infrastructure/repositories/UserRepository');
2
3
 
3
4
  const gqlContext = async ({ req }) => {
4
- const token = req.headers.authorization || '';
5
- const userRepository = new UserRepository();
6
-
7
- return {
8
- token,
9
- userRepository
5
+ const context = {
6
+ userRepository: new UserRepository()
10
7
  };
8
+ <% if (auth.includes('JWT')) { %>
9
+ const authHeader = req.headers.authorization || '';
10
+ if (authHeader.startsWith('Bearer ')) {
11
+ const token = authHeader.split(' ')[1];
12
+ const user = JwtService.verifyToken(token);
13
+ if (user) {
14
+ context.user = user;
15
+ }
16
+ }<% } %>
17
+ return context;
11
18
  };
12
19
 
13
20
  module.exports = { gqlContext };
@@ -1,31 +1,48 @@
1
+ <% if (auth.includes('JWT')) { %>const JwtService = require('@/infrastructure/auth/jwtService');
2
+ jest.mock('@/infrastructure/auth/jwtService');<% } %>
1
3
  const { gqlContext } = require('@/interfaces/graphql/context');
2
- const { resolvers } = require('@/interfaces/graphql/resolvers');
3
- const { typeDefs } = require('@/interfaces/graphql/typeDefs');
4
+ const { resolvers } = require('@/interfaces/graphql/index');
5
+ const { typeDefs } = require('@/interfaces/graphql/typeDefs/index');
4
6
 
5
7
  jest.mock('@/infrastructure/repositories/UserRepository');
6
8
 
7
9
  describe('GraphQL Context', () => {
8
- it('should exercise GraphQL index entry points', () => {
9
- expect(resolvers).toBeDefined();
10
- expect(typeDefs).toBeDefined();
10
+ afterEach(() => {
11
+ jest.clearAllMocks();
11
12
  });
12
- it('should return context with token when authorization header is present', async () => {
13
- const mockRequest = {
14
- headers: {
15
- authorization: 'Bearer token123',
16
- },
17
- };
18
13
 
19
- const context = await gqlContext({ req: mockRequest });
20
- expect(context.token).toBe('Bearer token123');
21
- });
14
+ it('should return context with user when authorization header is present and valid', async () => {
15
+ <% if (auth.includes('JWT')) { %>
16
+ const mockUser = { id: '1', email: 'test@test.com' };
17
+ JwtService.verifyToken.mockReturnValue(mockUser);
18
+ const mockRequest = {
19
+ headers: {
20
+ authorization: 'Bearer valid-token',
21
+ },
22
+ };
22
23
 
23
- it('should return context with empty token when authorization header is missing', async () => {
24
- const mockRequest = {
25
- headers: {},
26
- };
24
+ const context = await gqlContext({ req: mockRequest });
25
+ expect(context.user).toEqual(mockUser);
26
+ expect(JwtService.verifyToken).toHaveBeenCalledWith('valid-token');
27
+ <% } else { %>
28
+ const mockRequest = {
29
+ headers: {
30
+ authorization: 'Bearer token123',
31
+ },
32
+ };
33
+ const context = await gqlContext({ req: mockRequest });
34
+ expect(context.user).toBeUndefined();
35
+ <% } %>
36
+ expect(context.userRepository).toBeDefined();
37
+ });
38
+
39
+ it('should return context without user when authorization header is missing', async () => {
40
+ const mockRequest = {
41
+ headers: {},
42
+ };
27
43
 
28
- const context = await gqlContext({ req: mockRequest });
29
- expect(context.token).toBe('');
30
- });
44
+ const context = await gqlContext({ req: mockRequest });
45
+ expect(context.user).toBeUndefined();
46
+ expect(context.userRepository).toBeDefined();
47
+ });
31
48
  });
@@ -4,18 +4,23 @@ const userController = new UserController();
4
4
 
5
5
  const userResolvers = {
6
6
  Query: {
7
- getAllUsers: async () => {
7
+ getAllUsers: async (_, __, <% if (auth.includes('JWT')) { %>{ user }<% } else { %>_context<% } %>) => {
8
+ <% if (auth.includes('JWT')) { %>if (!user) throw new Error('Unauthorized');<% } %>
8
9
  return await userController.getUsers();
9
10
  }
10
11
  },
11
12
  Mutation: {
12
- createUser: async (_, { name, email }) => {
13
- return await userController.createUser({ name, email });
13
+ createUser: async (_, { name, email<% if (auth.some(a => a !=='None')) { %>, password<% } %> }) => {
14
+ // Create user is typically public for registration
15
+ const user = await userController.createUser({ name, email<% if (auth.some(a => a !=='None')) { %>, password<% } %> });
16
+ return user;
14
17
  },
15
- updateUser: async (_, { id, name, email }) => {
18
+ updateUser: async (_, { id, name, email }, <% if (auth.includes('JWT')) { %>{ user }<% } else { %>_context<% } %>) => {
19
+ <% if (auth.includes('JWT')) { %>if (!user) throw new Error('Unauthorized');<% } %>
16
20
  return await userController.updateUser(id, { name, email });
17
21
  },
18
- deleteUser: async (_, { id }) => {
22
+ deleteUser: async (_, { id }, <% if (auth.includes('JWT')) { %>{ user }<% } else { %>_context<% } %>) => {
23
+ <% if (auth.includes('JWT')) { %>if (!user) throw new Error('Unauthorized');<% } %>
19
24
  return await userController.deleteUser(id);
20
25
  }
21
26
  }<%_ if (database === 'MongoDB') { -%>,