express-pro-toolkit 2.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,426 @@
1
+ # express-pro-toolkit
2
+
3
+ > Enterprise-grade Express.js toolkit — async error handling, configurable error handler with logging hooks, request logger with correlation IDs, structured `AppError` class, pagination helpers, and consistent JSON responses. **Zero external dependencies.**
4
+
5
+ [![NPM Version](https://img.shields.io/npm/v/express-pro-toolkit.svg)](https://www.npmjs.com/package/express-pro-toolkit)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ ---
9
+
10
+ ## Features
11
+
12
+ | Utility | Description |
13
+ | --- | --- |
14
+ | `asyncHandler(fn)` | Wraps async route/error handlers — catches rejected promises automatically |
15
+ | `errorHandler` | Drop-in global error handler with consistent JSON responses |
16
+ | `createErrorHandler(opts)` | Configurable error handler with logging hooks (Sentry, Datadog, etc.) |
17
+ | `requestLogger` | Colourised request logger with response time |
18
+ | `createRequestLogger(opts)` | Configurable logger — skip routes, custom output, correlation IDs |
19
+ | `correlationId(opts)` | Generates / propagates `x-request-id` for distributed tracing |
20
+ | `notFoundHandler(opts)` | 404 catch-all middleware |
21
+ | `AppError` | Structured error class with static factories, codes, and operational flag |
22
+ | `sendSuccess()` | Consistent JSON success response |
23
+ | `sendError()` | Consistent JSON error response |
24
+ | `sendPaginated()` | Paginated JSON response with metadata |
25
+ | `httpStatus` | Frozen enum of common HTTP status codes |
26
+
27
+ **Zero external dependencies** · Works with **Express 4 & 5** · **TypeScript declarations included**
28
+
29
+ ---
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install express-pro-toolkit
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Quick Start
40
+
41
+ ```js
42
+ const express = require('express');
43
+ const {
44
+ asyncHandler,
45
+ createErrorHandler,
46
+ createRequestLogger,
47
+ correlationId,
48
+ notFoundHandler,
49
+ sendSuccess,
50
+ AppError,
51
+ httpStatus,
52
+ } = require('express-pro-toolkit');
53
+
54
+ const app = express();
55
+
56
+ app.use(express.json());
57
+ app.use(correlationId()); // x-request-id
58
+ app.use(createRequestLogger({ // skip health checks
59
+ skip: (req) => req.url === '/health',
60
+ }));
61
+
62
+ app.get('/api/users', asyncHandler(async (req, res) => {
63
+ const users = await getUsersFromDB();
64
+ sendSuccess(res, users, 'Users fetched');
65
+ }));
66
+
67
+ app.post('/api/users', asyncHandler(async (req, res) => {
68
+ if (!req.body.email) {
69
+ throw AppError.validation([{ field: 'email', message: 'Required' }]);
70
+ }
71
+ const user = await createUser(req.body);
72
+ sendSuccess(res, user, 'Created', httpStatus.CREATED);
73
+ }));
74
+
75
+ app.use(notFoundHandler()); // 404 catch-all
76
+ app.use(createErrorHandler({ // global error handler
77
+ onError: ({ err, requestId }) => {
78
+ // Send to Sentry, Datadog, Pino, etc.
79
+ },
80
+ }));
81
+
82
+ app.listen(3000);
83
+ ```
84
+
85
+ ---
86
+
87
+ ## API Reference
88
+
89
+ ### `asyncHandler(fn)`
90
+
91
+ Wraps an async `(req, res, next)` handler so rejected promises are forwarded to `next(err)`. Also supports 4-arg error-handling middleware.
92
+
93
+ ```js
94
+ router.get('/items', asyncHandler(async (req, res) => {
95
+ const items = await Item.find();
96
+ res.json(items);
97
+ }));
98
+
99
+ // Also works with async error middleware
100
+ app.use(asyncHandler(async (err, req, res, next) => {
101
+ await logToExternalService(err);
102
+ next(err);
103
+ }));
104
+ ```
105
+
106
+ ---
107
+
108
+ ### `AppError`
109
+
110
+ Structured error class with HTTP status codes, machine-readable codes, operational flags, and static factory methods.
111
+
112
+ ```js
113
+ const { AppError } = require('express-pro-toolkit');
114
+
115
+ // Static factories (most common)
116
+ throw AppError.badRequest('Invalid input'); // 400
117
+ throw AppError.unauthorized('Login required'); // 401
118
+ throw AppError.forbidden('No access'); // 403
119
+ throw AppError.notFound('User not found'); // 404
120
+ throw AppError.conflict('Email already exists'); // 409
121
+ throw AppError.tooManyRequests('Slow down'); // 429
122
+ throw AppError.internal('Unexpected failure'); // 500 (isOperational: false)
123
+
124
+ // Validation with field-level details
125
+ throw AppError.validation([
126
+ { field: 'email', message: 'Email is required' },
127
+ { field: 'name', message: 'Name is required' },
128
+ ]);
129
+
130
+ // Custom
131
+ throw new AppError('Payment failed', 402, {
132
+ code: 'PAYMENT_DECLINED',
133
+ errors: [{ provider: 'stripe', reason: 'insufficient_funds' }],
134
+ });
135
+ ```
136
+
137
+ **Operational vs Programmer errors:**
138
+ - `isOperational: true` (default) — expected failures, safe to expose to clients.
139
+ - `isOperational: false` — bugs; in production the message is replaced with "Internal Server Error".
140
+
141
+ ---
142
+
143
+ ### `errorHandler` / `createErrorHandler(options)`
144
+
145
+ | Option | Type | Default | Description |
146
+ | --- | --- | --- | --- |
147
+ | `includeStack` | `boolean` | `!production` | Include stack trace in response |
148
+ | `onError` | `function` | `console.error` | Logging hook — receives full payload |
149
+ | `defaultStatusCode` | `number` | `500` | Fallback HTTP status |
150
+
151
+ ```js
152
+ // Drop-in (default config)
153
+ app.use(errorHandler);
154
+
155
+ // Enterprise — custom logging hook
156
+ app.use(createErrorHandler({
157
+ onError: ({ err, statusCode, method, url, requestId, timestamp }) => {
158
+ Sentry.captureException(err, { tags: { requestId } });
159
+ },
160
+ }));
161
+ ```
162
+
163
+ **Error response includes:**
164
+ - `success: false`
165
+ - `message` — safe message (sanitised in production for 5xx)
166
+ - `errors` — detail array (validation, etc.)
167
+ - `code` — machine-readable error code (if set on error)
168
+ - `requestId` — correlation ID (if `correlationId` middleware is active)
169
+ - `stack` — stack trace (dev only by default)
170
+
171
+ ---
172
+
173
+ ### `requestLogger` / `createRequestLogger(options)`
174
+
175
+ | Option | Type | Default | Description |
176
+ | --- | --- | --- | --- |
177
+ | `log` | `function` | Colourised console | Custom log function `(info) => void` |
178
+ | `skip` | `function` | none | `(req, res) => boolean` — skip certain requests |
179
+
180
+ ```js
181
+ // Default (colourised, logs everything)
182
+ app.use(requestLogger);
183
+
184
+ // Skip health checks, pipe to Pino
185
+ app.use(createRequestLogger({
186
+ skip: (req) => req.url === '/health',
187
+ log: (info) => pino.info(info, 'http'),
188
+ }));
189
+ ```
190
+
191
+ **Log info object:**
192
+ ```js
193
+ {
194
+ method: 'GET',
195
+ url: '/api/users',
196
+ status: 200,
197
+ duration: 4.21, // ms
198
+ timestamp: '2026-03-01T12:00:00.000Z',
199
+ requestId: 'a1b2c3d4e5f6'
200
+ }
201
+ ```
202
+
203
+ ---
204
+
205
+ ### `correlationId(options)`
206
+
207
+ Generates or propagates a unique request ID for distributed tracing.
208
+
209
+ | Option | Type | Default | Description |
210
+ | --- | --- | --- | --- |
211
+ | `header` | `string` | `'x-request-id'` | Header to read / echo |
212
+ | `generator` | `function` | 16-char hex | Custom ID generator |
213
+ | `setResponseHeader` | `boolean` | `true` | Echo ID back to client |
214
+
215
+ ```js
216
+ app.use(correlationId());
217
+
218
+ // Custom header + UUID generator
219
+ app.use(correlationId({
220
+ header: 'x-correlation-id',
221
+ generator: () => crypto.randomUUID(),
222
+ }));
223
+
224
+ // Access in routes
225
+ app.get('/test', (req, res) => {
226
+ console.log(req.id); // 'a1b2c3d4e5f67890'
227
+ });
228
+ ```
229
+
230
+ ---
231
+
232
+ ### `notFoundHandler(options)`
233
+
234
+ Catches unmatched routes and forwards a 404 `AppError` to the error handler.
235
+
236
+ ```js
237
+ // After all routes, before errorHandler
238
+ app.use(notFoundHandler());
239
+ app.use(notFoundHandler({ message: 'Route does not exist' }));
240
+ ```
241
+
242
+ ---
243
+
244
+ ### `sendSuccess(res, data, message?, statusCode?, meta?)`
245
+
246
+ | Parameter | Type | Default | Description |
247
+ | --- | --- | --- | --- |
248
+ | `res` | `Response` | — | Express response |
249
+ | `data` | `any` | `null` | Payload |
250
+ | `message` | `string` | `"Success"` | Human-readable message |
251
+ | `statusCode` | `number` | `200` | HTTP status code |
252
+ | `meta` | `object` | — | Optional metadata (pagination, etc.) |
253
+
254
+ ```js
255
+ sendSuccess(res, user, 'User created', 201);
256
+
257
+ // With metadata
258
+ sendSuccess(res, users, 'Fetched', 200, { cached: true });
259
+ ```
260
+
261
+ ---
262
+
263
+ ### `sendError(res, message?, statusCode?, errors?, code?)`
264
+
265
+ ```js
266
+ sendError(res, 'Validation failed', 422, [
267
+ { field: 'email', message: 'Required' },
268
+ ], 'VALIDATION_ERROR');
269
+ ```
270
+
271
+ ---
272
+
273
+ ### `sendPaginated(res, items, pagination, message?)`
274
+
275
+ ```js
276
+ sendPaginated(res, users, { page: 2, limit: 25, total: 100 });
277
+
278
+ // Response:
279
+ // {
280
+ // "success": true,
281
+ // "message": "Success",
282
+ // "data": [...],
283
+ // "meta": {
284
+ // "page": 2,
285
+ // "limit": 25,
286
+ // "total": 100,
287
+ // "totalPages": 4,
288
+ // "hasNextPage": true,
289
+ // "hasPrevPage": true
290
+ // }
291
+ // }
292
+ ```
293
+
294
+ ---
295
+
296
+ ### `httpStatus`
297
+
298
+ Frozen object of common HTTP status codes — eliminates magic numbers.
299
+
300
+ ```js
301
+ const { httpStatus } = require('express-pro-toolkit');
302
+
303
+ res.status(httpStatus.CREATED).json(data); // 201
304
+ res.status(httpStatus.NO_CONTENT).end(); // 204
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Response Formats
310
+
311
+ ### Success
312
+
313
+ ```json
314
+ {
315
+ "success": true,
316
+ "message": "Message here",
317
+ "data": { }
318
+ }
319
+ ```
320
+
321
+ ### Success (paginated)
322
+
323
+ ```json
324
+ {
325
+ "success": true,
326
+ "message": "Message here",
327
+ "data": [ ],
328
+ "meta": {
329
+ "page": 1,
330
+ "limit": 25,
331
+ "total": 100,
332
+ "totalPages": 4,
333
+ "hasNextPage": true,
334
+ "hasPrevPage": false
335
+ }
336
+ }
337
+ ```
338
+
339
+ ### Error
340
+
341
+ ```json
342
+ {
343
+ "success": false,
344
+ "message": "Error message",
345
+ "code": "ERROR_CODE",
346
+ "errors": [ ],
347
+ "requestId": "a1b2c3d4e5f67890"
348
+ }
349
+ ```
350
+
351
+ ---
352
+
353
+ ## Example App
354
+
355
+ ```bash
356
+ cd express-pro-toolkit
357
+ npm install express
358
+ node example/server.js
359
+ ```
360
+
361
+ ---
362
+
363
+ ## Smoke Tests
364
+
365
+ ```bash
366
+ npm test
367
+ ```
368
+
369
+ ---
370
+
371
+ ## TypeScript
372
+
373
+ TypeScript declarations are bundled in `types/index.d.ts`. No `@types/` package needed.
374
+
375
+ ```ts
376
+ import {
377
+ asyncHandler,
378
+ AppError,
379
+ createErrorHandler,
380
+ httpStatus,
381
+ } from 'express-pro-toolkit';
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Publishing to NPM
387
+
388
+ ```bash
389
+ npm login
390
+ npm version patch # 2.0.0 → 2.0.1
391
+ npm publish
392
+ ```
393
+
394
+ ---
395
+
396
+ ## Folder Structure
397
+
398
+ ```
399
+ express-pro-toolkit/
400
+ ├── example/
401
+ │ └── server.js
402
+ ├── src/
403
+ │ ├── AppError.js
404
+ │ ├── asyncHandler.js
405
+ │ ├── correlationId.js
406
+ │ ├── errorHandler.js
407
+ │ ├── httpStatus.js
408
+ │ ├── notFoundHandler.js
409
+ │ ├── requestLogger.js
410
+ │ └── responseHelpers.js
411
+ ├── test/
412
+ │ └── smoke.js
413
+ ├── types/
414
+ │ └── index.d.ts
415
+ ├── index.js
416
+ ├── package.json
417
+ ├── README.md
418
+ ├── LICENSE
419
+ └── .npmignore
420
+ ```
421
+
422
+ ---
423
+
424
+ ## License
425
+
426
+ [MIT](LICENSE)
package/index.js ADDED
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ const asyncHandler = require('./src/asyncHandler');
4
+ const errorHandlerModule = require('./src/errorHandler');
5
+ const requestLoggerModule = require('./src/requestLogger');
6
+ const { sendSuccess, sendError, sendPaginated } = require('./src/responseHelpers');
7
+ const AppError = require('./src/AppError');
8
+ const correlationId = require('./src/correlationId');
9
+ const notFoundHandler = require('./src/notFoundHandler');
10
+ const httpStatus = require('./src/httpStatus');
11
+
12
+ module.exports = {
13
+ // Core (v1 backward-compatible)
14
+ asyncHandler,
15
+ errorHandler: errorHandlerModule,
16
+ requestLogger: requestLoggerModule,
17
+ sendSuccess,
18
+ sendError,
19
+
20
+ // Enterprise additions (v2)
21
+ createErrorHandler: errorHandlerModule.createErrorHandler,
22
+ createRequestLogger: requestLoggerModule.createRequestLogger,
23
+ sendPaginated,
24
+ AppError,
25
+ correlationId,
26
+ notFoundHandler,
27
+ httpStatus,
28
+ };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "express-pro-toolkit",
3
+ "version": "2.0.0",
4
+ "description": "Enterprise-grade Express.js toolkit — async error handling, configurable error handler, request logger with correlation IDs, structured AppError class, pagination helpers, and consistent JSON responses. Zero external dependencies.",
5
+ "main": "index.js",
6
+ "types": "types/index.d.ts",
7
+ "scripts": {
8
+ "test": "jest --verbose",
9
+ "test:smoke": "node test/smoke.js",
10
+ "test:coverage": "jest --verbose --coverage",
11
+ "example": "node example/server.js",
12
+ "lint": "echo \"No linter configured yet\"",
13
+ "prepublishOnly": "jest"
14
+ },
15
+ "keywords": [
16
+ "express",
17
+ "error-handler",
18
+ "async-handler",
19
+ "middleware",
20
+ "request-logger",
21
+ "json-response",
22
+ "express-middleware",
23
+ "error-handling",
24
+ "toolkit",
25
+ "correlation-id",
26
+ "request-id",
27
+ "app-error",
28
+ "pagination",
29
+ "enterprise",
30
+ "express-pro-toolkit"
31
+ ],
32
+ "author": "Hadeed Ul Hassan <hadeed.hassan189@gmail.com>",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/hadiid1718/express-pro-toolkit.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/hadiid1718/express-pro-toolkit/issues"
40
+ },
41
+ "homepage": "https://github.com/hadiid1718/express-pro-toolkit#readme",
42
+ "engines": {
43
+ "node": ">=12.0.0"
44
+ },
45
+ "peerDependencies": {
46
+ "express": ">=4.0.0"
47
+ },
48
+ "files": [
49
+ "index.js",
50
+ "src/",
51
+ "types/",
52
+ "LICENSE",
53
+ "README.md"
54
+ ]
55
+ }
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Custom application error class for express-pro-toolkit.
5
+ *
6
+ * Provides structured, operational errors with HTTP status codes,
7
+ * error codes, and detail arrays. Distinguishes operational errors
8
+ * (expected, safe to expose) from programmer errors (bugs).
9
+ *
10
+ * @extends Error
11
+ *
12
+ * @example
13
+ * const { AppError } = require('express-pro-toolkit');
14
+ *
15
+ * // Simple
16
+ * throw new AppError('User not found', 404);
17
+ *
18
+ * // With error code
19
+ * throw new AppError('Token expired', 401, { code: 'AUTH_TOKEN_EXPIRED' });
20
+ *
21
+ * // With validation details
22
+ * throw new AppError('Validation failed', 422, {
23
+ * code: 'VALIDATION_ERROR',
24
+ * errors: [
25
+ * { field: 'email', message: 'Email is required' },
26
+ * ],
27
+ * });
28
+ */
29
+ class AppError extends Error {
30
+ /**
31
+ * @param {string} message - Human-readable error message
32
+ * @param {number} [statusCode=500] - HTTP status code
33
+ * @param {object} [options] - Additional options
34
+ * @param {string} [options.code] - Machine-readable error code (e.g. 'VALIDATION_ERROR')
35
+ * @param {Array} [options.errors] - Array of detailed sub-errors
36
+ * @param {boolean} [options.isOperational=true] - Whether this is an operational (expected) error
37
+ */
38
+ constructor(message, statusCode, options) {
39
+ super(message);
40
+
41
+ /** @type {string} */
42
+ this.name = 'AppError';
43
+
44
+ /** @type {number} */
45
+ this.statusCode = typeof statusCode === 'number' ? statusCode : 500;
46
+
47
+ const opts = options && typeof options === 'object' ? options : {};
48
+
49
+ /** @type {string|undefined} */
50
+ this.code = opts.code || undefined;
51
+
52
+ /** @type {Array|null} */
53
+ this.errors = Array.isArray(opts.errors) ? opts.errors : null;
54
+
55
+ /**
56
+ * Operational errors are expected failures (bad input, not found, etc.).
57
+ * Non-operational errors indicate programmer bugs and should trigger alerts.
58
+ * @type {boolean}
59
+ */
60
+ this.isOperational = opts.isOperational !== false;
61
+
62
+ /** @type {number} */
63
+ this.timestamp = Date.now();
64
+
65
+ // Capture stack trace, excluding constructor call from it
66
+ if (Error.captureStackTrace) {
67
+ Error.captureStackTrace(this, AppError);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Create a 400 Bad Request error.
73
+ * @param {string} [message='Bad Request']
74
+ * @param {object} [options]
75
+ * @returns {AppError}
76
+ */
77
+ static badRequest(message, options) {
78
+ return new AppError(message || 'Bad Request', 400, options);
79
+ }
80
+
81
+ /**
82
+ * Create a 401 Unauthorized error.
83
+ * @param {string} [message='Unauthorized']
84
+ * @param {object} [options]
85
+ * @returns {AppError}
86
+ */
87
+ static unauthorized(message, options) {
88
+ return new AppError(message || 'Unauthorized', 401, options);
89
+ }
90
+
91
+ /**
92
+ * Create a 403 Forbidden error.
93
+ * @param {string} [message='Forbidden']
94
+ * @param {object} [options]
95
+ * @returns {AppError}
96
+ */
97
+ static forbidden(message, options) {
98
+ return new AppError(message || 'Forbidden', 403, options);
99
+ }
100
+
101
+ /**
102
+ * Create a 404 Not Found error.
103
+ * @param {string} [message='Not Found']
104
+ * @param {object} [options]
105
+ * @returns {AppError}
106
+ */
107
+ static notFound(message, options) {
108
+ return new AppError(message || 'Not Found', 404, options);
109
+ }
110
+
111
+ /**
112
+ * Create a 409 Conflict error.
113
+ * @param {string} [message='Conflict']
114
+ * @param {object} [options]
115
+ * @returns {AppError}
116
+ */
117
+ static conflict(message, options) {
118
+ return new AppError(message || 'Conflict', 409, options);
119
+ }
120
+
121
+ /**
122
+ * Create a 422 Validation error with field-level details.
123
+ * @param {Array} errors - Array of { field, message } objects
124
+ * @param {string} [message='Validation Failed']
125
+ * @returns {AppError}
126
+ */
127
+ static validation(errors, message) {
128
+ return new AppError(message || 'Validation Failed', 422, {
129
+ code: 'VALIDATION_ERROR',
130
+ errors: errors,
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Create a 429 Too Many Requests error.
136
+ * @param {string} [message='Too Many Requests']
137
+ * @param {object} [options]
138
+ * @returns {AppError}
139
+ */
140
+ static tooManyRequests(message, options) {
141
+ return new AppError(message || 'Too Many Requests', 429, options);
142
+ }
143
+
144
+ /**
145
+ * Create a 500 Internal Server Error.
146
+ * @param {string} [message='Internal Server Error']
147
+ * @param {object} [options]
148
+ * @returns {AppError}
149
+ */
150
+ static internal(message, options) {
151
+ return new AppError(message || 'Internal Server Error', 500, {
152
+ ...options,
153
+ isOperational: false,
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Serialise the error to a plain object (useful for logging).
159
+ * @returns {object}
160
+ */
161
+ toJSON() {
162
+ return {
163
+ name: this.name,
164
+ message: this.message,
165
+ statusCode: this.statusCode,
166
+ code: this.code,
167
+ errors: this.errors,
168
+ isOperational: this.isOperational,
169
+ timestamp: this.timestamp,
170
+ stack: this.stack,
171
+ };
172
+ }
173
+ }
174
+
175
+ module.exports = AppError;