nodejs-quickstart-structure 2.0.1 → 2.1.1

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 (165) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +64 -66
  3. package/bin/index.js +5 -2
  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 +6 -0
  9. package/lib/modules/database-setup.js +2 -1
  10. package/lib/modules/project-setup.js +1 -0
  11. package/lib/prompts.js +39 -0
  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 +38 -1
  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 +51 -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 +70 -5
  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 +55 -22
  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 +12 -3
  32. package/templates/clean-architecture/js/src/usecases/DeleteUser.js.ejs +27 -0
  33. package/templates/clean-architecture/js/src/usecases/DeleteUser.spec.js.ejs +9 -1
  34. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js.ejs +36 -0
  35. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +23 -1
  36. package/templates/clean-architecture/js/src/usecases/GetUserById.js.ejs +36 -0
  37. package/templates/clean-architecture/js/src/usecases/GetUserById.spec.js.ejs +48 -0
  38. package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +28 -0
  39. package/templates/clean-architecture/js/src/usecases/UpdateUser.spec.js.ejs +9 -1
  40. package/templates/clean-architecture/js/src/utils/errorMessages.js +1 -0
  41. package/templates/clean-architecture/js/src/utils/httpCodes.js +2 -0
  42. package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -3
  43. package/templates/clean-architecture/ts/src/domain/user.ts +3 -1
  44. package/templates/clean-architecture/ts/src/index.ts.ejs +4 -0
  45. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +71 -10
  46. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +32 -3
  47. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +43 -9
  48. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +57 -15
  49. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +57 -24
  50. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +14 -8
  51. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +33 -10
  52. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  53. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +1 -1
  54. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +9 -1
  55. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +16 -0
  56. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +12 -3
  57. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +35 -0
  58. package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +10 -1
  59. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts.ejs +24 -0
  60. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +9 -1
  61. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts.ejs +21 -0
  62. package/templates/clean-architecture/ts/src/usecases/getUserById.spec.ts.ejs +55 -0
  63. package/templates/clean-architecture/ts/src/usecases/getUserById.ts.ejs +23 -0
  64. package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +10 -1
  65. package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +25 -0
  66. package/templates/clean-architecture/ts/src/utils/errorMessages.ts +1 -0
  67. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
  68. package/templates/common/.cursorrules.ejs +9 -0
  69. package/templates/common/.env.example.ejs +17 -10
  70. package/templates/common/README.md.ejs +63 -18
  71. package/templates/common/auth/js/controllers/authController.js.ejs +170 -0
  72. package/templates/common/auth/js/controllers/authController.spec.js.ejs +148 -0
  73. package/templates/common/auth/js/middleware/authMiddleware.js.ejs +58 -0
  74. package/templates/common/auth/js/middleware/authMiddleware.spec.js.ejs +108 -0
  75. package/templates/common/auth/js/routes/authRoutes.js.ejs +16 -0
  76. package/templates/common/auth/js/services/jwtService.js.ejs +54 -0
  77. package/templates/common/auth/js/services/jwtService.spec.js.ejs +84 -0
  78. package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +161 -0
  79. package/templates/common/auth/ts/controllers/authController.ts.ejs +167 -0
  80. package/templates/common/auth/ts/middleware/authMiddleware.spec.ts.ejs +128 -0
  81. package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +59 -0
  82. package/templates/common/auth/ts/routes/authRoutes.ts.ejs +20 -0
  83. package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +89 -0
  84. package/templates/common/auth/ts/services/jwtService.ts.ejs +60 -0
  85. package/templates/common/babel.config.js.ejs +5 -0
  86. package/templates/common/caching/clean/js/CreateUser.js.ejs +14 -5
  87. package/templates/common/caching/clean/js/DeleteUser.js.ejs +2 -1
  88. package/templates/common/caching/clean/js/GetUserById.js.ejs +39 -0
  89. package/templates/common/caching/clean/js/UpdateUser.js.ejs +2 -1
  90. package/templates/common/caching/clean/ts/createUser.ts.ejs +14 -6
  91. package/templates/common/caching/clean/ts/deleteUser.ts.ejs +2 -1
  92. package/templates/common/caching/clean/ts/getUserById.ts.ejs +32 -0
  93. package/templates/common/caching/clean/ts/updateUser.ts.ejs +2 -1
  94. package/templates/common/caching/js/memoryCache.spec.js.ejs +2 -0
  95. package/templates/common/caching/js/redisClient.spec.js.ejs +2 -0
  96. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +2 -0
  97. package/templates/common/caching/ts/redisClient.spec.ts.ejs +2 -0
  98. package/templates/common/database/js/models/User.js.ejs +14 -1
  99. package/templates/common/database/js/models/User.js.mongoose.ejs +7 -0
  100. package/templates/common/database/js/models/User.spec.js.ejs +12 -0
  101. package/templates/common/database/js/mongoose.spec.js.ejs +2 -0
  102. package/templates/common/database/ts/models/User.spec.ts.ejs +10 -0
  103. package/templates/common/database/ts/models/User.ts.ejs +17 -0
  104. package/templates/common/database/ts/models/User.ts.mongoose.ejs +8 -0
  105. package/templates/common/database/ts/mongoose.spec.ts.ejs +2 -0
  106. package/templates/common/docker-compose.yml.ejs +12 -0
  107. package/templates/common/ecosystem.config.js.ejs +9 -3
  108. package/templates/common/eslint.config.mjs.ejs +3 -0
  109. package/templates/common/jest.config.js.ejs +13 -9
  110. package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +1 -1
  111. package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
  112. package/templates/common/migrations/init.js.ejs +5 -4
  113. package/templates/common/package.json.ejs +11 -2
  114. package/templates/common/prompts/project-context.md.ejs +8 -1
  115. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +149 -107
  116. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +88 -47
  117. package/templates/common/swagger.yml.ejs +148 -0
  118. package/templates/common/tsconfig.eslint.json +15 -0
  119. package/templates/common/tsconfig.json +3 -1
  120. package/templates/common/views/ejs/index.ejs +264 -30
  121. package/templates/common/views/ejs/login.ejs.ejs +244 -0
  122. package/templates/common/views/ejs/signup.ejs.ejs +282 -0
  123. package/templates/common/views/pug/index.pug +269 -38
  124. package/templates/common/views/pug/login.pug.ejs +195 -0
  125. package/templates/common/views/pug/signup.pug.ejs +241 -0
  126. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +6 -0
  127. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +6 -0
  128. package/templates/mvc/js/src/config/env.js.ejs +12 -3
  129. package/templates/mvc/js/src/controllers/userController.js.ejs +29 -5
  130. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +27 -12
  131. package/templates/mvc/js/src/graphql/context.js.ejs +14 -3
  132. package/templates/mvc/js/src/graphql/context.spec.js.ejs +36 -21
  133. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +10 -5
  134. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
  135. package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +1 -1
  136. package/templates/mvc/js/src/index.js.ejs +16 -3
  137. package/templates/mvc/js/src/routes/api.js.ejs +14 -0
  138. package/templates/mvc/js/src/routes/api.spec.js.ejs +3 -0
  139. package/templates/mvc/js/src/utils/errorMessages.js +1 -0
  140. package/templates/mvc/js/src/utils/httpCodes.js +1 -0
  141. package/templates/mvc/ts/src/config/env.ts.ejs +12 -3
  142. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +95 -7
  143. package/templates/mvc/ts/src/controllers/userController.ts.ejs +68 -11
  144. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +36 -23
  145. package/templates/mvc/ts/src/graphql/context.ts.ejs +15 -6
  146. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +32 -10
  147. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +15 -5
  148. package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +1 -1
  149. package/templates/mvc/ts/src/index.ts.ejs +15 -3
  150. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +6 -0
  151. package/templates/mvc/ts/src/routes/api.ts.ejs +15 -0
  152. package/templates/mvc/ts/src/utils/errorMessages.ts +1 -0
  153. package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
  154. package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -12
  155. package/templates/clean-architecture/js/src/usecases/CreateUser.js +0 -14
  156. package/templates/clean-architecture/js/src/usecases/DeleteUser.js +0 -11
  157. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +0 -12
  158. package/templates/clean-architecture/js/src/usecases/UpdateUser.js +0 -11
  159. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -13
  160. package/templates/clean-architecture/ts/src/usecases/createUser.ts +0 -13
  161. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +0 -9
  162. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +0 -10
  163. package/templates/clean-architecture/ts/src/usecases/updateUser.ts +0 -9
  164. package/templates/mvc/js/src/routes/api.js +0 -10
  165. package/templates/mvc/ts/src/routes/api.ts +0 -12
@@ -4,6 +4,8 @@ const cors = require('cors');
4
4
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>const apiRoutes = require('./routes/api');<%_ } %>
5
5
  const healthRoutes = require('./routes/healthRoute');
6
6
  <%_ if (communication === 'Kafka') { -%>const { connectKafka, sendMessage } = require('./services/kafkaService');<%_ } -%>
7
+ <%_ if (auth.includes('JWT')) { -%>const authRoutes = require('./routes/authRoutes');<%_ } -%>
8
+
7
9
  <%_ if (communication === 'GraphQL') { -%>
8
10
  const { ApolloServer } = require('@apollo/server');
9
11
  const { expressMiddleware } = require('@as-integrations/express4');
@@ -27,6 +29,7 @@ const { errorMiddleware } = require('./utils/errorMiddleware');
27
29
 
28
30
  app.use(cors());
29
31
  app.use(express.json());
32
+ app.use(express.urlencoded({ extended: true }));
30
33
  app.use(morgan('combined', { stream: { write: message => logger.info(message.trim()) } }));
31
34
 
32
35
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
@@ -40,15 +43,25 @@ app.set('view engine', '<%= viewEngine.toLowerCase() %>');
40
43
  app.use(express.static(path.join(__dirname, '../public')));<%_ } %>
41
44
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>
42
45
  app.use('/api', apiRoutes);
43
- <%_ } -%><% if (viewEngine && viewEngine !== 'None') { -%>
46
+ <%_ } -%><%_ if (auth.includes('JWT')) { -%>
47
+ app.use('/api/auth', authRoutes);
48
+ <%_ } -%>
49
+
50
+ <% if (viewEngine && viewEngine !== 'None') { -%>
44
51
  app.get('/', (req, res) => {
45
52
  res.render('index', {
46
- projectName: 'NodeJS Service',
53
+ projectName: '<%= projectName %>',
47
54
  architecture: 'MVC',
48
55
  database: '<%= database %>',
49
- communication: '<%= communication %>'
56
+ communication: '<%= communication %>',
57
+ auth: <%- JSON.stringify(auth) %>
50
58
  });
51
59
  });
60
+
61
+ <% if (auth.includes('JWT')) { %>
62
+ app.get('/login', (req, res) => res.render('login', { projectName: '<%= projectName %>' }));
63
+ app.get('/signup', (req, res) => res.render('signup', { projectName: '<%= projectName %>' }));
64
+ <% } %>
52
65
  <% } -%>
53
66
  app.use('/health', healthRoutes);
54
67
 
@@ -0,0 +1,14 @@
1
+ const { Router } = require('express');
2
+ const { getUsers, createUser, updateUser, deleteUser } = require('../controllers/userController');
3
+ <%_ if (auth.includes('JWT')) { _%>
4
+ const authMiddleware = require('../middleware/authMiddleware');
5
+ <%_ } _%>
6
+
7
+ const router = Router();
8
+
9
+ router.get('/users', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>getUsers);
10
+ router.post('/users', createUser);
11
+ router.patch('/users/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>updateUser);
12
+ router.delete('/users/:id', <% if (auth.includes('JWT')) { %>authMiddleware, <% } %>deleteUser);
13
+
14
+ module.exports = router;
@@ -14,6 +14,9 @@ jest.mock('@/controllers/userController', () => ({
14
14
  updateUser: (...args) => mockUpdateUser(...args),
15
15
  deleteUser: (...args) => mockDeleteUser(...args)
16
16
  }));
17
+ <%_ if (auth.includes('JWT')) { _%>
18
+ jest.mock('@/middleware/authMiddleware', () => (req, res, next) => next());
19
+ <%_ } _%>
17
20
 
18
21
  describe('ApiRoutes', () => {
19
22
  let app;
@@ -5,6 +5,7 @@ const ERROR_MESSAGES = {
5
5
  INTERNAL_SERVER_ERROR: 'Internal Server Error',
6
6
  BAD_REQUEST: 'Bad Request',
7
7
  FETCH_USERS_ERROR: 'Error fetching users',
8
+ FETCH_USER_ERROR: 'Error fetching user',
8
9
  CREATE_USER_ERROR: 'Error creating user',
9
10
  UPDATE_USER_ERROR: 'Error updating user',
10
11
  DELETE_USER_ERROR: 'Error deleting user',
@@ -2,6 +2,7 @@ const HTTP_STATUS = {
2
2
  OK: 200,
3
3
  CREATED: 201,
4
4
  BAD_REQUEST: 400,
5
+ UNAUTHORIZED: 401,
5
6
  NOT_FOUND: 404,
6
7
  INTERNAL_SERVER_ERROR: 500
7
8
  };
@@ -33,13 +33,22 @@ 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
- export const env = _env.data;
54
+ export const env = (_env.success ? _env.data : process.env) as unknown as z.infer<typeof envSchema>;
@@ -1,5 +1,9 @@
1
+ <% if (communication !== 'GraphQL') { -%>
1
2
  import { HTTP_STATUS } from '@/utils/httpCodes';
3
+ <% } -%>
4
+ <%_ if (communication === 'GraphQL') { _%>
2
5
  import { ERROR_MESSAGES } from '@/utils/errorMessages';
6
+ <%_ } _%>
3
7
  <% if (communication !== 'GraphQL') { -%>
4
8
  import { Request, Response, NextFunction } from 'express';
5
9
  <% } -%>
@@ -26,6 +30,12 @@ jest.mock('@/models/User', () => {
26
30
  };
27
31
  });
28
32
  const User = require('@/models/User');
33
+ <%_ if (auth.includes('JWT')) { _%>
34
+ jest.mock('bcryptjs', () => ({
35
+ hash: jest.fn().mockResolvedValue('hashed_password'),
36
+ compare: jest.fn().mockResolvedValue(true)
37
+ }));
38
+ <%_ } _%>
29
39
  <%_ if (caching === 'Redis') { -%>
30
40
  jest.mock('@/config/redisClient', () => ({
31
41
  getOrSet: jest.fn((_key, fetcher) => fetcher()),
@@ -179,7 +189,7 @@ describe('UserController', () => {
179
189
  describe('createUser', () => {
180
190
  it('should successfully create a new user (Happy Path)', async () => {
181
191
  // Arrange
182
- const payload = { name: 'Alice', email: 'alice@example.com' };
192
+ const payload = { name: 'Alice', email: 'alice@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
183
193
  <% if (communication === 'GraphQL') { -%>
184
194
  const dataArg = payload;
185
195
  <% } else { -%>
@@ -200,14 +210,21 @@ describe('UserController', () => {
200
210
  const result = await userController.createUser(dataArg) as any;
201
211
 
202
212
  // Assert
203
- expect(result!).toEqual(expectedUser);
213
+ expect(result!.password).toBeUndefined();
214
+ const { password, ...expectedUserWithoutPassword } = expectedUser as any;
215
+ expect(result!).toEqual(expectedUserWithoutPassword);
204
216
  <% } else { -%>
205
217
  await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
206
218
 
207
219
  // Assert
208
220
  expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);
209
- expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
210
- expect(User.create).toHaveBeenCalledWith(payload);
221
+ const { password: _, ...expectedUserWithoutPassword } = expectedUser as any;
222
+ expect(mockResponse.json).toHaveBeenCalledWith(expectedUserWithoutPassword);
223
+ expect(User.create).toHaveBeenCalledWith({
224
+ name: payload.name,
225
+ email: payload.email
226
+ <%_ if (auth.includes('JWT')) { _%>, password: 'hashed_password'<%_ } _%>
227
+ });
211
228
  <% } -%>
212
229
  <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
213
230
  expect(cacheService.del).toHaveBeenCalledWith('users:all');
@@ -217,10 +234,27 @@ describe('UserController', () => {
217
234
  <%_ } -%>
218
235
  });
219
236
 
237
+ <% if (auth.includes('JWT')) { %>
238
+ it('should throw error if password is missing when auth is enabled', async () => {
239
+ // Arrange
240
+ const payload = { name: 'Alice', email: 'alice@example.com' };
241
+
242
+ // Act & Assert
243
+ <% if (communication === 'GraphQL') { -%>
244
+ await expect(userController.createUser(payload as any)).rejects.toThrow('Password is required');
245
+ <% } else { -%>
246
+ mockRequest.body = payload;
247
+ await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
248
+ expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.BAD_REQUEST);
249
+ expect(mockResponse.json).toHaveBeenCalledWith({ error: 'Password is required' });
250
+ <% } -%>
251
+ });
252
+ <% } -%>
253
+
220
254
  it('should handle errors when creation fails (Error Handling)', async () => {
221
255
  // Arrange
222
256
  const error = new Error('Creation Error');
223
- const payload = { name: 'Bob', email: 'bob@example.com' };
257
+ const payload = { name: 'Bob', email: 'bob@example.com'<% if (auth.includes('JWT')) { %>, password: 'password123'<% } %> };
224
258
  <% if (communication === 'GraphQL') { -%>
225
259
  const dataArg = payload;
226
260
  <% } else { -%>
@@ -445,9 +479,17 @@ describe('UserController', () => {
445
479
  const error = new Error('Database Error');
446
480
  (User.create as jest.Mock).mockRejectedValue(error);
447
481
  <% if (communication === 'GraphQL') { -%>
448
- await expect(userController.createUser({ name: 'Alice', email: 'alice@example.com' })).rejects.toThrow(error);
482
+ await expect(userController.createUser({
483
+ name: 'Alice',
484
+ email: 'alice@example.com'
485
+ <%_ if (auth.includes('JWT')) { %>, password: 'password123' <%_ } %>
486
+ })).rejects.toThrow(error);
449
487
  <% } else { -%>
450
- mockRequest.body = { name: 'Alice', email: 'alice@example.com' };
488
+ mockRequest.body = {
489
+ name: 'Alice',
490
+ email: 'alice@example.com'
491
+ <%_ if (auth.includes('JWT')) { %>, password: 'password123' <%_ } %>
492
+ };
451
493
  await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
452
494
  expect(mockNext).toHaveBeenCalledWith(error);
453
495
  <% } -%>
@@ -475,6 +517,52 @@ describe('UserController', () => {
475
517
  mockRequest.body = { name: 'Bob' };
476
518
  await userController.updateUser(mockRequest as Request, mockResponse as Response, mockNext);
477
519
  expect(mockNext).toHaveBeenCalledWith(error);
520
+ <% } -%>
521
+ });
522
+ });
523
+
524
+ describe('getUserById', () => {
525
+ it('should successfully fetch a user by id', async () => {
526
+ const id = '1';
527
+ const expectedUser = { id, name: 'Alice', email: 'alice@test.com' };
528
+ <% if (communication === 'GraphQL') { -%>
529
+ const idArg = id;
530
+ <% } else { -%>
531
+ mockRequest.params = { id };
532
+ <% } -%>
533
+
534
+ <%_ if (database === 'MongoDB') { -%>
535
+ (User.findById as jest.Mock).mockResolvedValue(expectedUser);
536
+ <%_ } else if (database === 'None') { -%>
537
+ (User.findByPk as jest.Mock).mockResolvedValue(expectedUser);
538
+ <%_ } else { -%>
539
+ (User.findByPk as jest.Mock).mockResolvedValue(expectedUser);
540
+ <%_ } -%>
541
+
542
+ <% if (communication === 'GraphQL') { -%>
543
+ const result = await userController.getUserById(idArg);
544
+ expect(result).toEqual(expectedUser);
545
+ <% } else { -%>
546
+ await userController.getUserById(mockRequest as Request, mockResponse as Response, mockNext);
547
+ expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.OK);
548
+ expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
549
+ <% } -%>
550
+ });
551
+
552
+ it('should return 404 if user not found', async () => {
553
+ <% if (communication === 'GraphQL') { -%>
554
+ (User.findById as jest.Mock).mockResolvedValue(null);
555
+ (User.findByPk as jest.Mock).mockResolvedValue(null);
556
+ await expect(userController.getUserById('999')).rejects.toThrow();
557
+ <% } else { -%>
558
+ mockRequest.params = { id: '999' };
559
+ <%_ if (database === 'MongoDB') { -%>
560
+ (User.findById as jest.Mock).mockResolvedValue(null);
561
+ <%_ } else { -%>
562
+ (User.findByPk as jest.Mock).mockResolvedValue(null);
563
+ <%_ } -%>
564
+ await userController.getUserById(mockRequest as Request, mockResponse as Response, mockNext);
565
+ expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.NOT_FOUND);
478
566
  <% } -%>
479
567
  });
480
568
  });
@@ -12,6 +12,8 @@ import cacheService from '@/config/memoryCache';
12
12
  <%_ } -%><%_ if (communication === 'Kafka') { -%>
13
13
  import { kafkaService } from '@/services/kafkaService';
14
14
  import { KAFKA_ACTIONS } from '@/utils/kafkaEvents';
15
+ <%_ } -%><%_ if (auth.includes('JWT')) { %>
16
+ import bcrypt from 'bcryptjs';
15
17
  <%_ } -%>
16
18
 
17
19
  export class UserController {
@@ -40,22 +42,47 @@ export class UserController {
40
42
  }
41
43
  }
42
44
 
43
- async createUser(data: { name: string, email: string }) {
45
+ async getUserById(id: string) {
44
46
  try {
45
- const { name, email } = data;
47
+ <%_ if (database === 'MongoDB') { -%>
48
+ const user = await User.findById(id);
49
+ <%_ } else { -%>
50
+ const user = await User.findByPk(id);
51
+ <%_ } -%>
52
+ if (!user) throw new Error(ERROR_MESSAGES.USER_NOT_FOUND);
53
+ return user;
54
+ } catch (error) {
55
+ logger.error(`${ERROR_MESSAGES.FETCH_USER_ERROR}:`, error);
56
+ throw error;
57
+ }
58
+ }
59
+
60
+ async createUser(data: { name: string, email: string<% if (auth.includes('JWT')) { %>, password?: string<% } %> }) {
61
+ try {
62
+ const { name, email<% if (auth.includes('JWT')) { %>, password<% } %> } = data;
63
+ <% if (auth.includes('JWT')) { %>
64
+ if (!password) throw new Error('Password is required');
65
+ const hashedPassword = await bcrypt.hash(password, 10);
66
+ const user = await User.create({ name, email, password: hashedPassword });
67
+ <% } else { %>
46
68
  const user = await User.create({ name, email });
69
+ <% } %>
70
+
47
71
  <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
48
72
  await cacheService.del('users:all');
49
73
  <%_ } -%>
50
74
  <%_ if (communication === 'Kafka') { -%>
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- const userId = (user as any).id || (user as any)._id;
75
+ const rawUserForKafka = user as unknown as { id?: string | number; _id?: string | number; email: string };
76
+ const userId = rawUserForKafka.id || rawUserForKafka._id;
53
77
  await kafkaService.sendMessage('user-topic', JSON.stringify({
54
78
  action: KAFKA_ACTIONS.USER_CREATED,
55
- payload: { id: userId, email: user.email }
79
+ payload: { id: userId, email: rawUserForKafka.email }
56
80
  }), userId?.toString() || '');
57
81
  <%_ } -%>
58
- return user;
82
+ const rawUser = user as unknown as { toJSON?: () => Record<string, unknown> };
83
+ const userJson = typeof rawUser.toJSON === 'function' ? rawUser.toJSON() : (user as unknown as Record<string, unknown>);
84
+ const { password: _, ...userWithoutPassword } = userJson;
85
+ return userWithoutPassword;
59
86
  } catch (error) {
60
87
  logger.error(`${ERROR_MESSAGES.CREATE_USER_ERROR}:`, error);
61
88
  throw error;
@@ -147,22 +174,52 @@ export class UserController {
147
174
  }
148
175
  }
149
176
 
177
+ async getUserById(req: Request, res: Response, next: NextFunction) {
178
+ try {
179
+ const { id } = req.params;
180
+ <%_ if (database === 'MongoDB') { -%>
181
+ const user = await User.findById(id);
182
+ <%_ } else { -%>
183
+ const user = await User.findByPk(id);
184
+ <%_ } -%>
185
+ if (!user) {
186
+ return res.status(HTTP_STATUS.NOT_FOUND).json({ error: ERROR_MESSAGES.USER_NOT_FOUND });
187
+ }
188
+ res.status(HTTP_STATUS.OK).json(user);
189
+ } catch (error) {
190
+ logger.error(`${ERROR_MESSAGES.FETCH_USER_ERROR}:`, error);
191
+ next(error);
192
+ }
193
+ }
194
+
150
195
  async createUser(req: Request, res: Response, next: NextFunction) {
151
196
  try {
152
- const { name, email } = req.body || {};
197
+ const { name, email, password } = req.body || {};
198
+ <% if (auth.includes('JWT')) { %>
199
+ if (!password) {
200
+ return res.status(HTTP_STATUS.BAD_REQUEST).json({ error: 'Password is required' });
201
+ }
202
+ const hashedPassword = await bcrypt.hash(password, 10);
203
+ const user = await User.create({ name, email, password: hashedPassword });
204
+ <% } else { %>
153
205
  const user = await User.create({ name, email });
206
+ <% } %>
207
+
154
208
  <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
155
209
  await cacheService.del('users:all');
156
210
  <%_ } -%>
157
211
  <%_ if (communication === 'Kafka') { -%>
158
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
- const userId = (user as any).id || (user as any)._id;
212
+ const rawUserForKafka = user as unknown as { id?: string | number; _id?: string | number; email: string };
213
+ const userId = rawUserForKafka.id || rawUserForKafka._id;
160
214
  await kafkaService.sendMessage('user-topic', JSON.stringify({
161
215
  action: KAFKA_ACTIONS.USER_CREATED,
162
- payload: { id: userId, email: user.email }
216
+ payload: { id: userId, email: rawUserForKafka.email }
163
217
  }), userId?.toString() || '');
164
218
  <%_ } -%>
165
- res.status(HTTP_STATUS.CREATED).json(user);
219
+ const rawUser = user as unknown as { toJSON?: () => Record<string, unknown> };
220
+ const userJson = typeof rawUser.toJSON === 'function' ? rawUser.toJSON() : (user as unknown as Record<string, unknown>);
221
+ const { password: _, ...userWithoutPassword } = userJson;
222
+ res.status(HTTP_STATUS.CREATED).json(userWithoutPassword);
166
223
  } catch (error) {
167
224
  logger.error('Error creating user:', error);
168
225
  next(error);
@@ -1,30 +1,43 @@
1
- import { gqlContext } from '@/graphql/context';
2
1
  import { Request } from 'express';
3
- import { resolvers } from '@/graphql/resolvers';
4
- import { typeDefs } from '@/graphql/typeDefs';
2
+ <% if (auth.includes('JWT')) { %>import { JwtService } from '@/services/jwtService';
3
+ jest.mock('@/services/jwtService');<% } %>
4
+ import { gqlContext } from '@/graphql/context';
5
5
 
6
6
  describe('GraphQL Context', () => {
7
- it('should exercise GraphQL index entry points', () => {
8
- expect(resolvers).toBeDefined();
9
- expect(typeDefs).toBeDefined();
10
- });
11
- it('should return context with token when authorization header is present', async () => {
12
- const mockRequest = {
13
- headers: {
14
- authorization: 'Bearer token123',
15
- },
16
- } as Request;
7
+ afterEach(() => {
8
+ jest.clearAllMocks();
9
+ });
10
+
11
+ it('should return context with user when authorization header is present and valid', async () => {
12
+ <% if (auth.includes('JWT')) { %>
13
+ const mockUser = { id: '1', email: 'test@test.com' };
14
+ (JwtService.verifyToken as jest.Mock).mockReturnValue(mockUser);
15
+ const mockRequest = {
16
+ headers: {
17
+ authorization: 'Bearer valid-token',
18
+ },
19
+ } as Request;
17
20
 
18
- const context = await gqlContext({ req: mockRequest });
19
- expect(context).toEqual({ token: 'Bearer token123' });
20
- });
21
+ const context = await gqlContext({ req: mockRequest });
22
+ expect(context.user).toEqual(mockUser);
23
+ expect(JwtService.verifyToken).toHaveBeenCalledWith('valid-token');
24
+ <% } else { %>
25
+ const mockRequest = {
26
+ headers: {
27
+ authorization: 'Bearer token123',
28
+ },
29
+ } as Request;
30
+ const context = await gqlContext({ req: mockRequest });
31
+ expect(context.user).toBeUndefined();
32
+ <% } %>
33
+ });
21
34
 
22
- it('should return context with empty token when authorization header is missing', async () => {
23
- const mockRequest = {
24
- headers: {},
25
- } as Request;
35
+ it('should return empty context when authorization header is missing', async () => {
36
+ const mockRequest = {
37
+ headers: {},
38
+ } as Request;
26
39
 
27
- const context = await gqlContext({ req: mockRequest });
28
- expect(context).toEqual({ token: '' });
29
- });
40
+ const context = await gqlContext({ req: mockRequest });
41
+ expect(context.user).toBeUndefined();
42
+ });
30
43
  });
@@ -1,12 +1,21 @@
1
1
  import { Request } from 'express';
2
+ <% if (auth.includes('JWT')) { %>import { JwtService } from '../services/jwtService';<% } %>
2
3
 
3
4
  export interface MyContext {
4
- token?: string;
5
- // user?: User;
5
+ token?: string; // Kept for backward compatibility
6
+ user?: unknown;
6
7
  }
7
8
 
8
- export const gqlContext = async ({ req }: { req: Request }): Promise<MyContext> => {
9
- // Setup authorization or context here
10
- const token = req.headers.authorization || '';
11
- return { token };
9
+ export const gqlContext = async ({ req: <% if (auth.includes('JWT')) { %>req<% } else { %>_req<% } %> }: { req: Request }): Promise<MyContext> => {
10
+ const context: MyContext = {};
11
+ <% if (auth.includes('JWT')) { %>
12
+ const authHeader = req.headers.authorization || '';
13
+ if (authHeader.startsWith('Bearer ')) {
14
+ const token = authHeader.split(' ')[1];
15
+ const user = JwtService.verifyToken(token);
16
+ if (user) {
17
+ context.user = user;
18
+ }
19
+ }<% } %>
20
+ return context;
12
21
  };
@@ -15,40 +15,62 @@ jest.mock('@/controllers/userController', () => {
15
15
  });
16
16
 
17
17
  describe('User Resolvers', () => {
18
+ const mockContext = {
19
+ <% if (auth.includes('JWT')) { %>user: { id: 'admin', email: 'admin@test.com' }<% } %>
20
+ };
21
+
18
22
  afterEach(() => {
19
23
  jest.clearAllMocks();
20
24
  });
21
25
 
22
26
  describe('Query.getAllUsers', () => {
23
- it('should return all users', async () => {
24
- const result = await userResolvers.Query.getAllUsers();
27
+ it('should return all users when authorized', async () => {
28
+ const result = await userResolvers.Query.getAllUsers(null, null, mockContext);
25
29
  expect(result).toEqual([{ id: '1', name: 'John Doe', email: 'john@example.com' }]);
26
30
  expect(mockGetUsers).toHaveBeenCalledTimes(1);
27
31
  });
32
+
33
+ <% if (auth.includes('JWT')) { %>
34
+ it('should throw error when unauthorized', async () => {
35
+ await expect(userResolvers.Query.getAllUsers(null, null, {})).rejects.toThrow('Unauthorized');
36
+ });
37
+ <% } %>
28
38
  });
29
39
 
30
40
  describe('Mutation.createUser', () => {
31
- it('should create and return a new user', async () => {
32
- const result = await userResolvers.Mutation.createUser(null, { name: 'Jane', email: 'jane@example.com' });
41
+ it('should create and return a new user (Public)', async () => {
42
+ const payload = { name: 'Jane', email: 'jane@example.com'<% if (auth.some(a => a !=='None')) { %>, password: 'password123'<% } %> };
43
+ const result = await userResolvers.Mutation.createUser(null, payload);
33
44
  expect(result).toEqual({ id: '1', name: 'Jane', email: 'jane@example.com' });
34
- expect(mockCreateUser).toHaveBeenCalledWith({ name: 'Jane', email: 'jane@example.com' });
35
- expect(mockCreateUser).toHaveBeenCalledTimes(1);
45
+ expect(mockCreateUser).toHaveBeenCalledWith(payload);
36
46
  });
37
47
  });
38
48
 
39
49
  describe('Mutation.updateUser', () => {
40
- it('should update and return the user', async () => {
50
+ it('should update and return the user when authorized', async () => {
41
51
  const payload = { name: 'Updated' };
42
- const result = await userResolvers.Mutation.updateUser(null, { id: '1', ...payload });
52
+ const result = await userResolvers.Mutation.updateUser(null, { id: '1', ...payload }, mockContext);
43
53
  expect(result).toMatchObject(payload);
44
54
  });
55
+
56
+ <% if (auth.includes('JWT')) { %>
57
+ it('should throw error when unauthorized', async () => {
58
+ await expect(userResolvers.Mutation.updateUser(null, { id: '1', name: 'N' }, {})).rejects.toThrow('Unauthorized');
59
+ });
60
+ <% } %>
45
61
  });
46
62
 
47
63
  describe('Mutation.deleteUser', () => {
48
- it('should delete and return true', async () => {
49
- const result = await userResolvers.Mutation.deleteUser(null, { id: '1' });
64
+ it('should delete and return true when authorized', async () => {
65
+ const result = await userResolvers.Mutation.deleteUser(null, { id: '1' }, mockContext);
50
66
  expect(result).toBe(true);
51
67
  });
68
+
69
+ <% if (auth.includes('JWT')) { %>
70
+ it('should throw error when unauthorized', async () => {
71
+ await expect(userResolvers.Mutation.deleteUser(null, { id: '1' }, {})).rejects.toThrow('Unauthorized');
72
+ });
73
+ <% } %>
52
74
  });
53
75
  <%_ if (database === 'MongoDB') { -%>
54
76
  describe('User.id', () => {
@@ -1,24 +1,34 @@
1
1
  import { UserController } from '@/controllers/userController';
2
+ import { MyContext } from '@/graphql/context';
2
3
 
3
4
  const userController = new UserController();
4
5
 
5
6
  export const userResolvers = {
6
7
  Query: {
7
- getAllUsers: async () => {
8
+ getAllUsers: async (_: unknown, __: unknown, <% if (auth.includes('JWT')) { %>context<% } else { %>_context<% } %>: MyContext) => {
9
+ <%_ if (auth.includes('JWT')) { -%>
10
+ if (!context.user) throw new Error('Unauthorized');
11
+ <%_ } -%>
8
12
  const users = await userController.getUsers();
9
13
  return users;
10
14
  }
11
15
  },
12
16
  Mutation: {
13
- createUser: async (_: unknown, { name, email }: { name: string, email: string }) => {
14
- const user = await userController.createUser({ name, email });
17
+ createUser: async (_: unknown, { name, email<% if (auth.some(a => a !=='None')) { %>, password<% } %> }: { name: string, email: string<% if (auth.some(a => a !=='None')) { %>, password: string<% } %> }) => {
18
+ const user = await userController.createUser({ name, email<% if (auth.some(a => a !=='None')) { %>, password<% } %> });
15
19
  return user;
16
20
  },
17
- updateUser: async (_: unknown, { id, name, email }: { id: string, name?: string, email?: string }) => {
21
+ updateUser: async (_: unknown, { id, name, email }: { id: string, name?: string, email?: string }, <% if (auth.includes('JWT')) { %>context<% } else { %>_context<% } %>: MyContext) => {
22
+ <%_ if (auth.includes('JWT')) { -%>
23
+ if (!context.user) throw new Error('Unauthorized');
24
+ <%_ } -%>
18
25
  const user = await userController.updateUser(id, { name, email });
19
26
  return user;
20
27
  },
21
- deleteUser: async (_: unknown, { id }: { id: string }) => {
28
+ deleteUser: async (_: unknown, { id }: { id: string }, <% if (auth.includes('JWT')) { %>context<% } else { %>_context<% } %>: MyContext) => {
29
+ <%_ if (auth.includes('JWT')) { -%>
30
+ if (!context.user) throw new Error('Unauthorized');
31
+ <%_ } -%>
22
32
  const result = await userController.deleteUser(id);
23
33
  return result;
24
34
  }
@@ -10,7 +10,7 @@ export const userTypes = `#graphql
10
10
  }
11
11
 
12
12
  type Mutation {
13
- createUser(name: String!, email: String!): User
13
+ createUser(name: String!, email: String!<%_ if (auth.some(a => a !=='None')) { _%>, password: String!<%_ } _%>): User
14
14
  updateUser(id: ID!, name: String, email: String): User
15
15
  deleteUser(id: ID!): Boolean
16
16
  }