nodejs-quickstart-structure 1.12.0 → 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 +13 -3
- package/README.md +1 -0
- package/docs/generatorFlow.md +9 -9
- package/lib/modules/app-setup.js +63 -35
- package/lib/modules/kafka-setup.js +2 -4
- package/package.json +1 -1
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +20 -9
- package/templates/clean-architecture/ts/src/index.ts.ejs +15 -11
- package/templates/common/caching/js/redisClient.js.ejs +4 -0
- package/templates/common/caching/ts/redisClient.ts.ejs +4 -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/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/index.js.ejs +10 -8
- package/templates/mvc/ts/src/index.ts.ejs +13 -9
- /package/templates/clean-architecture/js/src/infrastructure/webserver/{middlewares/error.middleware.js → middleware/errorMiddleware.js} +0 -0
- /package/templates/clean-architecture/ts/src/utils/{error.middleware.ts.ejs → errorMiddleware.ts.ejs} +0 -0
- /package/templates/mvc/js/src/utils/{error.middleware.js → errorMiddleware.js} +0 -0
- /package/templates/mvc/ts/src/utils/{error.middleware.ts.ejs → errorMiddleware.ts.ejs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.13.0] - 2026-03-05
|
|
9
|
+
### Added
|
|
10
|
+
- **Enhanced Health Check System:**
|
|
11
|
+
- Standardized health logic for both MVC and Clean Architecture.
|
|
12
|
+
- Deep verification: Automatically pings the selected database (MySQL, PostgreSQL, MongoDB) during health checks and returns a `DOWN` status with 500 code if the connection is lost.
|
|
13
|
+
- Structured response including `uptime`, `memoryUsage`, `database` connectivity status, and `timestamp`.
|
|
14
|
+
- **Improved Graceful Shutdown:**
|
|
15
|
+
- Standardized signal handling (`SIGINT`, `SIGTERM`) across all architecture types to ensure clean disposal of database and caching resources.
|
|
16
|
+
- Integrated proper shutdown sequences for Redis, MySQL, PostgreSQL, and MongoDB.
|
|
17
|
+
|
|
8
18
|
## [1.12.0] - 2026-03-04
|
|
9
19
|
### Added
|
|
10
20
|
- **Zod Environment Validation:** Replaced manual `dotenv` process calls in server entry points with a centralized schema parser.
|
|
@@ -16,13 +26,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
26
|
|
|
17
27
|
## [1.11.1] - 2026-03-03
|
|
18
28
|
### Fixed
|
|
19
|
-
- Fixed relative import paths in Clean Architecture JS `
|
|
29
|
+
- Fixed relative import paths in Clean Architecture JS `errorMiddleware.js` — changed to correct 3-level relative paths (`../../../`).
|
|
20
30
|
|
|
21
31
|
## [1.11.0] - 2026-03-02
|
|
22
32
|
### Added
|
|
23
33
|
- **Centralized Error Handling Mechanism:** All generated projects now include a standardized, predictable error response structure for both REST APIs and GraphQL communication types.
|
|
24
34
|
- New `src/errors/` directory with custom error classes: `ApiError`, `NotFoundError`, `BadRequestError`.
|
|
25
|
-
- New `error.middleware.{ts|js}` global error handler placed at the end of the Express middleware chain in `src/utils/` (MVC) and `src/utils/` + `src/infrastructure/webserver/
|
|
35
|
+
- New `error.middleware.{ts|js}` global error handler placed at the end of the Express middleware chain in `src/utils/` (MVC) and `src/utils/` + `src/infrastructure/webserver/middleware/` (Clean Architecture).
|
|
26
36
|
- Integrates `winston` logger to automatically log 500-level errors to persistent log files.
|
|
27
37
|
- All controllers updated to pass errors via `next(error)` instead of manually sending responses.
|
|
28
38
|
- **GraphQL:** Apollo Server `formatError` hook configured with `unwrapResolverError` to intercept resolver errors and map `ApiError` instances to structured GraphQL extension codes.
|
|
@@ -32,7 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
32
42
|
### Fixed
|
|
33
43
|
- Fixed `swagger.js` / `swagger.yml` being incorrectly generated for non-REST API configurations (GraphQL, Kafka). Converted static `swagger.js` to `swagger.js.ejs` in MVC JS and Clean Architecture JS templates so `renderSwaggerConfig` can conditionally control its generation.
|
|
34
44
|
- Fixed `userRoutes.ts` in Clean Architecture TypeScript template not passing `NextFunction` to controller methods, causing `TypeScript TS2554: Expected 3 arguments` compile errors during Docker builds.
|
|
35
|
-
- Fixed incorrect relative import paths (`../../../../`) in Clean Architecture JS `
|
|
45
|
+
- Fixed incorrect relative import paths (`../../../../`) in Clean Architecture JS `errorMiddleware.js` — changed to correct 3-level relative paths (`../../../`).
|
|
36
46
|
- Fixed `HTTP_STATUS` being imported without destructuring from `httpCodes.js` (which uses a plain `module.exports = HTTP_STATUS` default export), causing `Cannot read properties of undefined` runtime errors.
|
|
37
47
|
- Fixed `renderErrorMiddleware` in `app-setup.js` deleting from the **template source directory** instead of the generated project's target directory. This caused error middleware templates to disappear from disk after the first test run, breaking all subsequent generations.
|
|
38
48
|
|
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ We don't just generate boilerplate; we generate **production-ready** foundations
|
|
|
34
34
|
- **🧪 Testing Strategy**: Integrated `Jest` and `Supertest` setup for Unit and Integration testing.
|
|
35
35
|
- **🔄 CI/CD Integration**: Pre-configured workflows for **GitHub Actions**, **Jenkins**, and **GitLab CI**.
|
|
36
36
|
- **⚓ Git Hooks**: `Husky` and `Lint-Staged` to ensure no bad code is ever committed.
|
|
37
|
+
- **🤝 Reliability**: Advanced Health Checks (`/health`) with deep database pings and Graceful Shutdown workflows for zero-downtime rollouts.
|
|
37
38
|
- **🐳 DevOps**: Highly optimized **Multi-Stage Dockerfile** for small, secure production images.
|
|
38
39
|
- **🚀 Deployment**: Ship confidently with an integrated **PM2 Ecosystem Configuration** for zero-downtime reloads and robust process management.
|
|
39
40
|
|
package/docs/generatorFlow.md
CHANGED
|
@@ -50,10 +50,10 @@ The `generateProject` function in `lib/generator.js` executes the following step
|
|
|
50
50
|
* Processes the entry point file to wire up the selected DB, Architecture, and Communication type.
|
|
51
51
|
* **GraphQL**: Wires up Apollo Server middleware with `formatError` hook for centralized error handling.
|
|
52
52
|
* **REST APIs / Kafka**: Registers `app.use(errorMiddleware)` at the end of the Express chain.
|
|
53
|
-
7. **
|
|
54
|
-
* Processes `
|
|
55
|
-
* Renders to `src/utils/
|
|
56
|
-
* **Clean Architecture**: Also handles `src/infrastructure/webserver/
|
|
53
|
+
7. **Error Middleware setup** (`renderErrorMiddleware`):
|
|
54
|
+
* Processes `errorMiddleware.{ts|js}.ejs` template from the target directory's `src/utils/` path.
|
|
55
|
+
* Renders to `src/utils/errorMiddleware.{ts|js}` in the generated project.
|
|
56
|
+
* **Clean Architecture**: Also handles `src/infrastructure/webserver/middleware/errorMiddleware.{js}` path.
|
|
57
57
|
8. **Dynamic Component Generation**:
|
|
58
58
|
* **MVC**: Generates `userController` (imports specific DB service, uses `next(error)`).
|
|
59
59
|
* **Clean Architecture**: Generates `UserRepository` (infrastructure layer implementation).
|
|
@@ -141,7 +141,7 @@ project-name/
|
|
|
141
141
|
│ ├── models/ # Database models
|
|
142
142
|
│ ├── routes/ # Express routes
|
|
143
143
|
│ ├── utils/
|
|
144
|
-
│ │ ├──
|
|
144
|
+
│ │ ├── errorMiddleware.{ts|js} # Global error handler
|
|
145
145
|
│ │ ├── logger.{ts|js}
|
|
146
146
|
│ │ └── httpCodes.{ts|js}
|
|
147
147
|
│ └── index.js|ts # Entry point (registers errorMiddleware last)
|
|
@@ -165,7 +165,7 @@ project-name/
|
|
|
165
165
|
│ │ └── resolvers/ # user.resolvers (calls controllers, throws errors)
|
|
166
166
|
│ ├── models/
|
|
167
167
|
│ ├── utils/
|
|
168
|
-
│ │ ├──
|
|
168
|
+
│ │ ├── errorMiddleware.{ts|js} # Express-level fallback error handler
|
|
169
169
|
│ │ └── logger.{ts|js}
|
|
170
170
|
│ └── index.js|ts # Apollo Server + formatError hook
|
|
171
171
|
└── ...
|
|
@@ -205,11 +205,11 @@ project-name/
|
|
|
205
205
|
│ │ ├── database/ # DB connection & models
|
|
206
206
|
│ │ ├── repositories/ # Data access implementation
|
|
207
207
|
│ │ └── webserver/
|
|
208
|
-
│ │ ├──
|
|
209
|
-
│ │ │ └──
|
|
208
|
+
│ │ ├── middleware/
|
|
209
|
+
│ │ │ └── errorMiddleware.js # JS only — Express error handler
|
|
210
210
|
│ │ └── server.js # Express app setup
|
|
211
211
|
│ ├── utils/
|
|
212
|
-
│ │ └──
|
|
212
|
+
│ │ └── errorMiddleware.ts # TS — global error handler
|
|
213
213
|
│ └── index.js|ts # Registers errorMiddleware after Apollo/Express
|
|
214
214
|
└── ...
|
|
215
215
|
```
|
package/lib/modules/app-setup.js
CHANGED
|
@@ -3,19 +3,20 @@ import path from 'path';
|
|
|
3
3
|
import ejs from 'ejs';
|
|
4
4
|
|
|
5
5
|
export const renderIndexFile = async (templatePath, targetDir, config) => {
|
|
6
|
-
const { communication, viewEngine, database, architecture, projectName, language } = config;
|
|
6
|
+
const { communication, viewEngine, database, architecture, projectName, language, caching } = config;
|
|
7
7
|
const indexFileName = language === 'TypeScript' ? 'index.ts' : 'index.js';
|
|
8
8
|
const indexPath = path.join(targetDir, 'src', indexFileName);
|
|
9
9
|
const indexTemplateSource = path.join(templatePath, 'src', `${indexFileName}.ejs`);
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
if (await fs.pathExists(indexTemplateSource)) {
|
|
12
12
|
const indexTemplate = await fs.readFile(indexTemplateSource, 'utf-8');
|
|
13
|
-
const indexContent = ejs.render(indexTemplate, {
|
|
14
|
-
communication,
|
|
13
|
+
const indexContent = ejs.render(indexTemplate, {
|
|
14
|
+
communication,
|
|
15
15
|
viewEngine,
|
|
16
16
|
database,
|
|
17
17
|
architecture,
|
|
18
|
-
projectName
|
|
18
|
+
projectName,
|
|
19
|
+
caching
|
|
19
20
|
});
|
|
20
21
|
await fs.writeFile(indexPath, indexContent);
|
|
21
22
|
await fs.remove(path.join(targetDir, 'src', `${indexFileName}.ejs`));
|
|
@@ -25,7 +26,7 @@ export const renderIndexFile = async (templatePath, targetDir, config) => {
|
|
|
25
26
|
export const renderEnvConfig = async (templatePath, targetDir, config) => {
|
|
26
27
|
const { language, architecture, database, caching, communication } = config;
|
|
27
28
|
const envExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
let configDir = path.join(targetDir, 'src', 'config');
|
|
30
31
|
if (architecture === 'Clean Architecture' && language === 'JavaScript') {
|
|
31
32
|
configDir = path.join(targetDir, 'src', 'infrastructure', 'config');
|
|
@@ -33,7 +34,7 @@ export const renderEnvConfig = async (templatePath, targetDir, config) => {
|
|
|
33
34
|
|
|
34
35
|
const envTemplatePath = path.join(configDir, `env.${envExt}.ejs`);
|
|
35
36
|
const envDestPath = path.join(configDir, `env.${envExt}`);
|
|
36
|
-
|
|
37
|
+
|
|
37
38
|
if (await fs.pathExists(envTemplatePath)) {
|
|
38
39
|
const envTemplate = await fs.readFile(envTemplatePath, 'utf-8');
|
|
39
40
|
const envContent = ejs.render(envTemplate, {
|
|
@@ -48,7 +49,7 @@ export const renderEnvConfig = async (templatePath, targetDir, config) => {
|
|
|
48
49
|
|
|
49
50
|
export const renderErrorMiddleware = async (templatePath, targetDir, config) => {
|
|
50
51
|
const { language, architecture } = config;
|
|
51
|
-
const errName = language === 'TypeScript' ? '
|
|
52
|
+
const errName = language === 'TypeScript' ? 'errorMiddleware.ts' : 'errorMiddleware.js';
|
|
52
53
|
const errTemplateName = `${errName}.ejs`;
|
|
53
54
|
|
|
54
55
|
if (architecture === 'MVC') {
|
|
@@ -68,8 +69,8 @@ export const renderErrorMiddleware = async (templatePath, targetDir, config) =>
|
|
|
68
69
|
await fs.writeFile(utilsDest, await fs.readFile(utilsEjsCopy, 'utf-8'));
|
|
69
70
|
await fs.remove(utilsEjsCopy);
|
|
70
71
|
}
|
|
71
|
-
// Also render the
|
|
72
|
-
const mwDir = path.join(targetDir, 'src/infrastructure/webserver/
|
|
72
|
+
// Also render the middleware version if present (NOT removing from template)
|
|
73
|
+
const mwDir = path.join(targetDir, 'src/infrastructure/webserver/middleware');
|
|
73
74
|
const mwEjsCopy = path.join(mwDir, errTemplateName);
|
|
74
75
|
const mwDest = path.join(mwDir, errName);
|
|
75
76
|
if (await fs.pathExists(mwEjsCopy)) {
|
|
@@ -94,11 +95,11 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
94
95
|
await fs.writeFile(userControllerPath, content);
|
|
95
96
|
await fs.remove(path.join(targetDir, 'src/controllers', `${userControllerName}.ejs`));
|
|
96
97
|
}
|
|
97
|
-
}
|
|
98
|
+
}
|
|
98
99
|
// Clean Architecture Repo
|
|
99
100
|
else if (architecture === 'Clean Architecture') {
|
|
100
101
|
const repoName = language === 'TypeScript' ? 'UserRepository.ts' : 'UserRepository.js';
|
|
101
|
-
const repoPath = path.join(targetDir, 'src/infrastructure/repositories', repoName);
|
|
102
|
+
const repoPath = path.join(targetDir, 'src/infrastructure/repositories', repoName);
|
|
102
103
|
const repoTemplate = path.join(templatePath, 'src/infrastructure/repositories', `${repoName}.ejs`);
|
|
103
104
|
|
|
104
105
|
if (await fs.pathExists(repoTemplate)) {
|
|
@@ -119,22 +120,58 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
119
120
|
}
|
|
120
121
|
// Render Server (Clean Arch JS only)
|
|
121
122
|
if (architecture === 'Clean Architecture' && language === 'JavaScript') {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
const serverName = 'server.js';
|
|
124
|
+
const serverPath = path.join(targetDir, 'src/infrastructure/webserver', serverName);
|
|
125
|
+
const serverTemplate = path.join(templatePath, 'src/infrastructure/webserver', `${serverName}.ejs`);
|
|
126
|
+
|
|
127
|
+
if (await fs.pathExists(serverTemplate)) {
|
|
128
|
+
const content = ejs.render(await fs.readFile(serverTemplate, 'utf-8'), { communication: config.communication, database, caching });
|
|
129
|
+
await fs.writeFile(serverPath, content);
|
|
130
|
+
await fs.remove(path.join(targetDir, 'src/infrastructure/webserver', `${serverName}.ejs`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Cleanup REST routes if GraphQL or Kafka is selected
|
|
135
|
+
if (config.communication !== 'REST APIs') {
|
|
136
|
+
if (architecture === 'MVC') {
|
|
137
|
+
await fs.remove(path.join(targetDir, 'src/routes'));
|
|
138
|
+
} else if (architecture === 'Clean Architecture') {
|
|
139
|
+
await fs.remove(path.join(targetDir, 'src/interfaces/routes'));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Advanced Health Check Route Modularization
|
|
144
|
+
const healthExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
145
|
+
const healthTemplatePath = path.join(templatePath, '../../common/health', healthExt, `healthRoute.${healthExt}.ejs`);
|
|
146
|
+
|
|
147
|
+
if (await fs.pathExists(healthTemplatePath)) {
|
|
148
|
+
let routeDestDir = path.join(targetDir, 'src', 'routes');
|
|
149
|
+
if (architecture === 'Clean Architecture') {
|
|
150
|
+
routeDestDir = path.join(targetDir, 'src', 'interfaces', 'routes');
|
|
151
|
+
}
|
|
152
|
+
await fs.ensureDir(routeDestDir);
|
|
153
|
+
|
|
154
|
+
const healthRouteContent = ejs.render(await fs.readFile(healthTemplatePath, 'utf-8'), { database, architecture });
|
|
155
|
+
await fs.writeFile(path.join(routeDestDir, `healthRoute.${healthExt}`), healthRouteContent);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Advanced Graceful Shutdown Modularization
|
|
159
|
+
const shutdownExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
160
|
+
const shutdownTemplatePath = path.join(templatePath, '../../common/shutdown', shutdownExt, `gracefulShutdown.${shutdownExt}.ejs`);
|
|
161
|
+
|
|
162
|
+
if (await fs.pathExists(shutdownTemplatePath)) {
|
|
163
|
+
let utilsDestDir = path.join(targetDir, 'src', 'utils');
|
|
164
|
+
await fs.ensureDir(utilsDestDir);
|
|
165
|
+
|
|
166
|
+
const shutdownContent = ejs.render(await fs.readFile(shutdownTemplatePath, 'utf-8'), { database, architecture, caching, communication: config.communication });
|
|
167
|
+
await fs.writeFile(path.join(utilsDestDir, `gracefulShutdown.${shutdownExt}`), shutdownContent);
|
|
131
168
|
}
|
|
132
169
|
|
|
133
170
|
// GraphQL Setup
|
|
134
171
|
if (config.communication === 'GraphQL') {
|
|
135
172
|
const ext = language === 'TypeScript' ? 'ts' : 'js';
|
|
136
173
|
let graphqlInternalPath = architecture === 'MVC' ? 'src/graphql' : 'src/interfaces/graphql';
|
|
137
|
-
|
|
174
|
+
|
|
138
175
|
const sourceGraphqDir = path.join(templatePath, graphqlInternalPath);
|
|
139
176
|
const targetGraphqlDir = path.join(targetDir, graphqlInternalPath);
|
|
140
177
|
|
|
@@ -169,27 +206,18 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
169
206
|
let graphqlInternalPath = architecture === 'MVC' ? 'src/graphql' : 'src/interfaces/graphql';
|
|
170
207
|
await fs.remove(path.join(targetDir, graphqlInternalPath));
|
|
171
208
|
}
|
|
172
|
-
|
|
173
|
-
// Cleanup REST routes if GraphQL or Kafka is selected
|
|
174
|
-
if (config.communication !== 'REST APIs') {
|
|
175
|
-
if (architecture === 'MVC') {
|
|
176
|
-
await fs.remove(path.join(targetDir, 'src/routes'));
|
|
177
|
-
} else if (architecture === 'Clean Architecture') {
|
|
178
|
-
await fs.remove(path.join(targetDir, 'src/interfaces/routes'));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
209
|
};
|
|
182
210
|
|
|
183
211
|
export const renderSwaggerConfig = async (templatesDir, targetDir, config) => {
|
|
184
212
|
const { communication, projectName, architecture, language } = config;
|
|
185
|
-
|
|
213
|
+
|
|
186
214
|
// Check for Swagger config template (typically in src/config/swagger.ts.ejs)
|
|
187
215
|
const swaggerTsTemplate = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
|
|
188
216
|
// MVC JS uses swagger.js.ejs in src/config/
|
|
189
217
|
const swaggerJsTemplate = path.join(targetDir, 'src', 'config', 'swagger.js.ejs');
|
|
190
218
|
// Clean Arch JS uses swagger.js.ejs in src/infrastructure/webserver/
|
|
191
219
|
const swaggerJsCleanTemplate = path.join(targetDir, 'src', 'infrastructure', 'webserver', 'swagger.js.ejs');
|
|
192
|
-
|
|
220
|
+
|
|
193
221
|
// Ensure config directory exists
|
|
194
222
|
let configDir = path.join(targetDir, 'src', 'config');
|
|
195
223
|
if (architecture === 'Clean Architecture' && language === 'JavaScript') {
|
|
@@ -221,7 +249,7 @@ export const renderSwaggerConfig = async (templatesDir, targetDir, config) => {
|
|
|
221
249
|
await fs.writeFile(path.join(targetDir, 'src', 'infrastructure', 'webserver', 'swagger.js'), content);
|
|
222
250
|
}
|
|
223
251
|
}
|
|
224
|
-
|
|
252
|
+
|
|
225
253
|
// Always remove the .ejs template after processing (or if non-REST, just delete it)
|
|
226
254
|
if (await fs.pathExists(swaggerTsTemplate)) await fs.remove(swaggerTsTemplate);
|
|
227
255
|
if (await fs.pathExists(swaggerJsTemplate)) await fs.remove(swaggerJsTemplate);
|
|
@@ -239,7 +267,7 @@ export const setupViews = async (templatesDir, targetDir, config) => {
|
|
|
239
267
|
if (architecture === 'MVC' && viewEngine && viewEngine !== 'None') {
|
|
240
268
|
const viewsSource = path.join(templatesDir, 'common', 'views', viewEngine.toLowerCase());
|
|
241
269
|
if (await fs.pathExists(viewsSource)) {
|
|
242
|
-
|
|
270
|
+
await fs.copy(viewsSource, path.join(targetDir, 'src/views'));
|
|
243
271
|
}
|
|
244
272
|
}
|
|
245
273
|
};
|
|
@@ -55,16 +55,14 @@ export const setupKafka = async (templatesDir, targetDir, config) => {
|
|
|
55
55
|
// Cleanup old services folder
|
|
56
56
|
await fs.remove(path.join(targetDir, 'src/services'));
|
|
57
57
|
|
|
58
|
-
// Remove REST-specific folders (Interfaces)
|
|
59
|
-
await fs.remove(path.join(targetDir, 'src/interfaces/routes'));
|
|
58
|
+
// Remove REST-specific folders (Interfaces) - Note: routes is kept for health endpoint
|
|
60
59
|
await fs.remove(path.join(targetDir, 'src/interfaces/controllers'));
|
|
61
60
|
|
|
62
61
|
// Original logic removed src/config entirely, but now we use it for Zod env validation in TS.
|
|
63
62
|
// We will no longer delete it.
|
|
64
63
|
} else if (architecture === 'MVC' && (!config.viewEngine || config.viewEngine === 'None')) {
|
|
65
|
-
// MVC Cleanup for Kafka Worker (No views)
|
|
64
|
+
// MVC Cleanup for Kafka Worker (No views) - Note: routes is kept for health endpoint
|
|
66
65
|
await fs.remove(path.join(targetDir, 'src/controllers'));
|
|
67
|
-
await fs.remove(path.join(targetDir, 'src/routes'));
|
|
68
66
|
}
|
|
69
67
|
};
|
|
70
68
|
|
package/package.json
CHANGED
|
@@ -2,9 +2,10 @@ const express = require('express');
|
|
|
2
2
|
const cors = require('cors');
|
|
3
3
|
const logger = require('../log/logger');
|
|
4
4
|
const morgan = require('morgan');
|
|
5
|
-
const { errorMiddleware } = require('./
|
|
6
|
-
|
|
5
|
+
const { errorMiddleware } = require('./middleware/errorMiddleware');
|
|
6
|
+
const healthRoutes = require('../../interfaces/routes/healthRoute');
|
|
7
7
|
<%_ if (communication === 'REST APIs') { -%>
|
|
8
|
+
const apiRoutes = require('../../interfaces/routes/api');
|
|
8
9
|
const swaggerUi = require('swagger-ui-express');
|
|
9
10
|
const swaggerSpecs = require('./swagger');
|
|
10
11
|
<%_ } -%>
|
|
@@ -36,7 +37,7 @@ const startServer = async () => {
|
|
|
36
37
|
<%_ } -%>
|
|
37
38
|
<%_ if (communication === 'GraphQL') { -%>
|
|
38
39
|
// GraphQL Setup
|
|
39
|
-
const
|
|
40
|
+
const apolloServer = new ApolloServer({
|
|
40
41
|
typeDefs,
|
|
41
42
|
resolvers,
|
|
42
43
|
plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
|
|
@@ -60,18 +61,28 @@ const startServer = async () => {
|
|
|
60
61
|
return formattedError;
|
|
61
62
|
},
|
|
62
63
|
});
|
|
63
|
-
await
|
|
64
|
-
app.use('/graphql', expressMiddleware(
|
|
64
|
+
await apolloServer.start();
|
|
65
|
+
app.use('/graphql', expressMiddleware(apolloServer, { context: gqlContext }));
|
|
65
66
|
<%_ } -%>
|
|
66
|
-
app.
|
|
67
|
-
res.json({ status: 'UP' });
|
|
68
|
-
});
|
|
67
|
+
app.use('/health', healthRoutes);
|
|
69
68
|
|
|
70
69
|
app.use(errorMiddleware);
|
|
71
70
|
|
|
72
|
-
app.listen(port, () => {
|
|
71
|
+
const server = app.listen(port, () => {
|
|
73
72
|
logger.info(`Server running on port ${port}`);
|
|
73
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
74
|
+
const { connectKafka, sendMessage } = require('../../infrastructure/messaging/kafkaClient');
|
|
75
|
+
connectKafka().then(() => {
|
|
76
|
+
logger.info('Kafka connected');
|
|
77
|
+
sendMessage('test-topic', 'Hello Kafka from Clean Arch JS!');
|
|
78
|
+
}).catch(err => {
|
|
79
|
+
logger.error('Failed to connect to Kafka:', err);
|
|
80
|
+
});
|
|
81
|
+
<%_ } -%>
|
|
74
82
|
});
|
|
83
|
+
|
|
84
|
+
const setupGracefulShutdown = require('../../utils/gracefulShutdown');
|
|
85
|
+
setupGracefulShutdown(server);
|
|
75
86
|
};
|
|
76
87
|
|
|
77
88
|
module.exports = startServer;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import express
|
|
1
|
+
import express from 'express';
|
|
2
2
|
import cors from 'cors';
|
|
3
3
|
import helmet from 'helmet';
|
|
4
4
|
import hpp from 'hpp';
|
|
5
5
|
import rateLimit from 'express-rate-limit';
|
|
6
6
|
import logger from '@/infrastructure/log/logger';
|
|
7
7
|
import morgan from 'morgan';
|
|
8
|
-
import { errorMiddleware } from '@/utils/
|
|
9
|
-
|
|
8
|
+
import { errorMiddleware } from '@/utils/errorMiddleware';
|
|
9
|
+
import { setupGracefulShutdown } from '@/utils/gracefulShutdown';
|
|
10
|
+
import healthRoutes from '@/interfaces/routes/healthRoute';
|
|
10
11
|
<% if (communication === 'REST APIs') { -%>
|
|
12
|
+
import userRoutes from '@/interfaces/routes/userRoutes';
|
|
11
13
|
import swaggerUi from 'swagger-ui-express';
|
|
12
14
|
import swaggerSpecs from '@/config/swagger';<% } -%>
|
|
13
15
|
<%_ if (communication === 'Kafka') { -%>import { KafkaService } from '@/infrastructure/messaging/kafkaClient';<%_ } -%>
|
|
@@ -56,15 +58,13 @@ app.use('/api/users', userRoutes);
|
|
|
56
58
|
<%_ if (communication === 'REST APIs') { -%>
|
|
57
59
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
|
58
60
|
<%_ } -%>
|
|
59
|
-
app.
|
|
60
|
-
res.json({ status: 'UP' });
|
|
61
|
-
});
|
|
61
|
+
app.use('/health', healthRoutes);
|
|
62
62
|
|
|
63
63
|
// Start Server Logic
|
|
64
64
|
const startServer = async () => {
|
|
65
65
|
<%_ if (communication === 'GraphQL') { -%>
|
|
66
66
|
// GraphQL Setup
|
|
67
|
-
const
|
|
67
|
+
const apolloServer = new ApolloServer<MyContext>({
|
|
68
68
|
typeDefs,
|
|
69
69
|
resolvers,
|
|
70
70
|
plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
|
|
@@ -88,14 +88,16 @@ const startServer = async () => {
|
|
|
88
88
|
return formattedError;
|
|
89
89
|
},
|
|
90
90
|
});
|
|
91
|
-
await
|
|
92
|
-
app.use('/graphql', expressMiddleware(
|
|
91
|
+
await apolloServer.start();
|
|
92
|
+
app.use('/graphql', expressMiddleware(apolloServer, { context: gqlContext }));
|
|
93
93
|
<%_ } -%>
|
|
94
94
|
app.use(errorMiddleware);
|
|
95
|
-
|
|
95
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
96
|
+
const kafkaService = new KafkaService();
|
|
97
|
+
<%_ } -%>
|
|
98
|
+
const server = app.listen(port, () => {
|
|
96
99
|
logger.info(`Server running on port ${port}`);
|
|
97
100
|
<%_ if (communication === 'Kafka') { -%>
|
|
98
|
-
const kafkaService = new KafkaService();
|
|
99
101
|
kafkaService.connect().then(() => {
|
|
100
102
|
logger.info('Kafka connected');
|
|
101
103
|
kafkaService.sendMessage('test-topic', 'Hello Kafka from Clean Arch TS!');
|
|
@@ -104,6 +106,8 @@ const startServer = async () => {
|
|
|
104
106
|
});
|
|
105
107
|
<%_ } -%>
|
|
106
108
|
});
|
|
109
|
+
|
|
110
|
+
setupGracefulShutdown(server<% if(communication === 'Kafka') { %>, kafkaService<% } %>);
|
|
107
111
|
};
|
|
108
112
|
|
|
109
113
|
<%_ if (database !== 'None') { -%>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const logger = require('<% if (architecture === "MVC") { %>../utils/logger<% } else { %>../../infrastructure/log/logger<% } %>');
|
|
4
|
+
const HTTP_STATUS = require('<% if (architecture === "MVC") { %>../utils/httpCodes<% } else { %>../../utils/httpCodes<% } %>');
|
|
5
|
+
|
|
6
|
+
router.get('/', async (req, res) => {
|
|
7
|
+
const healthData = {
|
|
8
|
+
status: 'UP',
|
|
9
|
+
uptime: process.uptime(),
|
|
10
|
+
memory: process.memoryUsage(),
|
|
11
|
+
database: 'disconnected',
|
|
12
|
+
timestamp: Date.now()
|
|
13
|
+
};
|
|
14
|
+
logger.info('Health Check');
|
|
15
|
+
|
|
16
|
+
<%_ if (database !== 'None') { -%>
|
|
17
|
+
try {
|
|
18
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
19
|
+
const mongoose = require('mongoose');
|
|
20
|
+
if (mongoose.connection.readyState === 1) {
|
|
21
|
+
if (mongoose.connection.db && mongoose.connection.db.admin) {
|
|
22
|
+
await mongoose.connection.db.admin().ping();
|
|
23
|
+
}
|
|
24
|
+
healthData.database = 'connected';
|
|
25
|
+
}
|
|
26
|
+
<%_ } else { -%>
|
|
27
|
+
const sequelize = require('<% if (architecture === "MVC") { %>../config/database<% } else { %>../../infrastructure/database/database<% } %>');
|
|
28
|
+
await sequelize.authenticate();
|
|
29
|
+
healthData.database = 'connected';
|
|
30
|
+
<%_ } -%>
|
|
31
|
+
} catch (err) {
|
|
32
|
+
healthData.database = 'error';
|
|
33
|
+
healthData.status = 'DOWN';
|
|
34
|
+
logger.error('Health Check Database Ping Failed:', err);
|
|
35
|
+
return res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json(healthData);
|
|
36
|
+
}
|
|
37
|
+
<%_ } else { -%>
|
|
38
|
+
healthData.database = 'None';
|
|
39
|
+
<%_ } -%>
|
|
40
|
+
|
|
41
|
+
res.status(HTTP_STATUS.OK).json(healthData);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
module.exports = router;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Router, Request, Response } from 'express';
|
|
2
|
+
import logger from '<% if (architecture === "MVC") { %>@/utils/logger<% } else { %>@/infrastructure/log/logger<% } %>';
|
|
3
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
router.get('/', async (req: Request, res: Response) => {
|
|
8
|
+
const healthData: Record<string, unknown> = {
|
|
9
|
+
status: 'UP',
|
|
10
|
+
uptime: process.uptime(),
|
|
11
|
+
memory: process.memoryUsage(),
|
|
12
|
+
database: 'disconnected',
|
|
13
|
+
timestamp: Date.now()
|
|
14
|
+
};
|
|
15
|
+
logger.info('Health Check');
|
|
16
|
+
|
|
17
|
+
<%_ if (database !== 'None') { -%>
|
|
18
|
+
try {
|
|
19
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
20
|
+
const mongoose = (await import('mongoose')).default;
|
|
21
|
+
if (mongoose.connection.readyState === 1) {
|
|
22
|
+
await mongoose.connection.db?.admin().ping();
|
|
23
|
+
healthData.database = 'connected';
|
|
24
|
+
}
|
|
25
|
+
<%_ } else { -%>
|
|
26
|
+
const sequelize = (await import('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>')).default;
|
|
27
|
+
await sequelize.authenticate();
|
|
28
|
+
healthData.database = 'connected';
|
|
29
|
+
<%_ } -%>
|
|
30
|
+
} catch (err) {
|
|
31
|
+
healthData.database = 'error';
|
|
32
|
+
healthData.status = 'DOWN';
|
|
33
|
+
logger.error('Health Check Database Ping Failed:', err);
|
|
34
|
+
return res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json(healthData);
|
|
35
|
+
}
|
|
36
|
+
<%_ } else { -%>
|
|
37
|
+
healthData.database = 'None';
|
|
38
|
+
<%_ } -%>
|
|
39
|
+
|
|
40
|
+
res.status(HTTP_STATUS.OK).json(healthData);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export default router;
|
|
@@ -27,4 +27,9 @@ const sendMessage = async (topic, message) => {
|
|
|
27
27
|
});
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
const disconnectKafka = async () => {
|
|
31
|
+
await producer.disconnect();
|
|
32
|
+
await consumer.disconnect();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
module.exports = { connectKafka, sendMessage, disconnectKafka };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<%_
|
|
2
|
+
let loggerPath = './logger';
|
|
3
|
+
let dbPath = '../config/database';
|
|
4
|
+
let redisPath = '../config/redisClient';
|
|
5
|
+
let kafkaPath = '../services/kafkaService';
|
|
6
|
+
|
|
7
|
+
if (architecture === 'Clean Architecture') {
|
|
8
|
+
loggerPath = '../infrastructure/log/logger';
|
|
9
|
+
dbPath = '../infrastructure/database/database';
|
|
10
|
+
redisPath = '../infrastructure/caching/redisClient';
|
|
11
|
+
kafkaPath = '../infrastructure/messaging/kafkaClient';
|
|
12
|
+
}
|
|
13
|
+
_%>
|
|
14
|
+
const logger = require('<%- loggerPath %>');
|
|
15
|
+
|
|
16
|
+
const setupGracefulShutdown = (server) => {
|
|
17
|
+
const gracefulShutdown = async (signal) => {
|
|
18
|
+
logger.info(`Received ${signal}. Shutting down gracefully...`);
|
|
19
|
+
server.close(async () => {
|
|
20
|
+
logger.info('HTTP server closed.');
|
|
21
|
+
try {
|
|
22
|
+
<%_ if (database !== 'None') { -%>
|
|
23
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
24
|
+
const mongoose = require('mongoose');
|
|
25
|
+
await mongoose.connection.close(false);
|
|
26
|
+
logger.info('MongoDB connection closed.');
|
|
27
|
+
<%_ } else { -%>
|
|
28
|
+
const sequelize = require('<%- dbPath %>');
|
|
29
|
+
await sequelize.close();
|
|
30
|
+
logger.info('Database connection closed.');
|
|
31
|
+
<%_ } -%>
|
|
32
|
+
<%_ } -%>
|
|
33
|
+
<%_ if (caching === 'Redis') { -%>
|
|
34
|
+
const redisService = require('<%- redisPath %>');
|
|
35
|
+
await redisService.quit();
|
|
36
|
+
logger.info('Redis connection closed.');
|
|
37
|
+
<%_ } -%>
|
|
38
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
39
|
+
const { disconnectKafka } = require('<%- kafkaPath %>');
|
|
40
|
+
await disconnectKafka();
|
|
41
|
+
logger.info('Kafka connection closed.');
|
|
42
|
+
<%_ } -%>
|
|
43
|
+
logger.info('Graceful shutdown fully completed.');
|
|
44
|
+
process.exit(0);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
logger.error('Error during shutdown:', err);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
logger.error('Could not close connections in time, forcefully shutting down');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}, 15000);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
58
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
module.exports = setupGracefulShutdown;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Server } from 'http';
|
|
2
|
+
<%_ if (architecture === 'MVC') { -%>
|
|
3
|
+
import logger from '@/utils/logger';
|
|
4
|
+
<%_ } else { -%>
|
|
5
|
+
import logger from '@/infrastructure/log/logger';
|
|
6
|
+
<%_ } -%>
|
|
7
|
+
|
|
8
|
+
export const setupGracefulShutdown = (server: Server<% if (communication === 'Kafka') { %>, kafkaService: { disconnect: () => Promise<void> }<% } %>) => {
|
|
9
|
+
const gracefulShutdown = async (signal: string) => {
|
|
10
|
+
logger.info(`Received ${signal}. Shutting down gracefully...`);
|
|
11
|
+
server.close(async () => {
|
|
12
|
+
logger.info('HTTP server closed.');
|
|
13
|
+
try {
|
|
14
|
+
<%_ if (database !== 'None') { -%>
|
|
15
|
+
<%_ if (database === 'MongoDB') { -%>
|
|
16
|
+
const mongoose = (await import('mongoose')).default;
|
|
17
|
+
await mongoose.connection.close(false);
|
|
18
|
+
logger.info('MongoDB connection closed.');
|
|
19
|
+
<%_ } else { -%>
|
|
20
|
+
<%_ if (architecture === 'MVC') { -%>
|
|
21
|
+
const sequelize = (await import('@/config/database')).default;
|
|
22
|
+
<%_ } else { -%>
|
|
23
|
+
const sequelize = (await import('@/infrastructure/database/database')).default;
|
|
24
|
+
<%_ } -%>
|
|
25
|
+
await sequelize.close();
|
|
26
|
+
logger.info('Database connection closed.');
|
|
27
|
+
<%_ } -%>
|
|
28
|
+
<%_ } -%>
|
|
29
|
+
<%_ if (caching === 'Redis') { -%>
|
|
30
|
+
<%_ if (architecture === 'MVC') { -%>
|
|
31
|
+
const redisService = (await import('@/config/redisClient')).default;
|
|
32
|
+
<%_ } else { -%>
|
|
33
|
+
const redisService = (await import('@/infrastructure/caching/redisClient')).default;
|
|
34
|
+
<%_ } -%>
|
|
35
|
+
await redisService.quit();
|
|
36
|
+
logger.info('Redis connection closed.');
|
|
37
|
+
<%_ } -%>
|
|
38
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
39
|
+
await kafkaService.disconnect();
|
|
40
|
+
logger.info('Kafka connection closed.');
|
|
41
|
+
<%_ } -%>
|
|
42
|
+
logger.info('Graceful shutdown fully completed.');
|
|
43
|
+
process.exit(0);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
logger.error('Error during shutdown:', err);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
logger.error('Could not close connections in time, forcefully shutting down');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}, 15000);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
57
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
58
|
+
};
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
<% if (language === 'TypeScript') { %>import request from 'supertest';
|
|
2
|
-
import express from 'express'
|
|
3
|
-
const
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';<% } else { %>const request = require('supertest');
|
|
4
|
+
const express = require('express');
|
|
5
|
+
const HTTP_STATUS = require('../src/utils/httpCodes');<% } %>
|
|
4
6
|
|
|
5
7
|
const app = express();
|
|
6
|
-
app.get('/health', (req, res) => res.json({
|
|
8
|
+
app.get('/health', (req, res) => res.status(HTTP_STATUS.OK).json({
|
|
9
|
+
status: 'UP',
|
|
10
|
+
uptime: 120,
|
|
11
|
+
memory: { rss: 1024, heapTotal: 512, heapUsed: 256, external: 128 },
|
|
12
|
+
database: 'connected'
|
|
13
|
+
}));
|
|
7
14
|
|
|
8
15
|
describe('Health Check', () => {
|
|
9
|
-
it('should return 200 OK', async () => {
|
|
16
|
+
it('should return 200 OK with detailed metrics', async () => {
|
|
10
17
|
const res = await request(app).get('/health');
|
|
11
|
-
expect(res.status).toBe(
|
|
12
|
-
expect(res.body).
|
|
18
|
+
expect(res.status).toBe(HTTP_STATUS.OK);
|
|
19
|
+
expect(res.body).toHaveProperty('status', 'UP');
|
|
20
|
+
expect(res.body).toHaveProperty('uptime');
|
|
21
|
+
expect(res.body).toHaveProperty('memory');
|
|
22
|
+
expect(res.body).toHaveProperty('database');
|
|
13
23
|
});
|
|
14
24
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const cors = require('cors');
|
|
3
3
|
<%_ if (communication === 'REST APIs') { -%>const apiRoutes = require('./routes/api');<%_ } -%>
|
|
4
|
+
const healthRoutes = require('./routes/healthRoute');
|
|
4
5
|
<%_ if (communication === 'Kafka') { -%>const { connectKafka, sendMessage } = require('./services/kafkaService');<%_ } -%>
|
|
5
6
|
<%_ if (communication === 'GraphQL') { -%>
|
|
6
7
|
const { ApolloServer } = require('@apollo/server');
|
|
@@ -21,7 +22,7 @@ const app = express();
|
|
|
21
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') { -%>
|
|
@@ -6,7 +6,9 @@ import hpp from 'hpp';
|
|
|
6
6
|
import rateLimit from 'express-rate-limit';
|
|
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') { %>
|
|
@@ -70,15 +72,13 @@ app.get('/', (req: Request, res: Response) => {
|
|
|
70
72
|
});
|
|
71
73
|
});
|
|
72
74
|
<% } -%>
|
|
73
|
-
app.
|
|
74
|
-
res.json({ status: 'UP' });
|
|
75
|
-
});
|
|
75
|
+
app.use('/health', healthRoutes);
|
|
76
76
|
|
|
77
77
|
// Start Server Logic
|
|
78
78
|
const startServer = async () => {
|
|
79
79
|
<%_ if (communication === 'GraphQL') { -%>
|
|
80
80
|
// GraphQL Setup
|
|
81
|
-
const
|
|
81
|
+
const apolloServer = new ApolloServer<MyContext>({
|
|
82
82
|
typeDefs,
|
|
83
83
|
resolvers,
|
|
84
84
|
plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
|
|
@@ -102,14 +102,16 @@ const startServer = async () => {
|
|
|
102
102
|
return formattedError;
|
|
103
103
|
},
|
|
104
104
|
});
|
|
105
|
-
await
|
|
106
|
-
app.use('/graphql', expressMiddleware(
|
|
105
|
+
await apolloServer.start();
|
|
106
|
+
app.use('/graphql', expressMiddleware(apolloServer, { context: gqlContext }));
|
|
107
107
|
<%_ } -%>
|
|
108
108
|
app.use(errorMiddleware);
|
|
109
|
-
|
|
109
|
+
<%_ if (communication === 'Kafka') { -%>
|
|
110
|
+
const kafkaService = new KafkaService();
|
|
111
|
+
<%_ } -%>
|
|
112
|
+
const server = app.listen(port, () => {
|
|
110
113
|
logger.info(`Server running on port ${port}`);
|
|
111
114
|
<%_ if (communication === 'Kafka') { -%>
|
|
112
|
-
const kafkaService = new KafkaService();
|
|
113
115
|
kafkaService.connect().then(() => {
|
|
114
116
|
logger.info('Kafka connected');
|
|
115
117
|
kafkaService.sendMessage('test-topic', 'Hello Kafka from MVC TS!');
|
|
@@ -118,6 +120,8 @@ const startServer = async () => {
|
|
|
118
120
|
});
|
|
119
121
|
<%_ } -%>
|
|
120
122
|
});
|
|
123
|
+
|
|
124
|
+
setupGracefulShutdown(server<% if(communication === 'Kafka') { %>, kafkaService<% } %>);
|
|
121
125
|
};
|
|
122
126
|
|
|
123
127
|
<%_ if (database !== 'None') { -%>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|