devdad-express-utils 1.5.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 CHANGED
@@ -73,19 +73,37 @@ sendError(res, "User not found", 404);
73
73
 
74
74
  ### Database Connection
75
75
 
76
- MongoDB connection utility with automatic reconnection and retry logic.
76
+ MongoDB connection utility with automatic reconnection, exponential backoff, and configurable retry logic.
77
77
 
78
78
  ```typescript
79
- import { connectDB, getDBStatus } from "devdad-express-utils";
79
+ import { connectDB, getDBStatus, resetDBConnection } from "devdad-express-utils";
80
80
 
81
81
  // Connect to MongoDB (ensure MONGO_URI is set in environment)
82
82
  await connectDB();
83
83
 
84
84
  // Check connection status
85
85
  const status = getDBStatus();
86
- 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();
87
90
  ```
88
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
+
89
107
  ### Logging
90
108
 
91
109
  Winston-based logger with configurable service name and environment-aware transports.
@@ -224,7 +242,15 @@ connectDB() => Promise<void>
224
242
  Gets the current MongoDB connection status.
225
243
 
226
244
  ```typescript
227
- 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>
228
254
  ```
229
255
 
230
256
  ### logger
@@ -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
- * Exits the process if all retries fail.
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 = 3;
6
+ const MAX_RETRIES = parseInt(process.env.DB_MAX_RETRIES || '10');
6
7
  /**
7
- * Interval between retry attempts in milliseconds
8
+ * Initial interval between retry attempts in milliseconds
9
+ * Can be overridden with DB_RETRY_INTERVAL environment variable
8
10
  */
9
- const RETRY_INTERVAL = 5000; // 5 seconds
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
- * Exits the process if all retries fail.
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
- console.log(`Retrying connection... Attempt ${this.retryCount} of ${MAX_RETRIES}`);
78
- await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL));
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
- process.exit(1);
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.connect();
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devdad-express-utils",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Reusable Express.js utilities for error handling, async wrapping, and more",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
File without changes
@@ -1 +0,0 @@
1
- "use strict";
File without changes
@@ -1 +0,0 @@
1
- "use strict";
@@ -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 {};
@@ -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>;
@@ -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
- };