nodejs-quickstart-structure 1.10.0 → 1.11.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 +23 -0
- package/README.md +28 -0
- 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/mvc/js/src/config/{swagger.js → swagger.js.ejs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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.11.0] - 2026-03-02
|
|
9
|
+
### Added
|
|
10
|
+
- **Centralized Error Handling Mechanism:** All generated projects now include a standardized, predictable error response structure for both REST APIs and GraphQL communication types.
|
|
11
|
+
- New `src/errors/` directory with custom error classes: `ApiError`, `NotFoundError`, `BadRequestError`.
|
|
12
|
+
- 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/middlewares/` (Clean Architecture).
|
|
13
|
+
- Integrates `winston` logger to automatically log 500-level errors to persistent log files.
|
|
14
|
+
- All controllers updated to pass errors via `next(error)` instead of manually sending responses.
|
|
15
|
+
- **GraphQL:** Apollo Server `formatError` hook configured with `unwrapResolverError` to intercept resolver errors and map `ApiError` instances to structured GraphQL extension codes.
|
|
16
|
+
- **REST APIs:** Express error middleware returns a consistent `{ statusCode, message, stack? }` JSON body.
|
|
17
|
+
- **Error Response Standardization:** All error responses follow the same schema regardless of database, caching, or communication type selected.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- 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.
|
|
21
|
+
- 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.
|
|
22
|
+
- Fixed incorrect relative import paths (`../../../../`) in Clean Architecture JS `error.middleware.js` — changed to correct 3-level relative paths (`../../../`).
|
|
23
|
+
- 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.
|
|
24
|
+
- 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.
|
|
25
|
+
|
|
26
|
+
## [1.10.1] - 2026-03-02
|
|
27
|
+
### Added
|
|
28
|
+
- Roadmap & Upcoming Features. **[View our Public Roadmap on Trello](https://trello.com/b/TPTo8ylF/nodejs-quickstart-structure-product)**
|
|
29
|
+
- Update start app with npx command.
|
|
30
|
+
|
|
8
31
|
## [1.10.0] - 2026-02-27
|
|
9
32
|
### Added
|
|
10
33
|
- **GraphQL Support:** The generator now supports scaffolding GraphQL APIs using Apollo Server (v4) alongside standard REST APIs. This feature includes built-in integrations for both MVC and Clean Architecture designs across TypeScript and JavaScript.
|
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Node.js Quickstart Generator
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/nodejs-quickstart-structure)
|
|
4
|
+
[](https://www.npmjs.com/package/nodejs-quickstart-structure)
|
|
5
|
+
[](https://www.npmjs.com/package/nodejs-quickstart-structure)
|
|
6
|
+
[](https://opensource.org/licenses/ISC)
|
|
7
|
+
|
|
3
8
|
A powerful CLI tool to scaffold production-ready Node.js microservices with built-in best practices, allowing you to choose between **MVC** or **Clean Architecture**, **JavaScript** or **TypeScript**, and your preferred database.
|
|
4
9
|
|
|
5
10
|
[](https://medium.com/@paudang/nodejs-quickstart-generator-93c276d60e0b)
|
|
@@ -14,6 +19,7 @@ A powerful CLI tool to scaffold production-ready Node.js microservices with buil
|
|
|
14
19
|
- **Database Integration**: Pre-configured setup for **MySQL**, **PostgreSQL**, or **MongoDB**.
|
|
15
20
|
- **Communication Flow**: Scaffold APIs using **REST**, **GraphQL** (with Apollo Server), or **Kafka** (event-driven).
|
|
16
21
|
- **Caching Layer**: Choose between **Redis** or built-in **Memory Cache** for data caching.
|
|
22
|
+
- **Centralized Error Handling**: Every project ships with a global error handler, custom error classes (`ApiError`, `NotFoundError`, `BadRequestError`), and structured JSON error responses — consistent across REST & GraphQL.
|
|
17
23
|
- **Dockerized**: Automatically generates `docker-compose.yml` for DB, Kafka, Redis, and Zookeeper.
|
|
18
24
|
- **Database Migrations/Schemas**: Integrated **Flyway** for SQL migrations or **Mongoose** schemas for MongoDB.
|
|
19
25
|
- **Professional Standards**: Generated projects come with highly professional, industry-standard tooling.
|
|
@@ -24,6 +30,7 @@ We don't just generate boilerplate; we generate **production-ready** foundations
|
|
|
24
30
|
|
|
25
31
|
- **🔍 Code Quality**: Pre-configured `Eslint` and `Prettier` for consistent coding standards.
|
|
26
32
|
- **🛡️ Security**: Built-in `Helmet`, `HPP`, `CORS`, and Rate-Limiting middleware.
|
|
33
|
+
- **🚨 Error Handling**: Centralized global error middleware with custom error classes and structured `{ statusCode, message }` JSON responses. GraphQL uses Apollo's `formatError` hook; REST uses Express error middleware. Both integrate with Winston for automatic 500-level logging.
|
|
27
34
|
- **🧪 Testing Strategy**: Integrated `Jest` and `Supertest` setup for Unit and Integration testing.
|
|
28
35
|
- **🔄 CI/CD Integration**: Pre-configured workflows for **GitHub Actions**, **Jenkins**, and **GitLab CI**.
|
|
29
36
|
- **⚓ Git Hooks**: `Husky` and `Lint-Staged` to ensure no bad code is ever committed.
|
|
@@ -56,6 +63,15 @@ Once installed, simply run the following command in any directory where you want
|
|
|
56
63
|
```bash
|
|
57
64
|
nodejs-quickstart init
|
|
58
65
|
```
|
|
66
|
+
or
|
|
67
|
+
|
|
68
|
+
## Quick Start (Recommended)
|
|
69
|
+
|
|
70
|
+
You can run the generator directly without installing it globally:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx nodejs-quickstart-structure init
|
|
74
|
+
```
|
|
59
75
|
|
|
60
76
|
### Configuration Options
|
|
61
77
|
|
|
@@ -75,6 +91,8 @@ The CLI will guide you through the following steps:
|
|
|
75
91
|
The generated project will include:
|
|
76
92
|
|
|
77
93
|
- `src/`: Source code (controllers, routes, services/use-cases).
|
|
94
|
+
- `src/errors/`: Custom error classes — `ApiError`, `NotFoundError`, `BadRequestError`.
|
|
95
|
+
- `src/utils/error.middleware.{ts|js}`: Global Express error handler (logs 500s, returns `{ statusCode, message }`).
|
|
78
96
|
- `flyway/sql/`: SQL migration scripts (if SQL database selected).
|
|
79
97
|
- `docker-compose.yml`: Services configuration for DB, Flyway, and Kafka.
|
|
80
98
|
- `package.json`: Dependencies and scripts (`start`, `dev`, `build`).
|
|
@@ -94,3 +112,13 @@ docker-compose up
|
|
|
94
112
|
## License
|
|
95
113
|
|
|
96
114
|
ISC
|
|
115
|
+
|
|
116
|
+
## 🚀 Roadmap & Upcoming Features
|
|
117
|
+
|
|
118
|
+
We are constantly working to improve `nodejs-quickstart-structure` and make it the most robust boilerplate generator for Node.js.
|
|
119
|
+
|
|
120
|
+
You can track our current progress, see what features are being worked on, and vote for your favorites on our public Trello board:
|
|
121
|
+
|
|
122
|
+
👉 **[View our Public Roadmap on Trello](https://trello.com/b/TPTo8ylF/nodejs-quickstart-structure-product)**
|
|
123
|
+
|
|
124
|
+
If you have a feature request or want to contribute, feel free to check the board and open an issue or pull request!
|
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
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Router, Request, Response } from 'express';
|
|
1
|
+
import { Router, Request, Response, NextFunction } from 'express';
|
|
2
2
|
import { UserController } from '@/interfaces/controllers/userController';
|
|
3
3
|
|
|
4
4
|
const router = Router();
|
|
5
5
|
const userController = new UserController();
|
|
6
6
|
|
|
7
|
-
router.post('/', (req: Request, res: Response) => userController.createUser(req, res));
|
|
8
|
-
router.get('/', (req: Request, res: Response) => userController.getUsers(req, res));
|
|
7
|
+
router.post('/', (req: Request, res: Response, next: NextFunction) => userController.createUser(req, res, next));
|
|
8
|
+
router.get('/', (req: Request, res: Response, next: NextFunction) => userController.getUsers(req, res, next));
|
|
9
9
|
|
|
10
10
|
export default router;
|
|
11
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import logger from '@/infrastructure/log/logger';
|
|
3
|
+
import { ApiError } from '@/errors/ApiError';
|
|
4
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
5
|
+
|
|
6
|
+
export const errorMiddleware = (err: Error, req: Request, res: Response, _: unknown) => {
|
|
7
|
+
let error = err;
|
|
8
|
+
|
|
9
|
+
if (!(error instanceof ApiError)) {
|
|
10
|
+
const statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR;
|
|
11
|
+
const message = error.message || 'Internal Server Error';
|
|
12
|
+
error = new ApiError(statusCode, message, false, err.stack);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { statusCode, message } = error as ApiError;
|
|
16
|
+
|
|
17
|
+
if (statusCode === HTTP_STATUS.INTERNAL_SERVER_ERROR) {
|
|
18
|
+
logger.error(`${statusCode} - ${message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
|
|
19
|
+
logger.error(error.stack || 'No stack trace');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
res.status(statusCode).json({
|
|
23
|
+
statusCode,
|
|
24
|
+
message,
|
|
25
|
+
...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
|
|
26
|
+
});
|
|
27
|
+
};
|
|
@@ -11,12 +11,11 @@
|
|
|
11
11
|
"lint:fix": "eslint . --fix",
|
|
12
12
|
"format": "prettier --write .",
|
|
13
13
|
"prepare": "node -e \"try { require('child_process').execSync('husky install'); } catch (e) { console.log('Not a git repository, skipping husky install'); }\"",
|
|
14
|
+
<% if (database === 'MongoDB') { %> "migrate": "migrate-mongo up",
|
|
15
|
+
<% } -%>
|
|
14
16
|
"test": "jest",
|
|
15
17
|
"test:watch": "jest --watch",
|
|
16
18
|
"test:coverage": "jest --coverage"
|
|
17
|
-
<%_ if (database === 'MongoDB') { -%>,
|
|
18
|
-
"migrate": "migrate-mongo up"
|
|
19
|
-
<%_ } -%>
|
|
20
19
|
},
|
|
21
20
|
"dependencies": {
|
|
22
21
|
"express": "^4.18.2",
|
|
@@ -80,9 +79,9 @@
|
|
|
80
79
|
"eslint-config-prettier": "^10.0.1",
|
|
81
80
|
"husky": "^8.0.3",
|
|
82
81
|
"lint-staged": "^15.4.3"<% if (language === 'TypeScript') { %>,
|
|
83
|
-
"typescript-eslint": "^8.24.1"
|
|
84
|
-
|
|
85
|
-
"@types/yamljs": "^0.2.34",<% }
|
|
82
|
+
"typescript-eslint": "^8.24.1",<%_ if (communication === 'REST APIs') { %>
|
|
83
|
+
"@types/swagger-ui-express": "^4.1.6",
|
|
84
|
+
"@types/yamljs": "^0.2.34",<%_ } -%>
|
|
86
85
|
"jest": "^29.7.0",
|
|
87
86
|
"ts-jest": "^29.2.5",
|
|
88
87
|
"@types/jest": "^29.5.14",
|
|
@@ -61,7 +61,7 @@ const createUser = async (data) => {
|
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
63
|
<% } else { -%>
|
|
64
|
-
const getUsers = async (req, res) => {
|
|
64
|
+
const getUsers = async (req, res, next) => {
|
|
65
65
|
try {
|
|
66
66
|
<%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
|
|
67
67
|
const users = await cacheService.getOrSet('users:all', async () => {
|
|
@@ -85,11 +85,11 @@ const getUsers = async (req, res) => {
|
|
|
85
85
|
res.json(users);
|
|
86
86
|
} catch (error) {
|
|
87
87
|
logger.error('Error fetching users:', error);
|
|
88
|
-
|
|
88
|
+
next(error);
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
const createUser = async (req, res) => {
|
|
92
|
+
const createUser = async (req, res, next) => {
|
|
93
93
|
try {
|
|
94
94
|
const { name, email } = req.body;
|
|
95
95
|
<%_ if (database === 'None') { -%>
|
|
@@ -107,7 +107,8 @@ const createUser = async (req, res) => {
|
|
|
107
107
|
res.status(HTTP_STATUS.CREATED).json(user);
|
|
108
108
|
<%_ } -%>
|
|
109
109
|
} catch (error) {
|
|
110
|
-
|
|
110
|
+
logger.error('Error creating user:', error);
|
|
111
|
+
next(error);
|
|
111
112
|
}
|
|
112
113
|
};
|
|
113
114
|
<% } -%>
|
|
@@ -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 };
|
|
@@ -1,23 +1,14 @@
|
|
|
1
|
-
const { GraphQLError } = require('graphql');
|
|
2
1
|
const userController = require('../../controllers/userController');
|
|
3
2
|
|
|
4
3
|
const userResolvers = {
|
|
5
4
|
Query: {
|
|
6
5
|
getAllUsers: async () => {
|
|
7
|
-
|
|
8
|
-
return await userController.getUsers();
|
|
9
|
-
} catch (error) {
|
|
10
|
-
throw new GraphQLError(error.message || 'Internal server error', { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
|
|
11
|
-
}
|
|
6
|
+
return await userController.getUsers();
|
|
12
7
|
}
|
|
13
8
|
},
|
|
14
9
|
Mutation: {
|
|
15
10
|
createUser: async (_, { name, email }) => {
|
|
16
|
-
|
|
17
|
-
return await userController.createUser({ name, email });
|
|
18
|
-
} catch (error) {
|
|
19
|
-
throw new GraphQLError(error.message || 'Internal server error', { extensions: { code: 'INTERNAL_SERVER_ERROR' } });
|
|
20
|
-
}
|
|
11
|
+
return await userController.createUser({ name, email });
|
|
21
12
|
}
|
|
22
13
|
}
|
|
23
14
|
};
|
|
@@ -7,6 +7,8 @@ require('dotenv').config();
|
|
|
7
7
|
const { ApolloServer } = require('@apollo/server');
|
|
8
8
|
const { expressMiddleware } = require('@apollo/server/express4');
|
|
9
9
|
const { ApolloServerPluginLandingPageLocalDefault } = require('@apollo/server/plugin/landingPage/default');
|
|
10
|
+
const { unwrapResolverError } = require('@apollo/server/errors');
|
|
11
|
+
const { ApiError } = require('./errors/ApiError');
|
|
10
12
|
const { typeDefs, resolvers } = require('./graphql');
|
|
11
13
|
const { gqlContext } = require('./graphql/context');
|
|
12
14
|
<% } -%>
|
|
@@ -19,6 +21,7 @@ const app = express();
|
|
|
19
21
|
const PORT = process.env.PORT || 3000;
|
|
20
22
|
const logger = require('./utils/logger');
|
|
21
23
|
const morgan = require('morgan');
|
|
24
|
+
const { errorMiddleware } = require('./utils/error.middleware');
|
|
22
25
|
|
|
23
26
|
app.use(cors());
|
|
24
27
|
app.use(express.json());
|
|
@@ -57,10 +60,30 @@ const startServer = async () => {
|
|
|
57
60
|
typeDefs,
|
|
58
61
|
resolvers,
|
|
59
62
|
plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
|
|
63
|
+
formatError: (formattedError, error) => {
|
|
64
|
+
const originalError = unwrapResolverError(error);
|
|
65
|
+
if (originalError instanceof ApiError) {
|
|
66
|
+
return {
|
|
67
|
+
...formattedError,
|
|
68
|
+
message: originalError.message,
|
|
69
|
+
extensions: {
|
|
70
|
+
...formattedError.extensions,
|
|
71
|
+
code: originalError.statusCode.toString(),
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
logger.error(`GraphQL Error: ${formattedError.message}`);
|
|
77
|
+
if (originalError && originalError.stack && process.env.NODE_ENV === 'development') {
|
|
78
|
+
logger.error(originalError.stack);
|
|
79
|
+
}
|
|
80
|
+
return formattedError;
|
|
81
|
+
},
|
|
60
82
|
});
|
|
61
83
|
await server.start();
|
|
62
84
|
app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
|
|
63
85
|
<%_ } -%>
|
|
86
|
+
app.use(errorMiddleware);
|
|
64
87
|
app.listen(PORT, () => {
|
|
65
88
|
logger.info(`Server running on port ${PORT}`);
|
|
66
89
|
<%_ if (communication === 'Kafka') { -%>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const logger = require('./logger');
|
|
2
|
+
const { ApiError } = require('../errors/ApiError');
|
|
3
|
+
const HTTP_STATUS = require('./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 };
|
|
@@ -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 User from '@/models/User';
|
|
@@ -63,7 +63,7 @@ export class UserController {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
<% } else { -%>
|
|
66
|
-
async getUsers(req: Request, res: Response) {
|
|
66
|
+
async getUsers(req: Request, res: Response, next: NextFunction) {
|
|
67
67
|
try {
|
|
68
68
|
<%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
|
|
69
69
|
const users = await cacheService.getOrSet('users:all', async () => {
|
|
@@ -86,16 +86,12 @@ export class UserController {
|
|
|
86
86
|
<%_ } -%>
|
|
87
87
|
res.json(users);
|
|
88
88
|
} catch (error) {
|
|
89
|
-
logger.error('Error fetching
|
|
90
|
-
|
|
91
|
-
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
|
|
92
|
-
} else {
|
|
93
|
-
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Unknown error occurred' });
|
|
94
|
-
}
|
|
89
|
+
logger.error('Error fetching user:', error);
|
|
90
|
+
next(error);
|
|
95
91
|
}
|
|
96
92
|
}
|
|
97
93
|
|
|
98
|
-
async createUser(req: Request, res: Response) {
|
|
94
|
+
async createUser(req: Request, res: Response, next: NextFunction) {
|
|
99
95
|
try {
|
|
100
96
|
const { name, email } = req.body;
|
|
101
97
|
<%_ if (database === 'None') { -%>
|
|
@@ -114,11 +110,7 @@ export class UserController {
|
|
|
114
110
|
<%_ } -%>
|
|
115
111
|
} catch (error) {
|
|
116
112
|
logger.error('Error creating user:', error);
|
|
117
|
-
|
|
118
|
-
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: error.message });
|
|
119
|
-
} else {
|
|
120
|
-
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ error: 'Unknown error occurred' });
|
|
121
|
-
}
|
|
113
|
+
next(error);
|
|
122
114
|
}
|
|
123
115
|
}
|
|
124
116
|
<% } -%>
|
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { GraphQLError } from 'graphql';
|
|
2
1
|
import { UserController } from '@/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
|
};
|
|
@@ -6,6 +6,7 @@ import rateLimit from 'express-rate-limit';
|
|
|
6
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
10
|
<%_ if (communication === 'REST APIs') { -%>
|
|
10
11
|
import apiRoutes from '@/routes/api';<%_ } -%>
|
|
11
12
|
<% if (communication === 'REST APIs') { %>
|
|
@@ -16,6 +17,8 @@ import swaggerSpecs from '@/config/swagger';<% } -%>
|
|
|
16
17
|
import { ApolloServer } from '@apollo/server';
|
|
17
18
|
import { expressMiddleware } from '@apollo/server/express4';
|
|
18
19
|
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';
|
|
20
|
+
import { unwrapResolverError } from '@apollo/server/errors';
|
|
21
|
+
import { ApiError } from '@/errors/ApiError';
|
|
19
22
|
import { typeDefs, resolvers } from '@/graphql';
|
|
20
23
|
import { gqlContext, MyContext } from '@/graphql/context';
|
|
21
24
|
<% } -%>
|
|
@@ -81,10 +84,30 @@ const startServer = async () => {
|
|
|
81
84
|
typeDefs,
|
|
82
85
|
resolvers,
|
|
83
86
|
plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
|
|
87
|
+
formatError: (formattedError, error) => {
|
|
88
|
+
const originalError = unwrapResolverError(error);
|
|
89
|
+
if (originalError instanceof ApiError) {
|
|
90
|
+
return {
|
|
91
|
+
...formattedError,
|
|
92
|
+
message: originalError.message,
|
|
93
|
+
extensions: {
|
|
94
|
+
...formattedError.extensions,
|
|
95
|
+
code: originalError.statusCode.toString(),
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
logger.error(`GraphQL Error: ${formattedError.message}`);
|
|
101
|
+
if (originalError instanceof Error && originalError.stack && process.env.NODE_ENV === 'development') {
|
|
102
|
+
logger.error(originalError.stack);
|
|
103
|
+
}
|
|
104
|
+
return formattedError;
|
|
105
|
+
},
|
|
84
106
|
});
|
|
85
107
|
await server.start();
|
|
86
108
|
app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
|
|
87
109
|
<%_ } -%>
|
|
110
|
+
app.use(errorMiddleware);
|
|
88
111
|
app.listen(port, () => {
|
|
89
112
|
logger.info(`Server running on port ${port}`);
|
|
90
113
|
<%_ if (communication === 'Kafka') { -%>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Router, Request, Response } from 'express';
|
|
1
|
+
import { Router, Request, Response, NextFunction } from 'express';
|
|
2
2
|
import { UserController } from '@/controllers/userController';
|
|
3
3
|
|
|
4
4
|
const router = Router();
|
|
5
5
|
const userController = new UserController();
|
|
6
6
|
|
|
7
|
-
router.get('/users', (req: Request, res: Response) => userController.getUsers(req, res));
|
|
8
|
-
router.post('/users', (req: Request, res: Response) => userController.createUser(req, res));
|
|
7
|
+
router.get('/users', (req: Request, res: Response, next: NextFunction) => userController.getUsers(req, res, next));
|
|
8
|
+
router.post('/users', (req: Request, res: Response, next: NextFunction) => userController.createUser(req, res, next));
|
|
9
9
|
|
|
10
10
|
export default router;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import logger from '@/utils/logger';
|
|
3
|
+
import { ApiError } from '@/errors/ApiError';
|
|
4
|
+
import { HTTP_STATUS } from '@/utils/httpCodes';
|
|
5
|
+
|
|
6
|
+
export const errorMiddleware = (err: Error, req: Request, res: Response, _: unknown) => {
|
|
7
|
+
let error = err;
|
|
8
|
+
|
|
9
|
+
if (!(error instanceof ApiError)) {
|
|
10
|
+
const statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR;
|
|
11
|
+
const message = error.message || 'Internal Server Error';
|
|
12
|
+
error = new ApiError(statusCode, message, false, err.stack);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { statusCode, message } = error as ApiError;
|
|
16
|
+
|
|
17
|
+
if (statusCode === HTTP_STATUS.INTERNAL_SERVER_ERROR) {
|
|
18
|
+
logger.error(`${statusCode} - ${message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
|
|
19
|
+
logger.error(error.stack || 'No stack trace');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
res.status(statusCode).json({
|
|
23
|
+
statusCode,
|
|
24
|
+
message,
|
|
25
|
+
...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
|
|
26
|
+
});
|
|
27
|
+
};
|
/package/templates/clean-architecture/js/src/infrastructure/webserver/{swagger.js → swagger.js.ejs}
RENAMED
|
File without changes
|
|
File without changes
|