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.
- package/CHANGELOG.md +22 -3
- package/README.md +2 -1
- package/bin/index.js +2 -2
- package/docs/generatorFlow.md +9 -9
- package/lib/generator.js +8 -2
- package/lib/modules/app-setup.js +85 -33
- package/lib/modules/config-files.js +18 -1
- package/lib/modules/kafka-setup.js +4 -39
- package/package.json +1 -1
- package/templates/clean-architecture/js/src/index.js.ejs +2 -4
- package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +47 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/{middlewares/error.middleware.js → middleware/errorMiddleware.js} +2 -1
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +25 -11
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +4 -1
- package/templates/clean-architecture/ts/src/config/env.ts.ejs +46 -0
- package/templates/clean-architecture/ts/src/index.ts.ejs +23 -22
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +4 -1
- package/templates/clean-architecture/ts/src/utils/{error.middleware.ts.ejs → errorMiddleware.ts.ejs} +2 -1
- package/templates/common/.env.example.ejs +3 -1
- package/templates/common/README.md.ejs +30 -0
- package/templates/common/caching/js/redisClient.js.ejs +4 -0
- package/templates/common/caching/ts/redisClient.ts.ejs +4 -0
- package/templates/common/database/js/mongoose.js.ejs +3 -1
- package/templates/common/database/ts/mongoose.ts.ejs +3 -1
- package/templates/common/docker-compose.yml.ejs +11 -1
- package/templates/common/ecosystem.config.js.ejs +40 -0
- package/templates/common/health/js/healthRoute.js.ejs +44 -0
- package/templates/common/health/ts/healthRoute.ts.ejs +43 -0
- package/templates/common/kafka/js/services/kafkaService.js.ejs +6 -1
- package/templates/common/kafka/ts/services/kafkaService.ts.ejs +5 -0
- package/templates/common/package.json.ejs +3 -1
- package/templates/common/shutdown/js/gracefulShutdown.js.ejs +61 -0
- package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +58 -0
- package/templates/common/tests/health.test.ts.ejs +16 -6
- package/templates/mvc/js/src/config/env.js.ejs +46 -0
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +4 -1
- package/templates/mvc/js/src/index.js.ejs +12 -10
- package/templates/mvc/js/src/utils/{error.middleware.js → errorMiddleware.js} +2 -1
- package/templates/mvc/ts/src/config/env.ts.ejs +45 -0
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +4 -1
- package/templates/mvc/ts/src/index.ts.ejs +20 -20
- package/templates/mvc/ts/src/utils/{error.middleware.ts.ejs → errorMiddleware.ts.ejs} +2 -1
- 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 =
|
|
22
|
+
const PORT = env.PORT;
|
|
22
23
|
const logger = require('./utils/logger');
|
|
23
24
|
const morgan = require('morgan');
|
|
24
|
-
const { errorMiddleware } = require('./utils/
|
|
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.
|
|
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
|
|
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
|
|
84
|
-
app.use('/graphql', expressMiddleware(
|
|
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
|
-
|
|
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/
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
|
108
|
-
app.use('/graphql', expressMiddleware(
|
|
105
|
+
await apolloServer.start();
|
|
106
|
+
app.use('/graphql', expressMiddleware(apolloServer, { context: gqlContext }));
|
|
109
107
|
<%_ } -%>
|
|
110
108
|
app.use(errorMiddleware);
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
kafkaService.
|
|
117
|
-
|
|
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
|
-
|
|
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)) {
|