devdad-express-utils 1.4.0 → 1.6.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/README.md +41 -36
- package/dist/DatabaseConnection.d.ts +10 -1
- package/dist/DatabaseConnection.js +39 -10
- package/dist/responseFormatter.d.ts +2 -1
- package/dist/responseFormatter.js +2 -1
- package/package.json +1 -1
- package/dist/AppError.1.d.ts +0 -0
- package/dist/AppError.1.js +0 -1
- package/dist/AppError.2.d.ts +0 -0
- package/dist/AppError.2.js +0 -1
- package/dist/authWrapper.d.ts +0 -17
- package/dist/authWrapper.js +0 -48
- package/dist/validateRequest.d.ts +0 -9
- package/dist/validateRequest.js +0 -19
package/README.md
CHANGED
|
@@ -60,44 +60,50 @@ Standardize API responses with consistent JSON structure.
|
|
|
60
60
|
import { sendSuccess, sendError, sendPaginated } from "devdad-express-utils";
|
|
61
61
|
|
|
62
62
|
// Success response
|
|
63
|
-
sendSuccess(res, { id: 1, name:
|
|
63
|
+
sendSuccess(res, { id: 1, name: "John" }, "User fetched", 200);
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Authentication Middleware
|
|
72
|
-
|
|
73
|
-
JWT-based authentication wrapper.
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { requireAuth } from "devdad-express-utils";
|
|
65
|
+
// Chain additional response methods
|
|
66
|
+
sendSuccess(res, { id: 1, name: "John" })
|
|
67
|
+
.cookie("session", "abc123")
|
|
68
|
+
.setHeader("X-Custom", "value");
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
app.get('/profile', authMiddleware, (req, res) => {
|
|
81
|
-
// req.user contains decoded JWT
|
|
82
|
-
res.json(req.user);
|
|
83
|
-
});
|
|
70
|
+
// Error response
|
|
71
|
+
sendError(res, "User not found", 404);
|
|
84
72
|
```
|
|
85
73
|
|
|
86
74
|
### Database Connection
|
|
87
75
|
|
|
88
|
-
MongoDB connection utility with automatic reconnection and retry logic.
|
|
76
|
+
MongoDB connection utility with automatic reconnection, exponential backoff, and configurable retry logic.
|
|
89
77
|
|
|
90
78
|
```typescript
|
|
91
|
-
import { connectDB, getDBStatus } from "devdad-express-utils";
|
|
79
|
+
import { connectDB, getDBStatus, resetDBConnection } from "devdad-express-utils";
|
|
92
80
|
|
|
93
81
|
// Connect to MongoDB (ensure MONGO_URI is set in environment)
|
|
94
82
|
await connectDB();
|
|
95
83
|
|
|
96
84
|
// Check connection status
|
|
97
85
|
const status = getDBStatus();
|
|
98
|
-
console.log(status); // { isConnected: true, readyState: 1, host: '...', name: '...' }
|
|
86
|
+
console.log(status); // { isConnected: true, readyState: 1, host: '...', name: '...', retryCount: 0 }
|
|
87
|
+
|
|
88
|
+
// Manually reset and retry (useful after Docker container restarts)
|
|
89
|
+
await resetDBConnection();
|
|
99
90
|
```
|
|
100
91
|
|
|
92
|
+
#### Environment Variables
|
|
93
|
+
|
|
94
|
+
- **MONGO_URI**: MongoDB connection string (required)
|
|
95
|
+
- **DB_MAX_RETRIES**: Maximum connection retry attempts (default: 10)
|
|
96
|
+
- **DB_RETRY_INTERVAL**: Initial retry interval in milliseconds (default: 3000)
|
|
97
|
+
- **NODE_ENV**: Set to 'production' to exit after max retries, 'development' to keep process alive
|
|
98
|
+
|
|
99
|
+
#### Retry Behavior
|
|
100
|
+
|
|
101
|
+
- **Exponential backoff**: Retry intervals increase exponentially (3s → 6s → 12s → 24s → 30s max)
|
|
102
|
+
- **Random jitter**: Adds up to 1 second of random delay to prevent thundering herd
|
|
103
|
+
- **Docker-friendly**: Higher default retry count (10) accommodates container startup times
|
|
104
|
+
- **Development mode**: Process stays alive after max retries for manual recovery
|
|
105
|
+
- **Production mode**: Process exits after max retries to allow container restart
|
|
106
|
+
|
|
101
107
|
### Logging
|
|
102
108
|
|
|
103
109
|
Winston-based logger with configurable service name and environment-aware transports.
|
|
@@ -122,6 +128,7 @@ logger.debug("Processing request", { requestId: "abc-123" });
|
|
|
122
128
|
#### Log Files
|
|
123
129
|
|
|
124
130
|
In development, logs are written to:
|
|
131
|
+
|
|
125
132
|
- `error.log`: Error level and above
|
|
126
133
|
- `combined.log`: All log levels
|
|
127
134
|
|
|
@@ -208,10 +215,10 @@ errorHandler(err: any, req: Request, res: Response, next: NextFunction) => void
|
|
|
208
215
|
|
|
209
216
|
### sendSuccess
|
|
210
217
|
|
|
211
|
-
Sends a standardized success response.
|
|
218
|
+
Sends a standardized success response. Returns the Response object for method chaining.
|
|
212
219
|
|
|
213
220
|
```typescript
|
|
214
|
-
sendSuccess(res: Response, data: any, message?: string, statusCode?: number) =>
|
|
221
|
+
sendSuccess(res: Response, data: any, message?: string, statusCode?: number) => Response
|
|
215
222
|
```
|
|
216
223
|
|
|
217
224
|
### sendError
|
|
@@ -222,16 +229,6 @@ Sends a standardized error response.
|
|
|
222
229
|
sendError(res: Response, message: string, statusCode?: number, data?: any) => void
|
|
223
230
|
```
|
|
224
231
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
### requireAuth
|
|
228
|
-
|
|
229
|
-
Middleware for JWT authentication.
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
requireAuth(options: { secret: string, algorithms?: Algorithm[] }) => (req, res, next) => void
|
|
233
|
-
```
|
|
234
|
-
|
|
235
232
|
### connectDB
|
|
236
233
|
|
|
237
234
|
Connects to MongoDB with retry logic and automatic reconnection.
|
|
@@ -245,7 +242,15 @@ connectDB() => Promise<void>
|
|
|
245
242
|
Gets the current MongoDB connection status.
|
|
246
243
|
|
|
247
244
|
```typescript
|
|
248
|
-
getDBStatus() => { isConnected: boolean; readyState: number; host: string; name: string; }
|
|
245
|
+
getDBStatus() => { isConnected: boolean; readyState: number; host: string; name: string; retryCount: number; }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### resetDBConnection
|
|
249
|
+
|
|
250
|
+
Manually resets retry count and attempts reconnection. Useful for recovering from Docker container restarts.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
resetDBConnection() => Promise<void>
|
|
249
254
|
```
|
|
250
255
|
|
|
251
256
|
### logger
|
|
@@ -253,7 +258,7 @@ getDBStatus() => { isConnected: boolean; readyState: number; host: string; name:
|
|
|
253
258
|
Winston logger instance with JSON formatting, timestamps, and error stack traces.
|
|
254
259
|
|
|
255
260
|
```typescript
|
|
256
|
-
logger: winston.Logger
|
|
261
|
+
logger: winston.Logger;
|
|
257
262
|
```
|
|
258
263
|
|
|
259
264
|
## Development
|
|
@@ -17,11 +17,12 @@ declare class DatabaseConnection {
|
|
|
17
17
|
connect(): Promise<void>;
|
|
18
18
|
/**
|
|
19
19
|
* Handles connection errors by retrying the connection up to MAX_RETRIES times.
|
|
20
|
-
*
|
|
20
|
+
* Uses exponential backoff for retry intervals.
|
|
21
21
|
*/
|
|
22
22
|
private handleConnectionError;
|
|
23
23
|
/**
|
|
24
24
|
* Handles disconnection events by attempting to reconnect if not already connected.
|
|
25
|
+
* Resets retry count for fresh reconnection attempts.
|
|
25
26
|
*/
|
|
26
27
|
private handleDisconnection;
|
|
27
28
|
/**
|
|
@@ -37,7 +38,13 @@ declare class DatabaseConnection {
|
|
|
37
38
|
readyState: number;
|
|
38
39
|
host: string;
|
|
39
40
|
name: string;
|
|
41
|
+
retryCount: number;
|
|
40
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* Manually reset retry count and attempt reconnection
|
|
45
|
+
* Useful for recovering from Docker container restarts
|
|
46
|
+
*/
|
|
47
|
+
resetAndRetry(): Promise<void>;
|
|
41
48
|
}
|
|
42
49
|
declare const _default: () => Promise<void>;
|
|
43
50
|
export default _default;
|
|
@@ -46,5 +53,7 @@ export declare const getDBStatus: () => {
|
|
|
46
53
|
readyState: number;
|
|
47
54
|
host: string;
|
|
48
55
|
name: string;
|
|
56
|
+
retryCount: number;
|
|
49
57
|
};
|
|
58
|
+
export declare const resetDBConnection: () => Promise<void>;
|
|
50
59
|
export { DatabaseConnection };
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import mongoose from "mongoose";
|
|
2
2
|
/**
|
|
3
3
|
* Maximum number of connection retry attempts
|
|
4
|
+
* Can be overridden with DB_MAX_RETRIES environment variable
|
|
4
5
|
*/
|
|
5
|
-
const MAX_RETRIES =
|
|
6
|
+
const MAX_RETRIES = parseInt(process.env.DB_MAX_RETRIES || '10');
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
+
* Initial interval between retry attempts in milliseconds
|
|
9
|
+
* Can be overridden with DB_RETRY_INTERVAL environment variable
|
|
8
10
|
*/
|
|
9
|
-
const
|
|
11
|
+
const INITIAL_RETRY_INTERVAL = parseInt(process.env.DB_RETRY_INTERVAL || '3000'); // 3 seconds
|
|
12
|
+
/**
|
|
13
|
+
* Maximum retry interval (caps exponential backoff)
|
|
14
|
+
*/
|
|
15
|
+
const MAX_RETRY_INTERVAL = 30000; // 30 seconds
|
|
10
16
|
/**
|
|
11
17
|
* Database connection class for MongoDB using Mongoose.
|
|
12
18
|
* Provides automatic reconnection, retry logic, and connection status monitoring.
|
|
@@ -69,27 +75,39 @@ class DatabaseConnection {
|
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
71
77
|
* Handles connection errors by retrying the connection up to MAX_RETRIES times.
|
|
72
|
-
*
|
|
78
|
+
* Uses exponential backoff for retry intervals.
|
|
73
79
|
*/
|
|
74
80
|
async handleConnectionError() {
|
|
75
81
|
if (this.retryCount < MAX_RETRIES) {
|
|
76
82
|
this.retryCount++;
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
// Calculate exponential backoff with jitter
|
|
84
|
+
const exponentialDelay = Math.min(INITIAL_RETRY_INTERVAL * Math.pow(2, this.retryCount - 1), MAX_RETRY_INTERVAL);
|
|
85
|
+
const jitter = Math.random() * 1000; // Add up to 1 second of random jitter
|
|
86
|
+
const delay = exponentialDelay + jitter;
|
|
87
|
+
console.log(`Retrying connection... Attempt ${this.retryCount} of ${MAX_RETRIES} (waiting ${Math.round(delay)}ms)`);
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
79
89
|
return this.connect();
|
|
80
90
|
}
|
|
81
91
|
else {
|
|
82
|
-
console.error(`Failed to connect to MongoDB after ${MAX_RETRIES} attempts`);
|
|
83
|
-
|
|
92
|
+
console.error(`Failed to connect to MongoDB after ${MAX_RETRIES} attempts. Please check:`, `1. Docker container is running: docker ps`, `2. MongoDB is accessible: ${process.env.MONGO_URI}`, `3. Network connectivity between containers`, `4. Environment variables are correctly set`);
|
|
93
|
+
// Only exit in production, allow recovery in development
|
|
94
|
+
if (process.env.NODE_ENV === 'production') {
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log('Development mode: keeping process alive for manual retry');
|
|
99
|
+
}
|
|
84
100
|
}
|
|
85
101
|
}
|
|
86
102
|
/**
|
|
87
103
|
* Handles disconnection events by attempting to reconnect if not already connected.
|
|
104
|
+
* Resets retry count for fresh reconnection attempts.
|
|
88
105
|
*/
|
|
89
|
-
handleDisconnection() {
|
|
106
|
+
async handleDisconnection() {
|
|
90
107
|
if (!this.isConnected) {
|
|
91
108
|
console.log("Attempting to reconnect to MongoDB...");
|
|
92
|
-
this.
|
|
109
|
+
this.retryCount = 0; // Reset retry count for reconnection attempts
|
|
110
|
+
await this.connect();
|
|
93
111
|
}
|
|
94
112
|
}
|
|
95
113
|
/**
|
|
@@ -116,12 +134,23 @@ class DatabaseConnection {
|
|
|
116
134
|
readyState: mongoose.connection.readyState,
|
|
117
135
|
host: mongoose.connection.host,
|
|
118
136
|
name: mongoose.connection.name,
|
|
137
|
+
retryCount: this.retryCount,
|
|
119
138
|
};
|
|
120
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Manually reset retry count and attempt reconnection
|
|
142
|
+
* Useful for recovering from Docker container restarts
|
|
143
|
+
*/
|
|
144
|
+
async resetAndRetry() {
|
|
145
|
+
console.log('Resetting connection state and retrying...');
|
|
146
|
+
this.retryCount = 0;
|
|
147
|
+
await this.connect();
|
|
148
|
+
}
|
|
121
149
|
}
|
|
122
150
|
// Create a singleton instance
|
|
123
151
|
const dbConnection = new DatabaseConnection();
|
|
124
152
|
// Export the connect function and the instance
|
|
125
153
|
export default dbConnection.connect.bind(dbConnection);
|
|
126
154
|
export const getDBStatus = dbConnection.getConnectionStatus.bind(dbConnection);
|
|
155
|
+
export const resetDBConnection = dbConnection.resetAndRetry.bind(dbConnection);
|
|
127
156
|
export { DatabaseConnection };
|
|
@@ -5,8 +5,9 @@ import { Response } from "express";
|
|
|
5
5
|
* @param {any} data - Response data.
|
|
6
6
|
* @param {string} [message='Success'] - Response message.
|
|
7
7
|
* @param {number} [statusCode=200] - HTTP status code.
|
|
8
|
+
* @returns {Response} The Express response object for chaining.
|
|
8
9
|
*/
|
|
9
|
-
export declare const sendSuccess: (res: Response, data: any, message?: string, statusCode?: number) =>
|
|
10
|
+
export declare const sendSuccess: (res: Response, data: any, message?: string, statusCode?: number) => Response;
|
|
10
11
|
/**
|
|
11
12
|
* Sends an error response.
|
|
12
13
|
* @param {Response} res - Express response object.
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @param {any} data - Response data.
|
|
5
5
|
* @param {string} [message='Success'] - Response message.
|
|
6
6
|
* @param {number} [statusCode=200] - HTTP status code.
|
|
7
|
+
* @returns {Response} The Express response object for chaining.
|
|
7
8
|
*/
|
|
8
9
|
export const sendSuccess = (res, data, message = "Success", statusCode = 200) => {
|
|
9
10
|
const response = {
|
|
@@ -11,7 +12,7 @@ export const sendSuccess = (res, data, message = "Success", statusCode = 200) =>
|
|
|
11
12
|
message,
|
|
12
13
|
data,
|
|
13
14
|
};
|
|
14
|
-
res.status(statusCode).json(response);
|
|
15
|
+
return res.status(statusCode).json(response);
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
17
18
|
* Sends an error response.
|
package/package.json
CHANGED
package/dist/AppError.1.d.ts
DELETED
|
File without changes
|
package/dist/AppError.1.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
package/dist/AppError.2.d.ts
DELETED
|
File without changes
|
package/dist/AppError.2.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
package/dist/authWrapper.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { JwtPayload, Algorithm } from "jsonwebtoken";
|
|
2
|
-
import { Request, Response, NextFunction } from "express";
|
|
3
|
-
interface AuthOptions {
|
|
4
|
-
secret: string;
|
|
5
|
-
algorithms?: Algorithm[];
|
|
6
|
-
}
|
|
7
|
-
interface AuthenticatedRequest extends Request {
|
|
8
|
-
user?: JwtPayload | string;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Middleware to require JWT authentication.
|
|
12
|
-
* Verifies the JWT token from Authorization header and attaches decoded payload to req.user.
|
|
13
|
-
* @param {AuthOptions} options - JWT verification options.
|
|
14
|
-
* @returns {(req: Request, res: Response, next: NextFunction) => void} - Middleware function.
|
|
15
|
-
*/
|
|
16
|
-
export declare const requireAuth: (options: AuthOptions) => (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
|
|
17
|
-
export {};
|
package/dist/authWrapper.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import jwt, { JsonWebTokenError, TokenExpiredError, } from "jsonwebtoken";
|
|
2
|
-
import { AppError } from "./AppError.js";
|
|
3
|
-
const defaultAlgorithms = ["HS256"];
|
|
4
|
-
/**
|
|
5
|
-
* Middleware to require JWT authentication.
|
|
6
|
-
* Verifies the JWT token from Authorization header and attaches decoded payload to req.user.
|
|
7
|
-
* @param {AuthOptions} options - JWT verification options.
|
|
8
|
-
* @returns {(req: Request, res: Response, next: NextFunction) => void} - Middleware function.
|
|
9
|
-
*/
|
|
10
|
-
export const requireAuth = (options) => {
|
|
11
|
-
return (req, res, next) => {
|
|
12
|
-
const authHeader = req.headers.authorization;
|
|
13
|
-
if (!authHeader) {
|
|
14
|
-
throw new AppError("Access denied. No token provided.", 401);
|
|
15
|
-
}
|
|
16
|
-
if (!authHeader.startsWith("Bearer ")) {
|
|
17
|
-
throw new AppError("Malformed authorization header.", 401);
|
|
18
|
-
}
|
|
19
|
-
const token = authHeader.substring(7);
|
|
20
|
-
if (!token) {
|
|
21
|
-
throw new AppError("Access denied. No token provided.", 401);
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
let decoded;
|
|
25
|
-
if (options.algorithms) {
|
|
26
|
-
decoded = jwt.verify(token, options.secret, {
|
|
27
|
-
algorithms: options.algorithms,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
decoded = jwt.verify(token, options.secret);
|
|
32
|
-
}
|
|
33
|
-
req.user = decoded;
|
|
34
|
-
next();
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
if (error instanceof JsonWebTokenError) {
|
|
38
|
-
throw new AppError("Invalid token.", 401);
|
|
39
|
-
}
|
|
40
|
-
else if (error instanceof TokenExpiredError) {
|
|
41
|
-
throw new AppError("Token expired.", 401);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
throw new AppError("Authentication failed.", 401);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { ValidationChain } from 'express-validator';
|
|
2
|
-
import { Request, Response, NextFunction } from 'express';
|
|
3
|
-
/**
|
|
4
|
-
* Middleware wrapper for express-validator validations.
|
|
5
|
-
* Runs the provided validations and throws AppError if any fail.
|
|
6
|
-
* @param {ValidationChain[]} validations - Array of express-validator validation chains.
|
|
7
|
-
* @returns {(req: Request, res: Response, next: NextFunction) => Promise<void>} - Middleware function.
|
|
8
|
-
*/
|
|
9
|
-
export declare const validateRequest: (validations: ValidationChain[]) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
package/dist/validateRequest.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { validationResult } from 'express-validator';
|
|
2
|
-
import { AppError } from './AppError.js';
|
|
3
|
-
/**
|
|
4
|
-
* Middleware wrapper for express-validator validations.
|
|
5
|
-
* Runs the provided validations and throws AppError if any fail.
|
|
6
|
-
* @param {ValidationChain[]} validations - Array of express-validator validation chains.
|
|
7
|
-
* @returns {(req: Request, res: Response, next: NextFunction) => Promise<void>} - Middleware function.
|
|
8
|
-
*/
|
|
9
|
-
export const validateRequest = (validations) => {
|
|
10
|
-
return async (req, res, next) => {
|
|
11
|
-
await Promise.all(validations.map(validation => validation.run(req)));
|
|
12
|
-
const errors = validationResult(req);
|
|
13
|
-
if (!errors.isEmpty()) {
|
|
14
|
-
const errorMessages = errors.array().map(err => err.msg);
|
|
15
|
-
throw new AppError('Validation failed', 400, errorMessages);
|
|
16
|
-
}
|
|
17
|
-
next();
|
|
18
|
-
};
|
|
19
|
-
};
|