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 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: 'John' }, 'User fetched', 200);
63
+ sendSuccess(res, { id: 1, name: "John" }, "User fetched", 200);
64
64
 
65
- // Error response
66
- sendError(res, 'User not found', 404);
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
- const authMiddleware = requireAuth({ secret: process.env.JWT_SECRET });
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) => void
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
- * 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 };
@@ -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) => void;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devdad-express-utils",
3
- "version": "1.4.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
- };