nodejs-quickstart-structure 1.11.1 → 1.13.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 (43) hide show
  1. package/CHANGELOG.md +22 -3
  2. package/README.md +2 -1
  3. package/bin/index.js +2 -2
  4. package/docs/generatorFlow.md +9 -9
  5. package/lib/generator.js +8 -2
  6. package/lib/modules/app-setup.js +85 -33
  7. package/lib/modules/config-files.js +18 -1
  8. package/lib/modules/kafka-setup.js +4 -39
  9. package/package.json +1 -1
  10. package/templates/clean-architecture/js/src/index.js.ejs +2 -4
  11. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +47 -0
  12. package/templates/clean-architecture/js/src/infrastructure/webserver/{middlewares/error.middleware.js → middleware/errorMiddleware.js} +2 -1
  13. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +25 -11
  14. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +4 -1
  15. package/templates/clean-architecture/ts/src/config/env.ts.ejs +46 -0
  16. package/templates/clean-architecture/ts/src/index.ts.ejs +23 -22
  17. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +4 -1
  18. package/templates/clean-architecture/ts/src/utils/{error.middleware.ts.ejs → errorMiddleware.ts.ejs} +2 -1
  19. package/templates/common/.env.example.ejs +3 -1
  20. package/templates/common/README.md.ejs +30 -0
  21. package/templates/common/caching/js/redisClient.js.ejs +4 -0
  22. package/templates/common/caching/ts/redisClient.ts.ejs +4 -0
  23. package/templates/common/database/js/mongoose.js.ejs +3 -1
  24. package/templates/common/database/ts/mongoose.ts.ejs +3 -1
  25. package/templates/common/docker-compose.yml.ejs +11 -1
  26. package/templates/common/ecosystem.config.js.ejs +40 -0
  27. package/templates/common/health/js/healthRoute.js.ejs +44 -0
  28. package/templates/common/health/ts/healthRoute.ts.ejs +43 -0
  29. package/templates/common/kafka/js/services/kafkaService.js.ejs +6 -1
  30. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +5 -0
  31. package/templates/common/package.json.ejs +3 -1
  32. package/templates/common/shutdown/js/gracefulShutdown.js.ejs +61 -0
  33. package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +58 -0
  34. package/templates/common/tests/health.test.ts.ejs +16 -6
  35. package/templates/mvc/js/src/config/env.js.ejs +46 -0
  36. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +4 -1
  37. package/templates/mvc/js/src/index.js.ejs +12 -10
  38. package/templates/mvc/js/src/utils/{error.middleware.js → errorMiddleware.js} +2 -1
  39. package/templates/mvc/ts/src/config/env.ts.ejs +45 -0
  40. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +4 -1
  41. package/templates/mvc/ts/src/index.ts.ejs +20 -20
  42. package/templates/mvc/ts/src/utils/{error.middleware.ts.ejs → errorMiddleware.ts.ejs} +2 -1
  43. package/templates/clean-architecture/js/src/domain/repositories/UserRepository.js +0 -9
@@ -0,0 +1,46 @@
1
+ const { z } = require('zod');
2
+ const logger = require('../utils/logger');
3
+
4
+ if (process.env.NODE_ENV !== 'production') {
5
+ require('dotenv').config();
6
+ }
7
+ const envSchema = z.object({
8
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
9
+ PORT: z.string().transform(Number).default('3000'),
10
+ <%_ if (database !== 'None') { -%>
11
+ DB_HOST: z.string(),
12
+ <%_ if (database === 'MySQL') { -%>
13
+ DB_USER: z.string(),
14
+ DB_PASSWORD: z.string(),
15
+ DB_NAME: z.string(),
16
+ DB_PORT: z.string().transform(Number),
17
+ <%_ } else if (database === 'PostgreSQL') { -%>
18
+ DB_USER: z.string(),
19
+ DB_PASSWORD: z.string(),
20
+ DB_NAME: z.string(),
21
+ DB_PORT: z.string().transform(Number),
22
+ <%_ } else if (database === 'MongoDB') { -%>
23
+ DB_NAME: z.string(),
24
+ DB_PORT: z.string().transform(Number),
25
+ <%_ } -%>
26
+ <%_ } -%>
27
+ <%_ if (caching === 'Redis') { -%>
28
+ REDIS_HOST: z.string(),
29
+ REDIS_PORT: z.string().transform(Number),
30
+ REDIS_PASSWORD: z.string().optional(),
31
+ <%_ } -%>
32
+ <%_ if (communication === 'Kafka') { -%>
33
+ KAFKA_BROKER: z.string(),
34
+ <%_ } -%>
35
+ });
36
+
37
+ const _env = envSchema.safeParse(process.env);
38
+
39
+ if (!_env.success) {
40
+ logger.error('❌ Invalid environment variables:', _env.error.format());
41
+ process.exit(1);
42
+ }
43
+
44
+ const env = _env.data;
45
+
46
+ module.exports = { env };
@@ -10,7 +10,10 @@ const userResolvers = {
10
10
  createUser: async (_, { name, email }) => {
11
11
  return await userController.createUser({ name, email });
12
12
  }
13
- }
13
+ }<%_ if (database === 'MongoDB') { -%>,
14
+ User: {
15
+ id: (parent) => parent.id || parent._id
16
+ }<%_ } %>
14
17
  };
15
18
 
16
19
  module.exports = { userResolvers };
@@ -1,7 +1,7 @@
1
1
  const express = require('express');
2
2
  const cors = require('cors');
3
- require('dotenv').config();
4
3
  <%_ if (communication === 'REST APIs') { -%>const apiRoutes = require('./routes/api');<%_ } -%>
4
+ const healthRoutes = require('./routes/healthRoute');
5
5
  <%_ if (communication === 'Kafka') { -%>const { connectKafka, sendMessage } = require('./services/kafkaService');<%_ } -%>
6
6
  <%_ if (communication === 'GraphQL') { -%>
7
7
  const { ApolloServer } = require('@apollo/server');
@@ -16,12 +16,13 @@ const { gqlContext } = require('./graphql/context');
16
16
  const swaggerUi = require('swagger-ui-express');
17
17
  const swaggerSpecs = require('./config/swagger');
18
18
  <%_ } -%>
19
+ const { env } = require('./config/env');
19
20
 
20
21
  const app = express();
21
- const PORT = process.env.PORT || 3000;
22
+ const PORT = env.PORT;
22
23
  const logger = require('./utils/logger');
23
24
  const morgan = require('morgan');
24
- const { errorMiddleware } = require('./utils/error.middleware');
25
+ const { errorMiddleware } = require('./utils/errorMiddleware');
25
26
 
26
27
  app.use(cors());
27
28
  app.use(express.json());
@@ -48,15 +49,13 @@ app.get('/', (req, res) => {
48
49
  });
49
50
  });
50
51
  <% } -%>
51
- app.get('/health', (req, res) => {
52
- res.json({ status: 'UP' });
53
- });
52
+ app.use('/health', healthRoutes);
54
53
 
55
54
  // Start Server Logic
56
55
  const startServer = async () => {
57
56
  <%_ if (communication === 'GraphQL') { -%>
58
57
  // GraphQL Setup
59
- const server = new ApolloServer({
58
+ const apolloServer = new ApolloServer({
60
59
  typeDefs,
61
60
  resolvers,
62
61
  plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
@@ -80,11 +79,11 @@ const startServer = async () => {
80
79
  return formattedError;
81
80
  },
82
81
  });
83
- await server.start();
84
- app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
82
+ await apolloServer.start();
83
+ app.use('/graphql', expressMiddleware(apolloServer, { context: gqlContext }));
85
84
  <%_ } -%>
86
85
  app.use(errorMiddleware);
87
- app.listen(PORT, () => {
86
+ const server = app.listen(PORT, () => {
88
87
  logger.info(`Server running on port ${PORT}`);
89
88
  <%_ if (communication === 'Kafka') { -%>
90
89
  connectKafka().then(() => {
@@ -95,6 +94,9 @@ const startServer = async () => {
95
94
  });
96
95
  <%_ } -%>
97
96
  });
97
+
98
+ const setupGracefulShutdown = require('./utils/gracefulShutdown');
99
+ setupGracefulShutdown(server);
98
100
  };
99
101
 
100
102
  <%_ if (database !== 'None') { -%>
@@ -2,7 +2,8 @@ const logger = require('./logger');
2
2
  const { ApiError } = require('../errors/ApiError');
3
3
  const HTTP_STATUS = require('./httpCodes');
4
4
 
5
- const errorMiddleware = (err, req, res, _) => {
5
+ // eslint-disable-next-line no-unused-vars
6
+ const errorMiddleware = (err, req, res, next) => {
6
7
  let error = err;
7
8
 
8
9
  if (!(error instanceof ApiError)) {
@@ -0,0 +1,45 @@
1
+ import dotenv from 'dotenv';
2
+ import { z } from 'zod';
3
+ import logger from '@/utils/logger';
4
+
5
+ if (process.env.NODE_ENV !== 'production') {
6
+ dotenv.config();
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
+ export const env = _env.data;
@@ -14,5 +14,8 @@ export const userResolvers = {
14
14
  const user = await userController.createUser({ name, email });
15
15
  return user;
16
16
  }
17
- }
17
+ }<%_ if (database === 'MongoDB') { -%>,
18
+ User: {
19
+ id: (parent: { id?: string; _id?: unknown }) => parent.id || parent._id
20
+ }<%_ } %>
18
21
  };
@@ -1,12 +1,14 @@
1
+ import { env } from '@/config/env';
1
2
  import express, { Request, Response } from 'express';
2
3
  import cors from 'cors';
3
4
  import helmet from 'helmet';
4
5
  import hpp from 'hpp';
5
6
  import rateLimit from 'express-rate-limit';
6
- import dotenv from 'dotenv';
7
7
  import logger from '@/utils/logger';
8
8
  import morgan from 'morgan';
9
- import { errorMiddleware } from '@/utils/error.middleware';
9
+ import { errorMiddleware } from '@/utils/errorMiddleware';
10
+ import { setupGracefulShutdown } from '@/utils/gracefulShutdown';
11
+ import healthRoutes from '@/routes/healthRoute';
10
12
  <%_ if (communication === 'REST APIs') { -%>
11
13
  import apiRoutes from '@/routes/api';<%_ } -%>
12
14
  <% if (communication === 'REST APIs') { %>
@@ -23,10 +25,8 @@ import { typeDefs, resolvers } from '@/graphql';
23
25
  import { gqlContext, MyContext } from '@/graphql/context';
24
26
  <% } -%>
25
27
 
26
- dotenv.config();
27
-
28
28
  const app = express();
29
- const port = process.env.PORT || 3000;
29
+ const port = env.PORT;
30
30
 
31
31
  // Security Middleware
32
32
  <%_ if (communication === 'GraphQL') { -%>
@@ -72,15 +72,13 @@ app.get('/', (req: Request, res: Response) => {
72
72
  });
73
73
  });
74
74
  <% } -%>
75
- app.get('/health', (req: Request, res: Response) => {
76
- res.json({ status: 'UP' });
77
- });
75
+ app.use('/health', healthRoutes);
78
76
 
79
77
  // Start Server Logic
80
78
  const startServer = async () => {
81
79
  <%_ if (communication === 'GraphQL') { -%>
82
80
  // GraphQL Setup
83
- const server = new ApolloServer<MyContext>({
81
+ const apolloServer = new ApolloServer<MyContext>({
84
82
  typeDefs,
85
83
  resolvers,
86
84
  plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
@@ -104,24 +102,26 @@ const startServer = async () => {
104
102
  return formattedError;
105
103
  },
106
104
  });
107
- await server.start();
108
- app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
105
+ await apolloServer.start();
106
+ app.use('/graphql', expressMiddleware(apolloServer, { context: gqlContext }));
109
107
  <%_ } -%>
110
108
  app.use(errorMiddleware);
111
- app.listen(port, () => {
109
+ <%_ if (communication === 'Kafka') { -%>
110
+ const kafkaService = new KafkaService();
111
+ <%_ } -%>
112
+ const server = app.listen(port, () => {
112
113
  logger.info(`Server running on port ${port}`);
113
114
  <%_ if (communication === 'Kafka') { -%>
114
- try {
115
- const kafkaService = new KafkaService();
116
- kafkaService.connect().then(() => {
117
- logger.info('Kafka connected');
118
- kafkaService.sendMessage('test-topic', 'Hello Kafka from MVC TS!');
119
- });
120
- } catch (err) {
115
+ kafkaService.connect().then(() => {
116
+ logger.info('Kafka connected');
117
+ kafkaService.sendMessage('test-topic', 'Hello Kafka from MVC TS!');
118
+ }).catch(err => {
121
119
  logger.error('Failed to connect to Kafka:', err);
122
- }
120
+ });
123
121
  <%_ } -%>
124
122
  });
123
+
124
+ setupGracefulShutdown(server<% if(communication === 'Kafka') { %>, kafkaService<% } %>);
125
125
  };
126
126
 
127
127
  <%_ if (database !== 'None') { -%>
@@ -3,7 +3,8 @@ import logger from '@/utils/logger';
3
3
  import { ApiError } from '@/errors/ApiError';
4
4
  import { HTTP_STATUS } from '@/utils/httpCodes';
5
5
 
6
- export const errorMiddleware = (err: Error, req: Request, res: Response, _: unknown) => {
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ export const errorMiddleware = (err: Error, req: Request, res: Response, next: unknown) => {
7
8
  let error = err;
8
9
 
9
10
  if (!(error instanceof ApiError)) {
@@ -1,9 +0,0 @@
1
- class UserRepository {
2
- async save(user) {
3
- // Database logic would go here
4
- user.id = Math.floor(Math.random() * 1000);
5
- return user;
6
- }
7
- }
8
-
9
- module.exports = UserRepository;