nodejs-structure-cli 1.0.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 (243) hide show
  1. package/README.md +32 -0
  2. package/bin/index.js +143 -0
  3. package/lib/generator.js +145 -0
  4. package/lib/modules/app-setup.js +479 -0
  5. package/lib/modules/caching-setup.js +76 -0
  6. package/lib/modules/config-files.js +151 -0
  7. package/lib/modules/database-setup.js +116 -0
  8. package/lib/modules/kafka-setup.js +249 -0
  9. package/lib/modules/project-setup.js +32 -0
  10. package/lib/prompts.js +128 -0
  11. package/package.json +66 -0
  12. package/templates/clean-architecture/js/src/domain/models/User.js.ejs +11 -0
  13. package/templates/clean-architecture/js/src/errors/ApiError.js +14 -0
  14. package/templates/clean-architecture/js/src/errors/BadRequestError.js +11 -0
  15. package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +22 -0
  16. package/templates/clean-architecture/js/src/errors/NotFoundError.js +11 -0
  17. package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +22 -0
  18. package/templates/clean-architecture/js/src/index.js.ejs +56 -0
  19. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +47 -0
  20. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +36 -0
  21. package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -0
  22. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +88 -0
  23. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +142 -0
  24. package/templates/clean-architecture/js/src/infrastructure/webserver/middleware/errorMiddleware.js +30 -0
  25. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +93 -0
  26. package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.js.ejs +6 -0
  27. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +190 -0
  28. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +234 -0
  29. package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -0
  30. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -0
  31. package/templates/clean-architecture/js/src/interfaces/graphql/index.js.ejs +5 -0
  32. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/index.js.ejs +6 -0
  33. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +27 -0
  34. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +66 -0
  35. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/index.js.ejs +6 -0
  36. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +19 -0
  37. package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +17 -0
  38. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -0
  39. package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +14 -0
  40. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -0
  41. package/templates/clean-architecture/js/src/usecases/DeleteUser.js +11 -0
  42. package/templates/clean-architecture/js/src/usecases/DeleteUser.spec.js.ejs +47 -0
  43. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +12 -0
  44. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -0
  45. package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +11 -0
  46. package/templates/clean-architecture/js/src/usecases/UpdateUser.spec.js.ejs +48 -0
  47. package/templates/clean-architecture/js/src/utils/errorMessages.js +14 -0
  48. package/templates/clean-architecture/js/src/utils/httpCodes.js +9 -0
  49. package/templates/clean-architecture/ts/src/config/env.ts.ejs +46 -0
  50. package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +6 -0
  51. package/templates/clean-architecture/ts/src/domain/user.ts.ejs +9 -0
  52. package/templates/clean-architecture/ts/src/errors/ApiError.ts +15 -0
  53. package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +22 -0
  54. package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +9 -0
  55. package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +22 -0
  56. package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +9 -0
  57. package/templates/clean-architecture/ts/src/index.ts.ejs +144 -0
  58. package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +63 -0
  59. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +36 -0
  60. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +175 -0
  61. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +125 -0
  62. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +331 -0
  63. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +208 -0
  64. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -0
  65. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +17 -0
  66. package/templates/clean-architecture/ts/src/interfaces/graphql/index.ts.ejs +3 -0
  67. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/index.ts.ejs +4 -0
  68. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -0
  69. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +29 -0
  70. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/index.ts.ejs +4 -0
  71. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +17 -0
  72. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -0
  73. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +18 -0
  74. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -0
  75. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +11 -0
  76. package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +47 -0
  77. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +9 -0
  78. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -0
  79. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +10 -0
  80. package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +48 -0
  81. package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +10 -0
  82. package/templates/clean-architecture/ts/src/utils/errorMessages.ts +12 -0
  83. package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +27 -0
  84. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +7 -0
  85. package/templates/common/.cursorrules.ejs +60 -0
  86. package/templates/common/.dockerignore +12 -0
  87. package/templates/common/.env.example.ejs +60 -0
  88. package/templates/common/.gitattributes +46 -0
  89. package/templates/common/.gitlab-ci.yml.ejs +86 -0
  90. package/templates/common/.lintstagedrc +6 -0
  91. package/templates/common/.prettierrc +7 -0
  92. package/templates/common/.snyk.ejs +45 -0
  93. package/templates/common/Dockerfile +73 -0
  94. package/templates/common/Jenkinsfile.ejs +87 -0
  95. package/templates/common/README.md.ejs +148 -0
  96. package/templates/common/_github/workflows/ci.yml.ejs +46 -0
  97. package/templates/common/_github/workflows/security.yml.ejs +36 -0
  98. package/templates/common/_gitignore +5 -0
  99. package/templates/common/_husky/pre-commit +4 -0
  100. package/templates/common/caching/clean/js/CreateUser.js.ejs +29 -0
  101. package/templates/common/caching/clean/js/DeleteUser.js.ejs +27 -0
  102. package/templates/common/caching/clean/js/GetAllUsers.js.ejs +37 -0
  103. package/templates/common/caching/clean/js/UpdateUser.js.ejs +27 -0
  104. package/templates/common/caching/clean/ts/createUser.ts.ejs +27 -0
  105. package/templates/common/caching/clean/ts/deleteUser.ts.ejs +24 -0
  106. package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +34 -0
  107. package/templates/common/caching/clean/ts/updateUser.ts.ejs +25 -0
  108. package/templates/common/caching/js/memoryCache.js.ejs +60 -0
  109. package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -0
  110. package/templates/common/caching/js/redisClient.js.ejs +75 -0
  111. package/templates/common/caching/js/redisClient.spec.js.ejs +147 -0
  112. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -0
  113. package/templates/common/caching/ts/memoryCache.ts.ejs +73 -0
  114. package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -0
  115. package/templates/common/caching/ts/redisClient.ts.ejs +89 -0
  116. package/templates/common/database/js/database.js.ejs +19 -0
  117. package/templates/common/database/js/database.spec.js.ejs +56 -0
  118. package/templates/common/database/js/models/User.js.ejs +91 -0
  119. package/templates/common/database/js/models/User.js.mongoose.ejs +35 -0
  120. package/templates/common/database/js/models/User.spec.js.ejs +94 -0
  121. package/templates/common/database/js/mongoose.js.ejs +33 -0
  122. package/templates/common/database/js/mongoose.spec.js.ejs +43 -0
  123. package/templates/common/database/ts/database.spec.ts.ejs +56 -0
  124. package/templates/common/database/ts/database.ts.ejs +21 -0
  125. package/templates/common/database/ts/models/User.spec.ts.ejs +100 -0
  126. package/templates/common/database/ts/models/User.ts.ejs +102 -0
  127. package/templates/common/database/ts/models/User.ts.mongoose.ejs +34 -0
  128. package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -0
  129. package/templates/common/database/ts/mongoose.ts.ejs +28 -0
  130. package/templates/common/docker-compose.yml.ejs +159 -0
  131. package/templates/common/ecosystem.config.js.ejs +40 -0
  132. package/templates/common/eslint.config.mjs.ejs +77 -0
  133. package/templates/common/health/js/healthRoute.js.ejs +50 -0
  134. package/templates/common/health/js/healthRoute.spec.js.ejs +70 -0
  135. package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -0
  136. package/templates/common/health/ts/healthRoute.ts.ejs +49 -0
  137. package/templates/common/jest.config.js.ejs +32 -0
  138. package/templates/common/jest.e2e.config.js.ejs +8 -0
  139. package/templates/common/kafka/js/config/kafka.js +9 -0
  140. package/templates/common/kafka/js/config/kafka.spec.js.ejs +27 -0
  141. package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +30 -0
  142. package/templates/common/kafka/js/messaging/baseConsumer.spec.js.ejs +58 -0
  143. package/templates/common/kafka/js/messaging/userEventSchema.js.ejs +12 -0
  144. package/templates/common/kafka/js/messaging/userEventSchema.spec.js.ejs +27 -0
  145. package/templates/common/kafka/js/messaging/welcomeEmailConsumer.js.ejs +44 -0
  146. package/templates/common/kafka/js/messaging/welcomeEmailConsumer.spec.js.ejs +86 -0
  147. package/templates/common/kafka/js/services/kafkaService.js.ejs +93 -0
  148. package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +106 -0
  149. package/templates/common/kafka/js/utils/kafkaEvents.js.ejs +7 -0
  150. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +27 -0
  151. package/templates/common/kafka/ts/config/kafka.ts +7 -0
  152. package/templates/common/kafka/ts/messaging/baseConsumer.spec.ts.ejs +50 -0
  153. package/templates/common/kafka/ts/messaging/baseConsumer.ts.ejs +27 -0
  154. package/templates/common/kafka/ts/messaging/userEventSchema.spec.ts.ejs +51 -0
  155. package/templates/common/kafka/ts/messaging/userEventSchema.ts.ejs +12 -0
  156. package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.spec.ts.ejs +86 -0
  157. package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.ts.ejs +38 -0
  158. package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +81 -0
  159. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +95 -0
  160. package/templates/common/kafka/ts/utils/kafkaEvents.ts.ejs +5 -0
  161. package/templates/common/migrate-mongo-config.js.ejs +31 -0
  162. package/templates/common/migrations/init.js.ejs +23 -0
  163. package/templates/common/package.json.ejs +137 -0
  164. package/templates/common/prompts/add-feature.md.ejs +26 -0
  165. package/templates/common/prompts/project-context.md.ejs +43 -0
  166. package/templates/common/prompts/troubleshoot.md.ejs +28 -0
  167. package/templates/common/public/css/style.css +147 -0
  168. package/templates/common/scripts/run-e2e.js.ejs +63 -0
  169. package/templates/common/shutdown/js/gracefulShutdown.js.ejs +65 -0
  170. package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +149 -0
  171. package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +179 -0
  172. package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +59 -0
  173. package/templates/common/sonar-project.properties.ejs +27 -0
  174. package/templates/common/src/config/auth.js.ejs +19 -0
  175. package/templates/common/src/config/auth.ts.ejs +19 -0
  176. package/templates/common/src/controllers/authController.js.ejs +101 -0
  177. package/templates/common/src/controllers/authController.ts.ejs +101 -0
  178. package/templates/common/src/middleware/auth.js.ejs +20 -0
  179. package/templates/common/src/middleware/auth.ts.ejs +25 -0
  180. package/templates/common/src/middleware/upload.js.ejs +31 -0
  181. package/templates/common/src/middleware/upload.ts.ejs +32 -0
  182. package/templates/common/src/routes/authRoutes.js.ejs +20 -0
  183. package/templates/common/src/routes/authRoutes.ts.ejs +20 -0
  184. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +120 -0
  185. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +120 -0
  186. package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -0
  187. package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -0
  188. package/templates/common/swagger.yml.ejs +118 -0
  189. package/templates/common/tsconfig.json +23 -0
  190. package/templates/common/views/ejs/index.ejs +55 -0
  191. package/templates/common/views/pug/index.pug +40 -0
  192. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +10 -0
  193. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +10 -0
  194. package/templates/mvc/js/src/config/env.js.ejs +46 -0
  195. package/templates/mvc/js/src/config/swagger.js.ejs +6 -0
  196. package/templates/mvc/js/src/controllers/userController.js.ejs +288 -0
  197. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +481 -0
  198. package/templates/mvc/js/src/errors/ApiError.js +14 -0
  199. package/templates/mvc/js/src/errors/BadRequestError.js +11 -0
  200. package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +22 -0
  201. package/templates/mvc/js/src/errors/NotFoundError.js +11 -0
  202. package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +22 -0
  203. package/templates/mvc/js/src/graphql/context.js.ejs +7 -0
  204. package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -0
  205. package/templates/mvc/js/src/graphql/index.js.ejs +5 -0
  206. package/templates/mvc/js/src/graphql/resolvers/index.js.ejs +6 -0
  207. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +25 -0
  208. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +64 -0
  209. package/templates/mvc/js/src/graphql/typeDefs/index.js.ejs +6 -0
  210. package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +19 -0
  211. package/templates/mvc/js/src/index.js.ejs +141 -0
  212. package/templates/mvc/js/src/routes/api.js.ejs +15 -0
  213. package/templates/mvc/js/src/routes/api.spec.js.ejs +41 -0
  214. package/templates/mvc/js/src/utils/errorMessages.js +14 -0
  215. package/templates/mvc/js/src/utils/errorMiddleware.js +29 -0
  216. package/templates/mvc/js/src/utils/httpCodes.js +9 -0
  217. package/templates/mvc/js/src/utils/logger.js +40 -0
  218. package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -0
  219. package/templates/mvc/ts/src/config/env.ts.ejs +45 -0
  220. package/templates/mvc/ts/src/config/swagger.ts.ejs +6 -0
  221. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +481 -0
  222. package/templates/mvc/ts/src/controllers/userController.ts.ejs +292 -0
  223. package/templates/mvc/ts/src/errors/ApiError.ts +15 -0
  224. package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +22 -0
  225. package/templates/mvc/ts/src/errors/BadRequestError.ts +9 -0
  226. package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +27 -0
  227. package/templates/mvc/ts/src/errors/NotFoundError.ts +9 -0
  228. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -0
  229. package/templates/mvc/ts/src/graphql/context.ts.ejs +12 -0
  230. package/templates/mvc/ts/src/graphql/index.ts.ejs +3 -0
  231. package/templates/mvc/ts/src/graphql/resolvers/index.ts.ejs +4 -0
  232. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -0
  233. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +29 -0
  234. package/templates/mvc/ts/src/graphql/typeDefs/index.ts.ejs +4 -0
  235. package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +17 -0
  236. package/templates/mvc/ts/src/index.ts.ejs +157 -0
  237. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +59 -0
  238. package/templates/mvc/ts/src/routes/api.ts.ejs +17 -0
  239. package/templates/mvc/ts/src/utils/errorMessages.ts +12 -0
  240. package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +27 -0
  241. package/templates/mvc/ts/src/utils/httpCodes.ts +7 -0
  242. package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +63 -0
  243. package/templates/mvc/ts/src/utils/logger.ts +36 -0
package/lib/prompts.js ADDED
@@ -0,0 +1,128 @@
1
+ import inquirer from 'inquirer';
2
+
3
+ const validateName = (name) => {
4
+ return /^[a-zA-Z0-9-_]+$/.test(name) ? true : 'Project name may only include letters, numbers, underscores and dashes.';
5
+ };
6
+
7
+ export const getProjectDetails = async (options = {}) => {
8
+ const questions = [
9
+ {
10
+ type: 'input',
11
+ name: 'projectName',
12
+ message: 'Project name:',
13
+ default: 'nodejs-service',
14
+ validate: validateName,
15
+ when: !options.projectName
16
+ },
17
+ {
18
+ type: 'select',
19
+ name: 'language',
20
+ message: 'Select Language:',
21
+ choices: ['JavaScript', 'TypeScript'],
22
+ default: 'TypeScript',
23
+ when: !options.language
24
+ },
25
+ {
26
+ type: 'select',
27
+ name: 'architecture',
28
+ message: 'Select Architecture:',
29
+ choices: ['MVC', 'Clean Architecture'],
30
+ default: 'MVC',
31
+ when: !options.architecture
32
+ },
33
+ {
34
+ type: 'select',
35
+ name: 'viewEngine',
36
+ message: 'Select View Engine:',
37
+ choices: ['None', 'EJS', 'Pug'],
38
+ when: (answers) => (options.architecture || answers.architecture) === 'MVC' && !options.viewEngine,
39
+ default: 'None'
40
+ },
41
+ {
42
+ type: 'select',
43
+ name: 'database',
44
+ message: 'Select Database:',
45
+ choices: ['None', 'MySQL', 'PostgreSQL', 'MongoDB'],
46
+ default: 'None',
47
+ when: !options.database
48
+ },
49
+ {
50
+ type: 'input',
51
+ name: 'dbName',
52
+ message: 'Database Name:',
53
+ default: 'demo',
54
+ validate: validateName,
55
+ when: (answers) => !options.dbName && (options.database || answers.database) !== 'None'
56
+ },
57
+ {
58
+ type: 'select',
59
+ name: 'communication',
60
+ message: 'Microservices Communication:',
61
+ choices: ['REST APIs', 'GraphQL', 'Kafka'],
62
+ default: 'REST APIs',
63
+ when: !options.communication
64
+ },
65
+ {
66
+ type: 'select',
67
+ name: 'caching',
68
+ message: 'Caching Layer:',
69
+ choices: ['None', 'Redis', 'Memory Cache'],
70
+ default: 'None',
71
+ when: (answers) => !options.caching && (options.database || answers.database) !== 'None'
72
+ },
73
+ {
74
+ type: 'select',
75
+ name: 'ciProvider',
76
+ message: 'Select CI/CD Provider:',
77
+ choices: ['None', 'GitHub Actions', 'Jenkins', 'GitLab CI'],
78
+ default: 'None',
79
+ when: !options.ciProvider
80
+ },
81
+ {
82
+ type: 'select',
83
+ name: 'includeSecurity',
84
+ message: 'Include Enterprise Security Hardening (Big Tech Standard: Snyk, SonarQube)?',
85
+ choices: ['No', 'Yes'],
86
+ default: "No",
87
+ when: (answers) => options.includeSecurity === undefined && (options.ciProvider || answers.ciProvider) !== 'None'
88
+ },
89
+ {
90
+ type: 'select',
91
+ name: 'auth',
92
+ message: 'Select Authentication:',
93
+ choices: ['None', 'Better-Auth', 'JWT', 'OAuth'],
94
+ default: 'None',
95
+ when: !options.auth
96
+ },
97
+ {
98
+ type: 'select',
99
+ name: 'googleLogin',
100
+ message: 'Include Social Login?',
101
+ choices: ['None', 'Google Login'],
102
+ default: 'None',
103
+ when: (answers) => (options.auth || answers.auth) === 'OAuth' && !options.googleLogin
104
+ },
105
+ {
106
+ type: 'select',
107
+ name: 'includeMulter',
108
+ message: 'Add image upload capability (Multer)?',
109
+ choices: ['No', 'Yes'],
110
+ default: 'No',
111
+ when: options.includeMulter === undefined
112
+ }
113
+ ];
114
+
115
+ const answers = await inquirer.prompt(questions);
116
+ const result = { ...options, ...answers };
117
+
118
+ // Normalize includeSecurity to boolean if it's a string from the select prompt
119
+ if (typeof result.includeSecurity === 'string') {
120
+ result.includeSecurity = result.includeSecurity === 'Yes';
121
+ }
122
+
123
+ if (typeof result.includeMulter === 'string') {
124
+ result.includeMulter = result.includeMulter === 'Yes';
125
+ }
126
+
127
+ return result;
128
+ };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "nodejs-structure-cli",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "The ultimate nodejs quickstart structure CLI to scaffold Node.js microservices with MVC or Clean Architecture",
6
+ "main": "bin/index.js",
7
+ "bin": {
8
+ "nodejs-structure-cli": "./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
+ "test:validate-command": "node scripts/validate-command.js",
16
+ "test:verify:mongo": "node scripts/verify-migration.js",
17
+ "docs:dev": "vitepress dev docs",
18
+ "docs:build": "vitepress build docs",
19
+ "docs:preview": "vitepress preview docs",
20
+ "security:check": "npm audit && npm run snyk:test",
21
+ "snyk:test": "snyk test"
22
+ },
23
+ "keywords": [
24
+ "nodejs",
25
+ "node",
26
+ "quickstart",
27
+ "structure",
28
+ "cli",
29
+ "scaffold",
30
+ "mvc",
31
+ "clean-architecture",
32
+ "microservices",
33
+ "backend",
34
+ "generator",
35
+ "boilerplate"
36
+ ],
37
+ "author": "jaysoni <sonijay2709@gmail.com>",
38
+ "contributors": [
39
+ {
40
+ "name": "jaysoni",
41
+ "url": "https://github.com/Jaysoni2709"
42
+ }
43
+ ],
44
+ "license": "ISC",
45
+ "dependencies": {
46
+ "chalk": "^5.4.1",
47
+ "commander": "^14.0.3",
48
+ "ejs": "^5.0.1",
49
+ "fs-extra": "^11.3.0",
50
+ "inquirer": "^13.3.2"
51
+ },
52
+ "overrides": {
53
+ "esbuild": "^0.25.0"
54
+ },
55
+ "devDependencies": {
56
+ "snyk": "^1.1303.2",
57
+ "vitepress": "^1.0.0-rc.45"
58
+ },
59
+ "files": [
60
+ "bin",
61
+ "lib",
62
+ "templates",
63
+ "README.md",
64
+ "CHANGELOG.md"
65
+ ]
66
+ }
@@ -0,0 +1,11 @@
1
+ class User {
2
+ constructor(id, name, email<% if (auth && auth !== 'None') { %>, password<% } %><% if (includeMulter) { %>, imageUrl<% } %>) {
3
+ this.id = id;
4
+ this.name = name;
5
+ this.email = email;
6
+ <% if (auth && auth !== 'None') { %>this.password = password;<% } %>
7
+ <% if (includeMulter) { %>this.imageUrl = imageUrl;<% } %>
8
+ }
9
+ }
10
+
11
+ module.exports = User;
@@ -0,0 +1,14 @@
1
+ class ApiError extends Error {
2
+ constructor(statusCode, message, isOperational = true, stack = '') {
3
+ super(message);
4
+ this.statusCode = statusCode;
5
+ this.isOperational = isOperational;
6
+ if (stack) {
7
+ this.stack = stack;
8
+ } else {
9
+ Error.captureStackTrace(this, this.constructor);
10
+ }
11
+ }
12
+ }
13
+
14
+ module.exports = { ApiError };
@@ -0,0 +1,11 @@
1
+ const { ApiError } = require('./ApiError');
2
+ const HTTP_STATUS = require('../utils/httpCodes');
3
+ const ERROR_MESSAGES = require('../utils/errorMessages');
4
+
5
+ class BadRequestError extends ApiError {
6
+ constructor(message = ERROR_MESSAGES.BAD_REQUEST) {
7
+ super(HTTP_STATUS.BAD_REQUEST, message);
8
+ }
9
+ }
10
+
11
+ module.exports = { BadRequestError };
@@ -0,0 +1,22 @@
1
+ const { BadRequestError } = require('@/errors/BadRequestError');
2
+ const { ApiError } = require('@/errors/ApiError');
3
+ const HTTP_STATUS = require('@/utils/httpCodes');
4
+ const ERROR_MESSAGES = require('@/utils/errorMessages');
5
+
6
+ describe('BadRequestError', () => {
7
+ it('should extend ApiError', () => {
8
+ const error = new BadRequestError();
9
+ expect(error).toBeInstanceOf(ApiError);
10
+ });
11
+
12
+ it('should have default message "Bad Request"', () => {
13
+ const error = new BadRequestError();
14
+ expect(error.message).toBe(ERROR_MESSAGES.BAD_REQUEST);
15
+ expect(error.statusCode).toBe(HTTP_STATUS.BAD_REQUEST);
16
+ });
17
+
18
+ it('should accept a custom message', () => {
19
+ const error = new BadRequestError('Custom bad request');
20
+ expect(error.message).toBe('Custom bad request');
21
+ });
22
+ });
@@ -0,0 +1,11 @@
1
+ const { ApiError } = require('./ApiError');
2
+ const HTTP_STATUS = require('../utils/httpCodes');
3
+ const ERROR_MESSAGES = require('../utils/errorMessages');
4
+
5
+ class NotFoundError extends ApiError {
6
+ constructor(message = ERROR_MESSAGES.RESOURCE_NOT_FOUND) {
7
+ super(HTTP_STATUS.NOT_FOUND, message);
8
+ }
9
+ }
10
+
11
+ module.exports = { NotFoundError };
@@ -0,0 +1,22 @@
1
+ const { NotFoundError } = require('@/errors/NotFoundError');
2
+ const { ApiError } = require('@/errors/ApiError');
3
+ const HTTP_STATUS = require('@/utils/httpCodes');
4
+ const ERROR_MESSAGES = require('@/utils/errorMessages');
5
+
6
+ describe('NotFoundError', () => {
7
+ it('should extend ApiError', () => {
8
+ const error = new NotFoundError();
9
+ expect(error).toBeInstanceOf(ApiError);
10
+ });
11
+
12
+ it('should have default message "Resource not found"', () => {
13
+ const error = new NotFoundError();
14
+ expect(error.message).toBe(ERROR_MESSAGES.RESOURCE_NOT_FOUND);
15
+ expect(error.statusCode).toBe(HTTP_STATUS.NOT_FOUND);
16
+ });
17
+
18
+ it('should accept a custom message', () => {
19
+ const error = new NotFoundError(ERROR_MESSAGES.USER_NOT_FOUND);
20
+ expect(error.message).toBe(ERROR_MESSAGES.USER_NOT_FOUND);
21
+ });
22
+ });
@@ -0,0 +1,56 @@
1
+ const startServer = require('./infrastructure/webserver/server');
2
+ const logger = require('./infrastructure/log/logger');
3
+ <% if (communication === 'Kafka') { -%>
4
+ const { connectKafka } = require('./infrastructure/messaging/kafkaClient');
5
+ <% } -%>
6
+ <%_ if (database !== 'None') { -%>
7
+ // Database Sync
8
+ <%_ if (database !== 'None') { -%>
9
+ <%_ if (database === 'MongoDB') { -%>
10
+ const connectDB = require('./infrastructure/database/database');
11
+ <%_ } else { -%>
12
+ const sequelize = require('./infrastructure/database/database');
13
+ <%_ } -%>
14
+ <%_ } -%>
15
+
16
+ const syncDatabase = async () => {
17
+ let retries = 30;
18
+ while (retries) {
19
+ try {
20
+ <%_ if (database === 'MongoDB') { -%>
21
+ await connectDB();
22
+ <%_ } else { -%>
23
+ await sequelize.sync();
24
+ <%_ } -%>
25
+ logger.info('Database synced');
26
+ // Start the web server after DB sync
27
+ startServer();
28
+ <%_ if (communication === 'Kafka') { -%>
29
+ // Connect Kafka
30
+ connectKafka().then(async () => {
31
+ logger.info('Kafka connected');
32
+ }).catch(err => {
33
+ logger.error('Failed to connect to Kafka:', err);
34
+ });
35
+ <%_ } -%>
36
+ break;
37
+ } catch (error) {
38
+ logger.error('Error syncing database:', error);
39
+ retries -= 1;
40
+ logger.info(`Retries left: ${retries}`);
41
+ await new Promise(res => setTimeout(res, 5000));
42
+ }
43
+ }
44
+ };
45
+ syncDatabase();
46
+ <%_ } else { -%>
47
+ startServer();
48
+ <%_ if (communication === 'Kafka') { -%>
49
+ // Connect Kafka
50
+ connectKafka().then(async () => {
51
+ logger.info('Kafka connected');
52
+ }).catch(err => {
53
+ logger.error('Failed to connect to Kafka:', err);
54
+ });
55
+ <%_ } -%>
56
+ <%_ } -%>
@@ -0,0 +1,47 @@
1
+ const { z } = require('zod');
2
+ const logger = require('../log/logger');
3
+
4
+ if (process.env.NODE_ENV !== 'production') {
5
+ require('dotenv').config();
6
+ }
7
+
8
+ const envSchema = z.object({
9
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
10
+ PORT: z.string().transform(Number).default('3000'),
11
+ <%_ if (database !== 'None') { -%>
12
+ DB_HOST: z.string(),
13
+ <%_ if (database === 'MySQL') { -%>
14
+ DB_USER: z.string(),
15
+ DB_PASSWORD: z.string(),
16
+ DB_NAME: z.string(),
17
+ DB_PORT: z.string().transform(Number),
18
+ <%_ } else if (database === 'PostgreSQL') { -%>
19
+ DB_USER: z.string(),
20
+ DB_PASSWORD: z.string(),
21
+ DB_NAME: z.string(),
22
+ DB_PORT: z.string().transform(Number),
23
+ <%_ } else if (database === 'MongoDB') { -%>
24
+ DB_NAME: z.string(),
25
+ DB_PORT: z.string().transform(Number),
26
+ <%_ } -%>
27
+ <%_ } -%>
28
+ <%_ if (caching === 'Redis') { -%>
29
+ REDIS_HOST: z.string(),
30
+ REDIS_PORT: z.string().transform(Number),
31
+ REDIS_PASSWORD: z.string().optional(),
32
+ <%_ } -%>
33
+ <%_ if (communication === 'Kafka') { -%>
34
+ KAFKA_BROKER: z.string(),
35
+ <%_ } -%>
36
+ });
37
+
38
+ const _env = envSchema.safeParse(process.env);
39
+
40
+ if (!_env.success) {
41
+ logger.error('❌ Invalid environment variables:', _env.error.format());
42
+ process.exit(1);
43
+ }
44
+
45
+ const env = _env.data;
46
+
47
+ module.exports = { env };
@@ -0,0 +1,36 @@
1
+ const winston = require('winston');
2
+ require('winston-daily-rotate-file');
3
+
4
+ const logger = winston.createLogger({
5
+ level: 'info',
6
+ format: winston.format.combine(
7
+ winston.format.timestamp(),
8
+ winston.format.json()
9
+ ),
10
+ defaultMeta: { service: 'user-service' },
11
+ transports: [
12
+ new winston.transports.DailyRotateFile({
13
+ filename: 'logs/error-%DATE%.log',
14
+ datePattern: 'YYYY-MM-DD',
15
+ zippedArchive: true,
16
+ maxSize: '20m',
17
+ maxFiles: '14d',
18
+ level: 'error',
19
+ }),
20
+ new winston.transports.DailyRotateFile({
21
+ filename: 'logs/combined-%DATE%.log',
22
+ datePattern: 'YYYY-MM-DD',
23
+ zippedArchive: true,
24
+ maxSize: '20m',
25
+ maxFiles: '14d',
26
+ }),
27
+ ],
28
+ });
29
+
30
+ logger.add(new winston.transports.Console({
31
+ format: process.env.NODE_ENV !== 'production'
32
+ ? winston.format.simple()
33
+ : winston.format.json(),
34
+ }));
35
+
36
+ module.exports = logger;
@@ -0,0 +1,63 @@
1
+ jest.mock('winston-daily-rotate-file');
2
+ jest.mock('winston', () => {
3
+ const mockLogger = {
4
+ add: jest.fn(),
5
+ info: jest.fn(),
6
+ error: jest.fn(),
7
+ warn: jest.fn()
8
+ };
9
+ const format = {
10
+ combine: jest.fn(),
11
+ timestamp: jest.fn(),
12
+ json: jest.fn(),
13
+ simple: jest.fn()
14
+ };
15
+ const transports = {
16
+ Console: jest.fn(),
17
+ DailyRotateFile: jest.fn()
18
+ };
19
+ return {
20
+ format,
21
+ transports,
22
+ createLogger: jest.fn().mockReturnValue(mockLogger)
23
+ };
24
+ });
25
+
26
+ <% if (architecture === 'MVC') { -%>
27
+ const logger = require('@/utils/logger');
28
+ <% } else { -%>
29
+ const logger = require('@/infrastructure/log/logger');
30
+ <% } -%>
31
+
32
+ describe('Logger', () => {
33
+ it('should export a logger instance', () => {
34
+ expect(logger).toBeDefined();
35
+ });
36
+
37
+ it('should have info method', () => {
38
+ expect(typeof logger.info).toBe('function');
39
+ });
40
+
41
+ it('should have error method', () => {
42
+ expect(typeof logger.error).toBe('function');
43
+ });
44
+
45
+ it('should call info', () => {
46
+ logger.info('test message');
47
+ expect(logger.info).toHaveBeenCalledWith('test message');
48
+ });
49
+
50
+ it('should call error', () => {
51
+ logger.error('test error');
52
+ expect(logger.error).toHaveBeenCalledWith('test error');
53
+ });
54
+
55
+ it('should use JSON format in production environment', () => {
56
+ const winston = require('winston');
57
+ jest.resetModules();
58
+ process.env.NODE_ENV = 'production';
59
+ require('@/infrastructure/log/logger');
60
+ expect(winston.format.json).toHaveBeenCalled();
61
+ process.env.NODE_ENV = 'test';
62
+ });
63
+ });
@@ -0,0 +1,88 @@
1
+ const UserModel = require('../database/models/User');
2
+
3
+ class UserRepository {
4
+ async save(user) {
5
+ <%_ if (database === 'None') { -%>
6
+ const newUser = await UserModel.create(user);
7
+ return newUser;
8
+ <%_ } else { -%>
9
+ const userData = { name: user.name, email: user.email };
10
+ <% if (auth && auth !== 'None') { %>if (user.password) userData.password = user.password;<% } %>
11
+ <% if (includeMulter) { %>if (user.imageUrl) userData.imageUrl = user.imageUrl;<% } %>
12
+ const newUser = await UserModel.create(userData);
13
+ <%_ if (database === 'MongoDB') { -%>
14
+ return { ...user, id: newUser._id.toString() };
15
+ <%_ } else { -%>
16
+ return { ...user, id: newUser.id };
17
+ <%_ } -%>
18
+ <%_ } -%>
19
+ }
20
+
21
+ async getUsers() {
22
+ <%_ if (database === 'None') { -%>
23
+ return await UserModel.find();
24
+ <%_ } else if (database === 'MongoDB') { -%>
25
+ const users = await UserModel.find();
26
+ return users.map(user => ({
27
+ id: user._id.toString(),
28
+ name: user.name,
29
+ email: user.email,
30
+ <% if (auth && auth !== 'None') { %>password: user.password,<% } %>
31
+ <% if (includeMulter) { %>imageUrl: user.imageUrl,<% } %>
32
+ }));
33
+ <%_ } else { -%>
34
+ const users = await UserModel.findAll();
35
+ return users.map(user => ({
36
+ id: user.id,
37
+ name: user.name,
38
+ email: user.email,
39
+ <% if (auth && auth !== 'None') { %>password: user.password,<% } %>
40
+ <% if (includeMulter) { %>imageUrl: user.imageUrl,<% } %>
41
+ }));
42
+ <%_ } -%>
43
+ }
44
+
45
+ async update(id, data) {
46
+ <%_ if (database === 'MongoDB') { -%>
47
+ const user = await UserModel.findByIdAndUpdate(id, data, { new: true });
48
+ if (!user) return null;
49
+ return {
50
+ id: user._id.toString(),
51
+ name: user.name,
52
+ email: user.email,
53
+ <% if (auth && auth !== 'None') { %>password: user.password,<% } %>
54
+ <% if (includeMulter) { %>imageUrl: user.imageUrl,<% } %>
55
+ };
56
+ <%_ } else if (database === 'None') { -%>
57
+ const user = await UserModel.update(id, data);
58
+ return user;
59
+ <%_ } else { -%>
60
+ const user = await UserModel.findByPk(id);
61
+ if (!user) return null;
62
+ await user.update(data);
63
+ return {
64
+ id: user.id || 0,
65
+ name: user.name,
66
+ email: user.email,
67
+ <% if (auth && auth !== 'None') { %>password: user.password,<% } %>
68
+ <% if (includeMulter) { %>imageUrl: user.imageUrl,<% } %>
69
+ };
70
+ <%_ } -%>
71
+ }
72
+
73
+ async delete(id) {
74
+ <%_ if (database === 'MongoDB') { -%>
75
+ const result = await UserModel.findByIdAndDelete(id);
76
+ return !!result;
77
+ <%_ } else if (database === 'None') { -%>
78
+ return await UserModel.destroy(id);
79
+ <%_ } else { -%>
80
+ const user = await UserModel.findByPk(id);
81
+ if (!user) return false;
82
+ await user.destroy();
83
+ return true;
84
+ <%_ } -%>
85
+ }
86
+ }
87
+
88
+ module.exports = UserRepository;