nodejs-quickstart-structure 1.10.1 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +4 -0
- package/docs/generateCase.md +245 -165
- package/docs/generatorFlow.md +65 -23
- package/lib/generator.js +4 -1
- package/lib/modules/app-setup.js +56 -5
- package/package.json +1 -1
- package/templates/clean-architecture/js/src/errors/ApiError.js +14 -0
- package/templates/clean-architecture/js/src/errors/BadRequestError.js +10 -0
- package/templates/clean-architecture/js/src/errors/NotFoundError.js +10 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/middlewares/error.middleware.js +29 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +24 -0
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +4 -7
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +2 -11
- package/templates/clean-architecture/js/src/interfaces/routes/api.js +2 -2
- package/templates/clean-architecture/ts/src/errors/ApiError.ts +15 -0
- package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +8 -0
- package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +8 -0
- package/templates/clean-architecture/ts/src/index.ts.ejs +23 -0
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +13 -19
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +4 -13
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +4 -3
- package/templates/clean-architecture/ts/src/utils/error.middleware.ts.ejs +27 -0
- package/templates/common/package.json.ejs +5 -6
- package/templates/mvc/js/src/controllers/userController.js.ejs +5 -4
- package/templates/mvc/js/src/errors/ApiError.js +14 -0
- package/templates/mvc/js/src/errors/BadRequestError.js +10 -0
- package/templates/mvc/js/src/errors/NotFoundError.js +10 -0
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +2 -11
- package/templates/mvc/js/src/index.js.ejs +23 -0
- package/templates/mvc/js/src/utils/error.middleware.js +28 -0
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +6 -14
- package/templates/mvc/ts/src/errors/ApiError.ts +15 -0
- package/templates/mvc/ts/src/errors/BadRequestError.ts +8 -0
- package/templates/mvc/ts/src/errors/NotFoundError.ts +8 -0
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +4 -13
- package/templates/mvc/ts/src/index.ts.ejs +23 -0
- package/templates/mvc/ts/src/routes/api.ts +3 -3
- package/templates/mvc/ts/src/utils/error.middleware.ts.ejs +27 -0
- /package/templates/clean-architecture/js/src/infrastructure/webserver/{swagger.js → swagger.js.ejs} +0 -0
- /package/templates/clean-architecture/ts/src/infrastructure/repositories/{userRepository.ts.ejs → UserRepository.ts.ejs} +0 -0
- /package/templates/mvc/js/src/config/{swagger.js → swagger.js.ejs} +0 -0
package/docs/generatorFlow.md
CHANGED
|
@@ -25,7 +25,7 @@ The generator prompts the user for the following configurations. These determine
|
|
|
25
25
|
| **View Engine** | `None`, `EJS`, `Pug` | `None` | (MVC Only) Template engine for server-side rendering. |
|
|
26
26
|
| **Database** | `None`, `MySQL`, `PostgreSQL`, `MongoDB` | `None` | The primary database. |
|
|
27
27
|
| **Database Name** | Input String | `demo` | The name of the database to use/create. |
|
|
28
|
-
| **Communication**| `REST APIs`, `Kafka` | `REST APIs` | The primary communication method. |
|
|
28
|
+
| **Communication**| `REST APIs`, `GraphQL`, `Kafka` | `REST APIs` | The primary communication method. |
|
|
29
29
|
| **Caching Layer**| `None`, `Redis`, `Memory Cache` | `None` | (If DB selected) Caching solution. |
|
|
30
30
|
| **CI/CD Provider**| `None`, `GitHub Actions`, `Jenkins`| `None` | Setup for Continuous Integration/Deployment. |
|
|
31
31
|
|
|
@@ -47,13 +47,23 @@ The `generateProject` function in `lib/generator.js` executes the following step
|
|
|
47
47
|
5. **Render `README.md`**:
|
|
48
48
|
* Generates custom documentation specific to the selected stack.
|
|
49
49
|
6. **Render `src/index.{js|ts}`**:
|
|
50
|
-
* Processes the entry point file to wire up the selected DB and
|
|
51
|
-
|
|
52
|
-
* **
|
|
50
|
+
* Processes the entry point file to wire up the selected DB, Architecture, and Communication type.
|
|
51
|
+
* **GraphQL**: Wires up Apollo Server middleware with `formatError` hook for centralized error handling.
|
|
52
|
+
* **REST APIs / Kafka**: Registers `app.use(errorMiddleware)` at the end of the Express chain.
|
|
53
|
+
7. **Render Error Middleware** (`renderErrorMiddleware`):
|
|
54
|
+
* Processes `error.middleware.{ts|js}.ejs` template from the target directory's `src/utils/` path.
|
|
55
|
+
* Renders to `src/utils/error.middleware.{ts|js}` in the generated project.
|
|
56
|
+
* **Clean Architecture**: Also handles `src/infrastructure/webserver/middlewares/error.middleware.{js}` path.
|
|
57
|
+
8. **Dynamic Component Generation**:
|
|
58
|
+
* **MVC**: Generates `userController` (imports specific DB service, uses `next(error)`).
|
|
53
59
|
* **Clean Architecture**: Generates `UserRepository` (infrastructure layer implementation).
|
|
54
60
|
* **Clean Architecture (JS only)**: Generates `server.js` (webserver setup).
|
|
55
|
-
|
|
56
|
-
*
|
|
61
|
+
9. **Communication Setup**:
|
|
62
|
+
* **GraphQL**:
|
|
63
|
+
* Generates Apollo Server v4 schema (`typeDefs`) and resolvers.
|
|
64
|
+
* Configures `formatError` hook with `unwrapResolverError` for structured error mapping.
|
|
65
|
+
* Automatically embeds Apollo Sandbox with local CSP headers (no CDN dependency).
|
|
66
|
+
* **Kafka**:
|
|
57
67
|
* Copies Kafka client/service templates.
|
|
58
68
|
* **Clean Architecture Restructuring**:
|
|
59
69
|
* Moves service to `src/infrastructure/messaging`.
|
|
@@ -61,14 +71,14 @@ The `generateProject` function in `lib/generator.js` executes the following step
|
|
|
61
71
|
* Removes REST-specific folders (`interfaces/routes`, `interfaces/controllers`).
|
|
62
72
|
* **MVC Cleanup**:
|
|
63
73
|
* If no View Engine is selected, removes `src/controllers` and `src/routes` (assumes pure worker).
|
|
64
|
-
|
|
74
|
+
10. **Common Configuration**:
|
|
65
75
|
* Copies `.gitignore`, `.dockerignore`, `Dockerfile`.
|
|
66
76
|
* Copies `tsconfig.json` (if TypeScript).
|
|
67
|
-
|
|
77
|
+
11. **Database Setup**:
|
|
68
78
|
* **MongoDB**: Sets up `migrate-mongo-config.js` and initial migration script.
|
|
69
79
|
* **SQL (MySQL/Postgres)**: Sets up `flyway/sql` directory and copies initial SQL migration files.
|
|
70
80
|
* **None**: Skips migration setup.
|
|
71
|
-
|
|
81
|
+
12. **Caching Setup**:
|
|
72
82
|
* **Redis**:
|
|
73
83
|
* Injects `ioredis` dependency into `package.json`.
|
|
74
84
|
* Generates `redisClient.{js|ts}` config.
|
|
@@ -78,22 +88,23 @@ The `generateProject` function in `lib/generator.js` executes the following step
|
|
|
78
88
|
* Injects `node-cache` dependency.
|
|
79
89
|
* Generates `memoryCache.{js|ts}` config.
|
|
80
90
|
* **MVC/Clean**: Consumes the generic abstraction injected above.
|
|
81
|
-
|
|
91
|
+
13. **Database Connection Config**:
|
|
82
92
|
* Renders `database.{js|ts}` or `mongoose.{js|ts}` based on DB selection.
|
|
83
93
|
* Places it in `src/config` (MVC) or `src/infrastructure/database` (Clean Arch).
|
|
84
94
|
* **None**: Skips this step.
|
|
85
|
-
|
|
95
|
+
14. **Model Generation**:
|
|
86
96
|
* Renders `User` model (Mongoose schema or Sequelize/TypeORM model) in the appropriate directory.
|
|
87
97
|
* **None**: Generates a simple Mock Entity/Model class with in-memory data for testing.
|
|
88
|
-
|
|
98
|
+
15. **View Engine Setup (MVC)**:
|
|
89
99
|
* If selected, copies views (`views/ejs` or `views/pug`) and `public` assets.
|
|
90
|
-
|
|
91
|
-
* If **REST APIs** is selected, generates
|
|
92
|
-
|
|
100
|
+
16. **Swagger Config** (`renderSwaggerConfig`):
|
|
101
|
+
* If **REST APIs** is selected, generates `swagger.yml` and `swagger.{ts|js}` config.
|
|
102
|
+
* Cleans up `.ejs` template copies for non-REST configs (GraphQL, Kafka).
|
|
103
|
+
17. **Code Quality Setup**:
|
|
93
104
|
* Generates `.eslintrc.json`, `.prettierrc`, `.lintstagedrc`.
|
|
94
|
-
|
|
105
|
+
18. **Test Setup**:
|
|
95
106
|
* Generates `jest.config.js` and a sample `health.test.{js|ts}`.
|
|
96
|
-
|
|
107
|
+
19. **CI/CD Setup**:
|
|
97
108
|
* Helper: `setupCiCd`
|
|
98
109
|
* Checks `config.ciProvider`:
|
|
99
110
|
* **GitHub Actions**: Copies `.github/workflows/ci.yml`.
|
|
@@ -125,16 +136,41 @@ Standard architecture for web APIs.
|
|
|
125
136
|
project-name/
|
|
126
137
|
├── src/
|
|
127
138
|
│ ├── config/ # Database, Redis, Swagger, etc.
|
|
128
|
-
│ ├── controllers/ # Request handlers
|
|
139
|
+
│ ├── controllers/ # Request handlers (use next(error))
|
|
140
|
+
│ ├── errors/ # ApiError, NotFoundError, BadRequestError
|
|
129
141
|
│ ├── models/ # Database models
|
|
130
142
|
│ ├── routes/ # Express routes
|
|
131
|
-
│
|
|
143
|
+
│ ├── utils/
|
|
144
|
+
│ │ ├── error.middleware.{ts|js} # Global error handler
|
|
145
|
+
│ │ ├── logger.{ts|js}
|
|
146
|
+
│ │ └── httpCodes.{ts|js}
|
|
147
|
+
│ └── index.js|ts # Entry point (registers errorMiddleware last)
|
|
132
148
|
├── tests/ # Jest tests
|
|
133
149
|
├── package.json
|
|
134
150
|
├── Dockerfile
|
|
135
151
|
└── docker-compose.yml
|
|
136
152
|
```
|
|
137
153
|
|
|
154
|
+
### Case A2: MVC (GraphQL)
|
|
155
|
+
Apollo Server v4 mounted as Express middleware.
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
project-name/
|
|
159
|
+
├── src/
|
|
160
|
+
│ ├── config/ # Database, Redis config
|
|
161
|
+
│ ├── controllers/ # GraphQL resolver backing logic (throws errors)
|
|
162
|
+
│ ├── errors/ # ApiError, NotFoundError, BadRequestError
|
|
163
|
+
│ ├── graphql/
|
|
164
|
+
│ │ ├── schema/ # typeDefs
|
|
165
|
+
│ │ └── resolvers/ # user.resolvers (calls controllers, throws errors)
|
|
166
|
+
│ ├── models/
|
|
167
|
+
│ ├── utils/
|
|
168
|
+
│ │ ├── error.middleware.{ts|js} # Express-level fallback error handler
|
|
169
|
+
│ │ └── logger.{ts|js}
|
|
170
|
+
│ └── index.js|ts # Apollo Server + formatError hook
|
|
171
|
+
└── ...
|
|
172
|
+
```
|
|
173
|
+
|
|
138
174
|
### Case B: MVC (Web App with Views)
|
|
139
175
|
Includes frontend views rendered on the server.
|
|
140
176
|
|
|
@@ -158,17 +194,23 @@ Separation of concerns with Domain, Use Cases, and Infrastructure.
|
|
|
158
194
|
project-name/
|
|
159
195
|
├── src/
|
|
160
196
|
│ ├── domain/ # Entities (Enterprise rules)
|
|
197
|
+
│ ├── errors/ # ApiError, NotFoundError, BadRequestError
|
|
161
198
|
│ ├── use_cases/ # Application business rules
|
|
162
199
|
│ ├── interfaces/ # Adapters
|
|
163
|
-
│ │ ├── controllers/
|
|
164
|
-
│ │ └── routes/
|
|
200
|
+
│ │ ├── controllers/ # Use next(error) for error propagation
|
|
201
|
+
│ │ └── routes/ # Pass NextFunction through handlers
|
|
165
202
|
│ ├── infrastructure/ # Frameworks & Drivers
|
|
166
203
|
│ │ ├── config/ # Environment config
|
|
167
204
|
│ │ ├── caching/ # Redis Client
|
|
168
205
|
│ │ ├── database/ # DB connection & models
|
|
169
206
|
│ │ ├── repositories/ # Data access implementation
|
|
170
|
-
│ │ └── webserver/
|
|
171
|
-
│
|
|
207
|
+
│ │ └── webserver/
|
|
208
|
+
│ │ ├── middlewares/
|
|
209
|
+
│ │ │ └── error.middleware.js # JS only — Express error handler
|
|
210
|
+
│ │ └── server.js # Express app setup
|
|
211
|
+
│ ├── utils/
|
|
212
|
+
│ │ └── error.middleware.ts # TS — global error handler
|
|
213
|
+
│ └── index.js|ts # Registers errorMiddleware after Apollo/Express
|
|
172
214
|
└── ...
|
|
173
215
|
```
|
|
174
216
|
|
package/lib/generator.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { setupProjectDirectory, copyBaseStructure, copyCommonFiles } from './modules/project-setup.js';
|
|
4
4
|
import { renderPackageJson, renderDockerCompose, renderReadme, renderDockerfile, renderProfessionalConfig, setupCiCd, renderTestSample, renderEnvExample } from './modules/config-files.js';
|
|
5
|
-
import { renderIndexFile, renderDynamicComponents, renderSwaggerConfig, setupViews as setupSrcViews } from './modules/app-setup.js';
|
|
5
|
+
import { renderIndexFile, renderErrorMiddleware, renderDynamicComponents, renderSwaggerConfig, setupViews as setupSrcViews } from './modules/app-setup.js';
|
|
6
6
|
import { setupDatabase } from './modules/database-setup.js';
|
|
7
7
|
import { setupKafka, setupViews } from './modules/kafka-setup.js';
|
|
8
8
|
import { setupCaching } from './modules/caching-setup.js';
|
|
@@ -33,6 +33,9 @@ export const generateProject = async (config) => {
|
|
|
33
33
|
// 6. Render index file (ts/js)
|
|
34
34
|
await renderIndexFile(templatePath, targetDir, config);
|
|
35
35
|
|
|
36
|
+
// 6a. Render error middleware
|
|
37
|
+
await renderErrorMiddleware(templatePath, targetDir, config);
|
|
38
|
+
|
|
36
39
|
// 7. Render Dynamic Components (Controllers/Repos/Server)
|
|
37
40
|
await renderDynamicComponents(templatePath, targetDir, config);
|
|
38
41
|
|
package/lib/modules/app-setup.js
CHANGED
|
@@ -22,6 +22,40 @@ export const renderIndexFile = async (templatePath, targetDir, config) => {
|
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
export const renderErrorMiddleware = async (templatePath, targetDir, config) => {
|
|
26
|
+
const { language, architecture } = config;
|
|
27
|
+
const errName = language === 'TypeScript' ? 'error.middleware.ts' : 'error.middleware.js';
|
|
28
|
+
const errTemplateName = `${errName}.ejs`;
|
|
29
|
+
|
|
30
|
+
if (architecture === 'MVC') {
|
|
31
|
+
// MVC: render from target's src/utils/ (the .ejs copy put there by copyBaseStructure)
|
|
32
|
+
const ejsCopy = path.join(targetDir, 'src/utils', errTemplateName);
|
|
33
|
+
const dest = path.join(targetDir, 'src/utils', errName);
|
|
34
|
+
if (await fs.pathExists(ejsCopy)) {
|
|
35
|
+
await fs.writeFile(dest, await fs.readFile(ejsCopy, 'utf-8'));
|
|
36
|
+
await fs.remove(ejsCopy);
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
// Clean Architecture: render from target's src/utils/ (the .ejs copy put there by copyBaseStructure)
|
|
40
|
+
const utilsEjsCopy = path.join(targetDir, 'src/utils', errTemplateName);
|
|
41
|
+
const utilsDest = path.join(targetDir, 'src/utils', errName);
|
|
42
|
+
await fs.ensureDir(path.join(targetDir, 'src/utils'));
|
|
43
|
+
if (await fs.pathExists(utilsEjsCopy)) {
|
|
44
|
+
await fs.writeFile(utilsDest, await fs.readFile(utilsEjsCopy, 'utf-8'));
|
|
45
|
+
await fs.remove(utilsEjsCopy);
|
|
46
|
+
}
|
|
47
|
+
// Also render the middlewares version if present (NOT removing from template)
|
|
48
|
+
const mwDir = path.join(targetDir, 'src/infrastructure/webserver/middlewares');
|
|
49
|
+
const mwEjsCopy = path.join(mwDir, errTemplateName);
|
|
50
|
+
const mwDest = path.join(mwDir, errName);
|
|
51
|
+
if (await fs.pathExists(mwEjsCopy)) {
|
|
52
|
+
await fs.ensureDir(mwDir);
|
|
53
|
+
await fs.writeFile(mwDest, await fs.readFile(mwEjsCopy, 'utf-8'));
|
|
54
|
+
await fs.remove(mwEjsCopy);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
25
59
|
export const renderDynamicComponents = async (templatePath, targetDir, config) => {
|
|
26
60
|
const { architecture, language, database, caching } = config;
|
|
27
61
|
|
|
@@ -127,6 +161,10 @@ export const renderSwaggerConfig = async (templatesDir, targetDir, config) => {
|
|
|
127
161
|
|
|
128
162
|
// Check for Swagger config template (typically in src/config/swagger.ts.ejs)
|
|
129
163
|
const swaggerTsTemplate = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
|
|
164
|
+
// MVC JS uses swagger.js.ejs in src/config/
|
|
165
|
+
const swaggerJsTemplate = path.join(targetDir, 'src', 'config', 'swagger.js.ejs');
|
|
166
|
+
// Clean Arch JS uses swagger.js.ejs in src/infrastructure/webserver/
|
|
167
|
+
const swaggerJsCleanTemplate = path.join(targetDir, 'src', 'infrastructure', 'webserver', 'swagger.js.ejs');
|
|
130
168
|
|
|
131
169
|
// Ensure config directory exists
|
|
132
170
|
let configDir = path.join(targetDir, 'src', 'config');
|
|
@@ -146,13 +184,26 @@ export const renderSwaggerConfig = async (templatesDir, targetDir, config) => {
|
|
|
146
184
|
const content = ejs.render(await fs.readFile(swaggerTsTemplate, 'utf-8'), { communication });
|
|
147
185
|
await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
|
|
148
186
|
}
|
|
187
|
+
|
|
188
|
+
// MVC JS: render swagger.js.ejs → swagger.js
|
|
189
|
+
if (await fs.pathExists(swaggerJsTemplate)) {
|
|
190
|
+
const content = await fs.readFile(swaggerJsTemplate, 'utf-8');
|
|
191
|
+
await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.js'), content);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Clean Arch JS: render swagger.js.ejs → swagger.js in webserver/
|
|
195
|
+
if (await fs.pathExists(swaggerJsCleanTemplate)) {
|
|
196
|
+
const content = await fs.readFile(swaggerJsCleanTemplate, 'utf-8');
|
|
197
|
+
await fs.writeFile(path.join(targetDir, 'src', 'infrastructure', 'webserver', 'swagger.js'), content);
|
|
198
|
+
}
|
|
149
199
|
}
|
|
150
200
|
|
|
151
|
-
// Always remove the template after processing or if
|
|
152
|
-
if (await fs.pathExists(swaggerTsTemplate))
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
201
|
+
// Always remove the .ejs template after processing (or if non-REST, just delete it)
|
|
202
|
+
if (await fs.pathExists(swaggerTsTemplate)) await fs.remove(swaggerTsTemplate);
|
|
203
|
+
if (await fs.pathExists(swaggerJsTemplate)) await fs.remove(swaggerJsTemplate);
|
|
204
|
+
if (await fs.pathExists(swaggerJsCleanTemplate)) await fs.remove(swaggerJsCleanTemplate);
|
|
205
|
+
|
|
206
|
+
// Also cleanup yml template if not REST APIs since copyBaseStructure copies it earlier
|
|
156
207
|
const swaggerYmlDestPath = path.join(targetDir, 'src', 'config', 'swagger.yml.ejs');
|
|
157
208
|
if (await fs.pathExists(swaggerYmlDestPath)) {
|
|
158
209
|
await fs.remove(swaggerYmlDestPath);
|
package/package.json
CHANGED
|
@@ -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,10 @@
|
|
|
1
|
+
const { ApiError } = require('./ApiError');
|
|
2
|
+
const { HTTP_STATUS } = require('../utils/httpCodes');
|
|
3
|
+
|
|
4
|
+
class BadRequestError extends ApiError {
|
|
5
|
+
constructor(message = 'Bad request') {
|
|
6
|
+
super(HTTP_STATUS.BAD_REQUEST, message);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = { BadRequestError };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { ApiError } = require('./ApiError');
|
|
2
|
+
const { HTTP_STATUS } = require('../utils/httpCodes');
|
|
3
|
+
|
|
4
|
+
class NotFoundError extends ApiError {
|
|
5
|
+
constructor(message = 'Resource not found') {
|
|
6
|
+
super(HTTP_STATUS.NOT_FOUND, message);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = { NotFoundError };
|
package/templates/clean-architecture/js/src/infrastructure/webserver/middlewares/error.middleware.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const logger = require('../../log/logger');
|
|
2
|
+
const { ApiError } = require('../../../errors/ApiError');
|
|
3
|
+
const HTTP_STATUS = require('../../../utils/httpCodes');
|
|
4
|
+
|
|
5
|
+
const errorMiddleware = (err, req, res, _) => {
|
|
6
|
+
let error = err;
|
|
7
|
+
|
|
8
|
+
if (!(error instanceof ApiError)) {
|
|
9
|
+
const statusCode = err.statusCode || HTTP_STATUS.INTERNAL_SERVER_ERROR;
|
|
10
|
+
const message = error.message || 'Internal Server Error';
|
|
11
|
+
error = new ApiError(statusCode, message, false, err.stack);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { statusCode, message } = error;
|
|
15
|
+
|
|
16
|
+
if (statusCode === HTTP_STATUS.INTERNAL_SERVER_ERROR) {
|
|
17
|
+
logger.error(`${statusCode} - ${message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
|
|
18
|
+
logger.error(error.stack || 'No stack trace');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
res.status(statusCode).json({
|
|
22
|
+
statusCode,
|
|
23
|
+
message,
|
|
24
|
+
...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = { errorMiddleware };
|
|
29
|
+
|
|
@@ -3,6 +3,7 @@ const cors = require('cors');
|
|
|
3
3
|
require('dotenv').config();
|
|
4
4
|
const logger = require('../log/logger');
|
|
5
5
|
const morgan = require('morgan');
|
|
6
|
+
const { errorMiddleware } = require('./middlewares/error.middleware');
|
|
6
7
|
<%_ if (communication === 'REST APIs') { -%>const apiRoutes = require('../../interfaces/routes/api');<%_ } -%>
|
|
7
8
|
<%_ if (communication === 'REST APIs') { -%>
|
|
8
9
|
const swaggerUi = require('swagger-ui-express');
|
|
@@ -12,6 +13,8 @@ const swaggerSpecs = require('./swagger');
|
|
|
12
13
|
const { ApolloServer } = require('@apollo/server');
|
|
13
14
|
const { expressMiddleware } = require('@apollo/server/express4');
|
|
14
15
|
const { ApolloServerPluginLandingPageLocalDefault } = require('@apollo/server/plugin/landingPage/default');
|
|
16
|
+
const { unwrapResolverError } = require('@apollo/server/errors');
|
|
17
|
+
const { ApiError } = require('../../errors/ApiError');
|
|
15
18
|
const { typeDefs, resolvers } = require('../../interfaces/graphql');
|
|
16
19
|
const { gqlContext } = require('../../interfaces/graphql/context');
|
|
17
20
|
<%_ } -%>
|
|
@@ -34,6 +37,25 @@ const startServer = async (port) => {
|
|
|
34
37
|
typeDefs,
|
|
35
38
|
resolvers,
|
|
36
39
|
plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
|
|
40
|
+
formatError: (formattedError, error) => {
|
|
41
|
+
const originalError = unwrapResolverError(error);
|
|
42
|
+
if (originalError instanceof ApiError) {
|
|
43
|
+
return {
|
|
44
|
+
...formattedError,
|
|
45
|
+
message: originalError.message,
|
|
46
|
+
extensions: {
|
|
47
|
+
...formattedError.extensions,
|
|
48
|
+
code: originalError.statusCode.toString(),
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
logger.error(`GraphQL Error: ${formattedError.message}`);
|
|
54
|
+
if (originalError && originalError.stack && process.env.NODE_ENV === 'development') {
|
|
55
|
+
logger.error(originalError.stack);
|
|
56
|
+
}
|
|
57
|
+
return formattedError;
|
|
58
|
+
},
|
|
37
59
|
});
|
|
38
60
|
await server.start();
|
|
39
61
|
app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
|
|
@@ -42,6 +64,8 @@ const startServer = async (port) => {
|
|
|
42
64
|
res.json({ status: 'UP' });
|
|
43
65
|
});
|
|
44
66
|
|
|
67
|
+
app.use(errorMiddleware);
|
|
68
|
+
|
|
45
69
|
app.listen(port, () => {
|
|
46
70
|
logger.info(`Server running on port ${port}`);
|
|
47
71
|
});
|
|
@@ -33,23 +33,20 @@ class UserController {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
<% } else { -%>
|
|
36
|
-
getUsers(req, res) {
|
|
36
|
+
getUsers(req, res, next) {
|
|
37
37
|
this.getAllUsersUseCase.execute()
|
|
38
38
|
.then(users => res.json(users))
|
|
39
|
-
.catch(
|
|
40
|
-
logger.error('Error getting users:', err);
|
|
41
|
-
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: err.message });
|
|
42
|
-
});
|
|
39
|
+
.catch(next);
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
async createUser(req, res) {
|
|
42
|
+
async createUser(req, res, next) {
|
|
46
43
|
const { name, email } = req.body;
|
|
47
44
|
try {
|
|
48
45
|
const user = await this.createUserUseCase.execute(name, email);
|
|
49
46
|
res.status(HTTP_STATUS.CREATED).json(user);
|
|
50
47
|
} catch (error) {
|
|
51
48
|
logger.error('Error creating user:', error);
|
|
52
|
-
|
|
49
|
+
next(error);
|
|
53
50
|
}
|
|
54
51
|
}
|
|
55
52
|
<% } -%>
|
package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { GraphQLError } = require('graphql');
|
|
2
1
|
const UserController = require('../../controllers/userController');
|
|
3
2
|
|
|
4
3
|
const userController = new UserController();
|
|
@@ -6,20 +5,12 @@ const userController = new UserController();
|
|
|
6
5
|
const userResolvers = {
|
|
7
6
|
Query: {
|
|
8
7
|
getAllUsers: async () => {
|
|
9
|
-
|
|
10
|
-
return await userController.getUsers();
|
|
11
|
-
} catch (error) {
|
|
12
|
-
throw new GraphQLError(error.message || 'Internal server error', { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
|
|
13
|
-
}
|
|
8
|
+
return await userController.getUsers();
|
|
14
9
|
}
|
|
15
10
|
},
|
|
16
11
|
Mutation: {
|
|
17
12
|
createUser: async (_, { name, email }) => {
|
|
18
|
-
|
|
19
|
-
return await userController.createUser({ name, email });
|
|
20
|
-
} catch (error) {
|
|
21
|
-
throw new GraphQLError(error.message || 'Internal server error', { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
|
|
22
|
-
}
|
|
13
|
+
return await userController.createUser({ name, email });
|
|
23
14
|
}
|
|
24
15
|
}
|
|
25
16
|
};
|
|
@@ -4,7 +4,7 @@ const UserController = require('../controllers/userController');
|
|
|
4
4
|
|
|
5
5
|
const userController = new UserController();
|
|
6
6
|
|
|
7
|
-
router.post('/users', (req, res) => userController.createUser(req, res));
|
|
8
|
-
router.get('/users', (req, res) => userController.getUsers(req, res));
|
|
7
|
+
router.post('/users', (req, res, next) => userController.createUser(req, res, next));
|
|
8
|
+
router.get('/users', (req, res, next) => userController.getUsers(req, res, next));
|
|
9
9
|
|
|
10
10
|
module.exports = router;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class ApiError extends Error {
|
|
2
|
+
statusCode: number;
|
|
3
|
+
isOperational: boolean;
|
|
4
|
+
|
|
5
|
+
constructor(statusCode: number, message: string, isOperational = true, stack = '') {
|
|
6
|
+
super(message);
|
|
7
|
+
this.statusCode = statusCode;
|
|
8
|
+
this.isOperational = isOperational;
|
|
9
|
+
if (stack) {
|
|
10
|
+
this.stack = stack;
|
|
11
|
+
} else {
|
|
12
|
+
Error.captureStackTrace(this, this.constructor);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -6,6 +6,7 @@ import rateLimit from 'express-rate-limit';
|
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
7
|
import logger from '@/infrastructure/log/logger';
|
|
8
8
|
import morgan from 'morgan';
|
|
9
|
+
import { errorMiddleware } from '@/utils/error.middleware';
|
|
9
10
|
<%_ if (communication === 'REST APIs') { -%>import userRoutes from '@/interfaces/routes/userRoutes';<%_ } -%>
|
|
10
11
|
<% if (communication === 'REST APIs') { -%>
|
|
11
12
|
import swaggerUi from 'swagger-ui-express';
|
|
@@ -15,6 +16,8 @@ import swaggerSpecs from '@/config/swagger';<% } -%>
|
|
|
15
16
|
import { ApolloServer } from '@apollo/server';
|
|
16
17
|
import { expressMiddleware } from '@apollo/server/express4';
|
|
17
18
|
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';
|
|
19
|
+
import { unwrapResolverError } from '@apollo/server/errors';
|
|
20
|
+
import { ApiError } from '@/errors/ApiError';
|
|
18
21
|
import { typeDefs, resolvers } from '@/interfaces/graphql';
|
|
19
22
|
import { gqlContext, MyContext } from '@/interfaces/graphql/context';
|
|
20
23
|
<% } -%>
|
|
@@ -66,10 +69,30 @@ const startServer = async () => {
|
|
|
66
69
|
typeDefs,
|
|
67
70
|
resolvers,
|
|
68
71
|
plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
|
|
72
|
+
formatError: (formattedError, error) => {
|
|
73
|
+
const originalError = unwrapResolverError(error);
|
|
74
|
+
if (originalError instanceof ApiError) {
|
|
75
|
+
return {
|
|
76
|
+
...formattedError,
|
|
77
|
+
message: originalError.message,
|
|
78
|
+
extensions: {
|
|
79
|
+
...formattedError.extensions,
|
|
80
|
+
code: originalError.statusCode.toString(),
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
logger.error(`GraphQL Error: ${formattedError.message}`);
|
|
86
|
+
if (originalError instanceof Error && originalError.stack && process.env.NODE_ENV === 'development') {
|
|
87
|
+
logger.error(originalError.stack);
|
|
88
|
+
}
|
|
89
|
+
return formattedError;
|
|
90
|
+
},
|
|
69
91
|
});
|
|
70
92
|
await server.start();
|
|
71
93
|
app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
|
|
72
94
|
<%_ } -%>
|
|
95
|
+
app.use(errorMiddleware);
|
|
73
96
|
app.listen(port, () => {
|
|
74
97
|
logger.info(`Server running on port ${port}`);
|
|
75
98
|
<%_ if (communication === 'Kafka') { -%>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<% if (communication !== 'GraphQL') { -%>
|
|
2
|
-
import { Request, Response } from 'express';
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
3
|
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
4
4
|
<% } -%>
|
|
5
5
|
import { UserRepository } from '@/infrastructure/repositories/UserRepository';
|
|
@@ -25,7 +25,7 @@ export class UserController {
|
|
|
25
25
|
return user;
|
|
26
26
|
} catch (error: unknown) {
|
|
27
27
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
28
|
-
logger.error('UserController Error:', message);
|
|
28
|
+
logger.error('UserController CreateUser Error:', message);
|
|
29
29
|
throw error;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -36,37 +36,31 @@ export class UserController {
|
|
|
36
36
|
return users;
|
|
37
37
|
} catch (error: unknown) {
|
|
38
38
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
39
|
-
logger.error('UserController Error:', message);
|
|
39
|
+
logger.error('UserController GetUsers Error:', message);
|
|
40
40
|
throw error;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
<% } else { -%>
|
|
44
|
-
async createUser(req: Request, res: Response) {
|
|
44
|
+
async createUser(req: Request, res: Response, next: NextFunction) {
|
|
45
45
|
try {
|
|
46
46
|
const { name, email } = req.body;
|
|
47
47
|
const user = await this.createUserUseCase.execute(name, email);
|
|
48
48
|
res.status(HTTP_STATUS.CREATED).json(user);
|
|
49
|
-
} catch (error) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} else {
|
|
54
|
-
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Unknown error occurred' });
|
|
55
|
-
}
|
|
49
|
+
} catch (error: unknown) {
|
|
50
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
51
|
+
logger.error('UserController CreateUser Error:', message);
|
|
52
|
+
next(error);
|
|
56
53
|
}
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
async getUsers(req: Request, res: Response) {
|
|
56
|
+
async getUsers(req: Request, res: Response, next: NextFunction) {
|
|
60
57
|
try {
|
|
61
58
|
const users = await this.getAllUsersUseCase.execute();
|
|
62
59
|
res.json(users);
|
|
63
|
-
} catch (error) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
} else {
|
|
68
|
-
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Unknown error occurred' });
|
|
69
|
-
}
|
|
60
|
+
} catch (error: unknown) {
|
|
61
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
62
|
+
logger.error('UserController GetUsers Error:', message);
|
|
63
|
+
next(error);
|
|
70
64
|
}
|
|
71
65
|
}
|
|
72
66
|
<% } -%>
|
package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { GraphQLError } from 'graphql';
|
|
2
1
|
import { UserController } from '@/interfaces/controllers/userController';
|
|
3
2
|
|
|
4
3
|
const userController = new UserController();
|
|
@@ -6,22 +5,14 @@ const userController = new UserController();
|
|
|
6
5
|
export const userResolvers = {
|
|
7
6
|
Query: {
|
|
8
7
|
getAllUsers: async () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} catch (error: unknown) {
|
|
12
|
-
const message = error instanceof Error ? error.message : 'Internal server error';
|
|
13
|
-
throw new GraphQLError(message, { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
|
|
14
|
-
}
|
|
8
|
+
const users = await userController.getUsers();
|
|
9
|
+
return users;
|
|
15
10
|
}
|
|
16
11
|
},
|
|
17
12
|
Mutation: {
|
|
18
13
|
createUser: async (_: unknown, { name, email }: { name: string, email: string }) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} catch (error: unknown) {
|
|
22
|
-
const message = error instanceof Error ? error.message : 'Internal server error';
|
|
23
|
-
throw new GraphQLError(message, { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
|
|
24
|
-
}
|
|
14
|
+
const user = await userController.createUser({ name, email });
|
|
15
|
+
return user;
|
|
25
16
|
}
|
|
26
17
|
}
|
|
27
18
|
};
|