@naman_deep_singh/server-utils 1.0.8 → 1.2.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 +401 -11
- package/dist/{index.d.ts → cjs/index.d.ts} +1 -1
- package/dist/{index.js → cjs/index.js} +3 -1
- package/dist/{middleware.d.ts → cjs/middleware.d.ts} +2 -0
- package/dist/{middleware.js → cjs/middleware.js} +137 -10
- package/dist/{server.d.ts → cjs/server.d.ts} +1 -0
- package/dist/{server.js → cjs/server.js} +119 -4
- package/dist/{types.d.ts → cjs/types.d.ts} +17 -0
- package/dist/{utils.js → cjs/utils.js} +14 -8
- package/dist/esm/health.d.ts +5 -0
- package/dist/esm/health.js +40 -0
- package/dist/esm/index.d.ts +46 -0
- package/dist/esm/index.js +58 -0
- package/dist/esm/middleware.d.ts +39 -0
- package/dist/esm/middleware.js +327 -0
- package/dist/esm/periodic-health.d.ts +11 -0
- package/dist/esm/periodic-health.js +64 -0
- package/dist/esm/server.d.ts +70 -0
- package/dist/esm/server.js +386 -0
- package/dist/esm/shutdown.d.ts +5 -0
- package/dist/esm/shutdown.js +52 -0
- package/dist/esm/types.d.ts +87 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +38 -0
- package/dist/types/health.d.ts +5 -0
- package/dist/types/index.d.ts +46 -0
- package/dist/types/middleware.d.ts +39 -0
- package/dist/types/periodic-health.d.ts +11 -0
- package/dist/types/server.d.ts +70 -0
- package/dist/types/shutdown.d.ts +5 -0
- package/dist/types/types.d.ts +87 -0
- package/dist/types/utils.d.ts +3 -0
- package/package.json +26 -10
- package/src/health.ts +0 -47
- package/src/index.ts +0 -126
- package/src/middleware.ts +0 -275
- package/src/periodic-health.ts +0 -83
- package/src/server.ts +0 -412
- package/src/shutdown.ts +0 -69
- package/src/types.ts +0 -80
- package/src/utils.ts +0 -34
- package/tsconfig.json +0 -21
- /package/dist/{health.d.ts → cjs/health.d.ts} +0 -0
- /package/dist/{health.js → cjs/health.js} +0 -0
- /package/dist/{periodic-health.d.ts → cjs/periodic-health.d.ts} +0 -0
- /package/dist/{periodic-health.js → cjs/periodic-health.js} +0 -0
- /package/dist/{shutdown.d.ts → cjs/shutdown.d.ts} +0 -0
- /package/dist/{shutdown.js → cjs/shutdown.js} +0 -0
- /package/dist/{types.js → cjs/types.js} +0 -0
- /package/dist/{utils.d.ts → cjs/utils.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @naman_deep_singh/server-utils
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Version:** 1.2.0 (with integrated cache & session support)
|
|
4
|
+
|
|
5
|
+
Extensible server utilities for Express.js microservices with multi-protocol support, integrated caching, session management, and TypeScript.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -11,13 +13,15 @@ npm install @naman_deep_singh/server-utils
|
|
|
11
13
|
## Features
|
|
12
14
|
|
|
13
15
|
- ✅ **Multi-protocol support** - HTTP, gRPC, JSON-RPC, WebSockets, Webhooks
|
|
16
|
+
- ✅ **Integrated caching** - Redis, Memcache, in-memory with automatic fallback
|
|
17
|
+
- ✅ **Session management** - Distributed session store with configurable TTL
|
|
14
18
|
- ✅ **Express.js integration** with middleware collection
|
|
15
|
-
- ✅ **Graceful shutdown** handling
|
|
16
|
-
- ✅ **Health checks** with custom checks
|
|
19
|
+
- ✅ **Graceful shutdown** handling with cache/session cleanup
|
|
20
|
+
- ✅ **Health checks** with custom checks and cache health integration
|
|
17
21
|
- ✅ **TypeScript support** with full type safety
|
|
18
22
|
- ✅ **Hybrid exports** - use named imports or namespace imports
|
|
19
23
|
- ✅ **Plugin architecture** for extensibility
|
|
20
|
-
- ✅ **Built-in middleware** - logging, validation, rate limiting, auth
|
|
24
|
+
- ✅ **Built-in middleware** - logging, validation, rate limiting, auth, caching, sessions
|
|
21
25
|
|
|
22
26
|
## Quick Start
|
|
23
27
|
|
|
@@ -180,9 +184,24 @@ server.app.use('/protected', requireAuth({
|
|
|
180
184
|
|
|
181
185
|
## Health Checks
|
|
182
186
|
|
|
187
|
+
### Basic Health Check
|
|
183
188
|
```typescript
|
|
184
|
-
import { addHealthCheck } from '@naman_deep_singh/server-utils';
|
|
189
|
+
import { addHealthCheck, createHealthCheck, withHealthCheck } from '@naman_deep_singh/server-utils';
|
|
190
|
+
|
|
191
|
+
// Method 1: Direct addition
|
|
192
|
+
addHealthCheck(server.app, '/health');
|
|
193
|
+
|
|
194
|
+
// Method 2: As middleware
|
|
195
|
+
server.app.get('/health', createHealthCheck());
|
|
196
|
+
|
|
197
|
+
// Method 3: As plugin
|
|
198
|
+
const server = createServerWithPlugins('My API', '1.0.0', [
|
|
199
|
+
withHealthCheck('/health')
|
|
200
|
+
]);
|
|
201
|
+
```
|
|
185
202
|
|
|
203
|
+
### Advanced Health Checks
|
|
204
|
+
```typescript
|
|
186
205
|
addHealthCheck(server.app, '/health', {
|
|
187
206
|
customChecks: [
|
|
188
207
|
{
|
|
@@ -200,6 +219,17 @@ addHealthCheck(server.app, '/health', {
|
|
|
200
219
|
}
|
|
201
220
|
]
|
|
202
221
|
});
|
|
222
|
+
|
|
223
|
+
// Response format:
|
|
224
|
+
// {
|
|
225
|
+
// "status": "healthy",
|
|
226
|
+
// "checks": {
|
|
227
|
+
// "server": true,
|
|
228
|
+
// "timestamp": 1640995200000,
|
|
229
|
+
// "database": true,
|
|
230
|
+
// "redis": false
|
|
231
|
+
// }
|
|
232
|
+
// }
|
|
203
233
|
```
|
|
204
234
|
|
|
205
235
|
## Server Management
|
|
@@ -246,12 +276,78 @@ interface ServerConfig {
|
|
|
246
276
|
}
|
|
247
277
|
```
|
|
248
278
|
|
|
279
|
+
## Environment Utilities
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { getEnv, getEnvNumber, getEnvBoolean } from '@naman_deep_singh/server-utils';
|
|
283
|
+
|
|
284
|
+
// Get string environment variable
|
|
285
|
+
const dbUrl = getEnv('DATABASE_URL'); // Throws if missing
|
|
286
|
+
const dbUrl = getEnv('DATABASE_URL', 'localhost'); // With default
|
|
287
|
+
|
|
288
|
+
// Get number environment variable
|
|
289
|
+
const port = getEnvNumber('PORT', 3000);
|
|
290
|
+
const maxConnections = getEnvNumber('MAX_CONNECTIONS'); // Throws if missing
|
|
291
|
+
|
|
292
|
+
// Get boolean environment variable
|
|
293
|
+
const enableLogging = getEnvBoolean('ENABLE_LOGGING', true);
|
|
294
|
+
const isProduction = getEnvBoolean('NODE_ENV'); // Must be 'true' or 'false'
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Periodic Health Monitoring
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { PeriodicHealthMonitor } from '@naman_deep_singh/server-utils';
|
|
301
|
+
|
|
302
|
+
const server = createServer('My API', '1.0.0', {
|
|
303
|
+
periodicHealthCheck: {
|
|
304
|
+
enabled: true,
|
|
305
|
+
interval: 30000, // 30 seconds
|
|
306
|
+
services: [
|
|
307
|
+
{
|
|
308
|
+
name: 'database',
|
|
309
|
+
url: 'http://localhost:5432/health',
|
|
310
|
+
timeout: 5000
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: 'redis',
|
|
314
|
+
url: 'http://localhost:6379/ping'
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Manual health check
|
|
321
|
+
const monitor = new PeriodicHealthMonitor(config, 'My Service');
|
|
322
|
+
monitor.start();
|
|
323
|
+
const status = await monitor.getHealthStatus();
|
|
324
|
+
console.log(status); // { database: true, redis: false }
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Express Re-exports
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// Import Express types and classes directly from server-utils
|
|
331
|
+
import { Request, Response, NextFunction, Router, Application } from '@naman_deep_singh/server-utils';
|
|
332
|
+
import type { RequestHandler, ErrorRequestHandler } from '@naman_deep_singh/server-utils';
|
|
333
|
+
|
|
334
|
+
// No need to install Express separately in your services
|
|
335
|
+
```
|
|
336
|
+
|
|
249
337
|
## API Reference
|
|
250
338
|
|
|
251
339
|
### Core Functions
|
|
252
340
|
- `createServer(name?, version?, config?)` - Create server instance
|
|
253
341
|
- `ExpressServer` - Server class for advanced usage
|
|
254
342
|
|
|
343
|
+
### Environment Utilities
|
|
344
|
+
- `getEnv(key, defaultValue?)` - Get string environment variable
|
|
345
|
+
- `getEnvNumber(key, defaultValue?)` - Get number environment variable
|
|
346
|
+
- `getEnvBoolean(key, defaultValue?)` - Get boolean environment variable
|
|
347
|
+
|
|
348
|
+
### Health Monitoring
|
|
349
|
+
- `PeriodicHealthMonitor` - Automated service health checking
|
|
350
|
+
|
|
255
351
|
### Middleware Functions
|
|
256
352
|
- `createLoggingMiddleware(format?)` - Request logging
|
|
257
353
|
- `createErrorHandler()` - Error handling
|
|
@@ -260,8 +356,10 @@ interface ServerConfig {
|
|
|
260
356
|
- `createAuthMiddleware(config)` - Authentication
|
|
261
357
|
|
|
262
358
|
### Health & Monitoring
|
|
263
|
-
- `createHealthCheck(config?)` -
|
|
359
|
+
- `createHealthCheck(config?)` - Create health check middleware
|
|
360
|
+
- `withHealthCheck(path?, config?)` - Health check plugin
|
|
264
361
|
- `addHealthCheck(app, path?, config?)` - Add health check to app
|
|
362
|
+
- `PeriodicHealthMonitor` - Automated service health checking
|
|
265
363
|
|
|
266
364
|
### Graceful Shutdown
|
|
267
365
|
- `createGracefulShutdown(server, config?)` - Setup graceful shutdown
|
|
@@ -270,12 +368,304 @@ interface ServerConfig {
|
|
|
270
368
|
## Dependencies
|
|
271
369
|
|
|
272
370
|
### Required
|
|
273
|
-
- **express** - Web framework
|
|
274
|
-
- **cors** - CORS middleware
|
|
275
|
-
- **helmet** - Security middleware
|
|
276
|
-
- **cookie-parser** - Cookie parsing
|
|
371
|
+
- **express** - Web framework (v5.1.0+)
|
|
277
372
|
|
|
278
373
|
### Optional (for specific features)
|
|
374
|
+
- **cors** - CORS middleware (if using CORS)
|
|
375
|
+
- **helmet** - Security middleware (if using Helmet)
|
|
376
|
+
- **cookie-parser** - Cookie parsing (if using cookies)
|
|
279
377
|
- **@grpc/grpc-js** - For gRPC support
|
|
280
378
|
- **jayson** - For JSON-RPC support
|
|
281
|
-
- **socket.io** - For WebSocket support
|
|
379
|
+
- **socket.io** - For WebSocket support
|
|
380
|
+
|
|
381
|
+
## Response Format
|
|
382
|
+
|
|
383
|
+
All middleware responses follow the consistent format:
|
|
384
|
+
|
|
385
|
+
```json
|
|
386
|
+
{
|
|
387
|
+
"success": true/false,
|
|
388
|
+
"message": "Operation message",
|
|
389
|
+
"data": {...} | undefined,
|
|
390
|
+
"error": {
|
|
391
|
+
"message": "Error message",
|
|
392
|
+
"details": {...}
|
|
393
|
+
} | null,
|
|
394
|
+
"meta": {...} | null
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Integration with @naman_deep_singh/response-utils
|
|
399
|
+
|
|
400
|
+
For advanced error handling, use with `@naman_deep_singh/errors-utils`:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
import { expressErrorHandler } from '@naman_deep_singh/errors-utils';
|
|
404
|
+
|
|
405
|
+
// Replace basic error handler with advanced one
|
|
406
|
+
server.app.use(expressErrorHandler);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Integration with @naman_deep_singh/response-utils
|
|
410
|
+
|
|
411
|
+
For consistent API responses, use with `@naman_deep_singh/response-utils`:
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import { responderMiddleware } from '@naman_deep_singh/response-utils';
|
|
415
|
+
|
|
416
|
+
server.app.use(responderMiddleware());
|
|
417
|
+
|
|
418
|
+
// Now use responder in routes
|
|
419
|
+
server.app.get('/users', (req, res) => {
|
|
420
|
+
const responder = (res as any).responder();
|
|
421
|
+
return responder.okAndSend({ users: [] });
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Cache & Session Integration
|
|
426
|
+
|
|
427
|
+
Built-in support for distributed caching and session management (disabled by default).
|
|
428
|
+
|
|
429
|
+
### Enable Redis Cache
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
const server = createServer('My API', '1.0.0', {
|
|
433
|
+
port: 3000,
|
|
434
|
+
cache: {
|
|
435
|
+
enabled: true,
|
|
436
|
+
adapter: 'redis',
|
|
437
|
+
options: {
|
|
438
|
+
host: 'localhost',
|
|
439
|
+
port: 6379,
|
|
440
|
+
password: 'your_password'
|
|
441
|
+
},
|
|
442
|
+
defaultTTL: 3600 // seconds
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Access cache in routes
|
|
447
|
+
server.app.get('/user/:id', async (req, res) => {
|
|
448
|
+
const key = `user:${req.params.id}`;
|
|
449
|
+
const cached = await (req as any).cache.get(key);
|
|
450
|
+
if (cached) return res.json(cached);
|
|
451
|
+
|
|
452
|
+
// Fetch from DB, then cache
|
|
453
|
+
const user = { id: req.params.id, name: 'John' };
|
|
454
|
+
await (req as any).cache.set(key, user, 3600);
|
|
455
|
+
return res.json(user);
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Enable Redis Cluster Cache
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
const server = createServer('My API', '1.0.0', {
|
|
463
|
+
port: 3000,
|
|
464
|
+
cache: {
|
|
465
|
+
enabled: true,
|
|
466
|
+
adapter: 'redis',
|
|
467
|
+
options: {
|
|
468
|
+
cluster: [
|
|
469
|
+
{ host: 'redis-node-1', port: 6379 },
|
|
470
|
+
{ host: 'redis-node-2', port: 6379 },
|
|
471
|
+
{ host: 'redis-node-3', port: 6379 }
|
|
472
|
+
]
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Enable Memcache
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
const server = createServer('My API', '1.0.0', {
|
|
482
|
+
port: 3000,
|
|
483
|
+
cache: {
|
|
484
|
+
enabled: true,
|
|
485
|
+
adapter: 'memcache',
|
|
486
|
+
options: {
|
|
487
|
+
servers: ['localhost:11211', 'localhost:11212']
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Enable Sessions
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
const server = createServer('My API', '1.0.0', {
|
|
497
|
+
port: 3000,
|
|
498
|
+
cache: {
|
|
499
|
+
enabled: true,
|
|
500
|
+
adapter: 'redis',
|
|
501
|
+
options: { host: 'localhost', port: 6379 }
|
|
502
|
+
},
|
|
503
|
+
session: {
|
|
504
|
+
enabled: true,
|
|
505
|
+
cookieName: 'my_app.sid', // Defaults to {servername}.sid
|
|
506
|
+
ttl: 3600, // 1 hour
|
|
507
|
+
cookieOptions: {
|
|
508
|
+
httpOnly: true,
|
|
509
|
+
secure: true, // HTTPS only
|
|
510
|
+
sameSite: 'strict'
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Use sessions in routes
|
|
516
|
+
server.app.post('/login', async (req, res) => {
|
|
517
|
+
const sessionStore = (req.app as any).locals.sessionStore;
|
|
518
|
+
const sessionId = Math.random().toString(36).substring(7);
|
|
519
|
+
|
|
520
|
+
await sessionStore.create(sessionId, {
|
|
521
|
+
userId: 123,
|
|
522
|
+
username: 'john_doe',
|
|
523
|
+
loginTime: new Date()
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
res.cookie((req.app as any).locals.sessionCookieName, sessionId, {
|
|
527
|
+
httpOnly: true,
|
|
528
|
+
secure: true
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
return res.json({ message: 'Logged in' });
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
server.app.get('/profile', async (req, res) => {
|
|
535
|
+
const sessionId = (req as any).sessionId;
|
|
536
|
+
if (!sessionId) return res.status(401).json({ error: 'No session' });
|
|
537
|
+
|
|
538
|
+
const session = await (req as any).getSession();
|
|
539
|
+
if (!session) return res.status(401).json({ error: 'Session expired' });
|
|
540
|
+
|
|
541
|
+
return res.json({ user: session.username, loginTime: session.loginTime });
|
|
542
|
+
});
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Per-Route Response Caching
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
import { cacheResponse } from '@naman_deep_singh/server-utils';
|
|
549
|
+
|
|
550
|
+
// Cache GET response for 1 hour (3600 seconds)
|
|
551
|
+
server.app.get('/api/posts', cacheResponse(3600), (req, res) => {
|
|
552
|
+
// This endpoint's response is cached
|
|
553
|
+
res.json({ posts: [...] });
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Cache with default TTL from server config
|
|
557
|
+
server.app.get('/api/trending', cacheResponse(), (req, res) => {
|
|
558
|
+
res.json({ trending: [...] });
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Health Check with Cache Status
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
const server = createServer('My API', '1.0.0', {
|
|
566
|
+
healthCheck: '/health', // Automatic health endpoint
|
|
567
|
+
cache: { enabled: true, adapter: 'redis', ... }
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Health endpoint now includes cache status
|
|
571
|
+
// GET /health returns:
|
|
572
|
+
// {
|
|
573
|
+
// "status": "healthy",
|
|
574
|
+
// "service": "My API",
|
|
575
|
+
// "version": "1.0.0",
|
|
576
|
+
// "uptime": 12345,
|
|
577
|
+
// "timestamp": "2025-12-12T...",
|
|
578
|
+
// "cache": {
|
|
579
|
+
// "isAlive": true,
|
|
580
|
+
// "adapter": "redis",
|
|
581
|
+
// "timestamp": "2025-12-12T..."
|
|
582
|
+
// }
|
|
583
|
+
// }
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Cache Configuration Options
|
|
587
|
+
|
|
588
|
+
All configuration is optional — cache and session are disabled by default:
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
interface CacheConfig {
|
|
592
|
+
enabled?: boolean; // Default: false
|
|
593
|
+
adapter?: 'redis' | 'memcache' | 'memory'; // Default: 'memory'
|
|
594
|
+
options?: {
|
|
595
|
+
// Redis single instance
|
|
596
|
+
host?: string; // Default: 'localhost'
|
|
597
|
+
port?: number; // Default: 6379
|
|
598
|
+
username?: string;
|
|
599
|
+
password?: string;
|
|
600
|
+
db?: number; // 0-15
|
|
601
|
+
tls?: boolean;
|
|
602
|
+
|
|
603
|
+
// Redis cluster
|
|
604
|
+
cluster?: Array<{ host: string; port: number }> | {
|
|
605
|
+
nodes: Array<{ host: string; port: number }>;
|
|
606
|
+
options?: { maxRedirections?: number; ... };
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// Memcache
|
|
610
|
+
servers?: string | string[]; // e.g., 'localhost:11211'
|
|
611
|
+
|
|
612
|
+
namespace?: string; // Key prefix
|
|
613
|
+
ttl?: number; // Default TTL in seconds
|
|
614
|
+
};
|
|
615
|
+
defaultTTL?: number; // Fallback TTL for routes
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
interface SessionConfig {
|
|
619
|
+
enabled?: boolean; // Default: false
|
|
620
|
+
cookieName?: string; // Default: {servername}.sid
|
|
621
|
+
ttl?: number; // Default: 3600 (1 hour)
|
|
622
|
+
cookieOptions?: {
|
|
623
|
+
path?: string;
|
|
624
|
+
httpOnly?: boolean; // Default: true
|
|
625
|
+
secure?: boolean; // HTTPS only
|
|
626
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### Graceful Shutdown
|
|
632
|
+
|
|
633
|
+
Cache and session stores are automatically closed on graceful shutdown:
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
const server = createServer('My API', '1.0.0', {
|
|
637
|
+
gracefulShutdown: true, // Enabled by default
|
|
638
|
+
cache: { enabled: true, adapter: 'redis', ... },
|
|
639
|
+
session: { enabled: true, ... }
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// On SIGTERM/SIGINT, server will:
|
|
643
|
+
// 1. Stop accepting requests
|
|
644
|
+
// 2. Close cache connection (Redis/Memcache)
|
|
645
|
+
// 3. Close session store
|
|
646
|
+
// 4. Exit gracefully
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
## Feature Flags
|
|
650
|
+
|
|
651
|
+
All major features can be toggled independently:
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
const server = createServer('My API', '1.0.0', {
|
|
655
|
+
port: 3000,
|
|
656
|
+
cors: true, // Default: true
|
|
657
|
+
helmet: true, // Default: true
|
|
658
|
+
json: true, // Default: true
|
|
659
|
+
cookieParser: false, // Default: false
|
|
660
|
+
healthCheck: '/health', // Default: true
|
|
661
|
+
gracefulShutdown: true, // Default: true
|
|
662
|
+
cache: { enabled: false }, // Default: disabled
|
|
663
|
+
session: { enabled: false }, // Default: disabled
|
|
664
|
+
socketIO: { enabled: false }, // Default: disabled
|
|
665
|
+
periodicHealthCheck: { // Default: disabled
|
|
666
|
+
enabled: false,
|
|
667
|
+
interval: 30000,
|
|
668
|
+
services: [...]
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
```
|
|
@@ -4,7 +4,7 @@ export { Request, Response, NextFunction, Router, Application } from 'express';
|
|
|
4
4
|
export type { RequestHandler, ErrorRequestHandler } from 'express';
|
|
5
5
|
export { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
|
|
6
6
|
export { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
|
|
7
|
-
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, type ValidationRule, type RateLimitConfig, type AuthConfig } from './middleware';
|
|
7
|
+
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, cacheResponse, useSession, type ValidationRule, type RateLimitConfig, type AuthConfig } from './middleware';
|
|
8
8
|
export { getEnv, getEnvNumber, getEnvBoolean } from './utils';
|
|
9
9
|
export { PeriodicHealthMonitor } from './periodic-health';
|
|
10
10
|
export type { ServerConfig, HealthCheckConfig, HealthCheck, GracefulShutdownConfig, ServerPlugin, SocketIOConfig, SocketInstance, PeriodicHealthCheckConfig, HealthCheckService } from './types';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PeriodicHealthMonitor = exports.getEnvBoolean = exports.getEnvNumber = exports.getEnv = exports.requireAuth = exports.rateLimit = exports.validateFields = exports.withAuth = exports.withRateLimit = exports.withValidation = exports.withRequestId = exports.withErrorHandler = exports.withLogging = exports.createAuthMiddleware = exports.createRateLimitMiddleware = exports.createValidationMiddleware = exports.createRequestIdMiddleware = exports.createErrorHandler = exports.createLoggingMiddleware = exports.startServerWithShutdown = exports.withGracefulShutdown = exports.createGracefulShutdown = exports.addHealthCheck = exports.withHealthCheck = exports.createHealthCheck = exports.Router = exports.createServer = exports.ExpressServer = void 0;
|
|
3
|
+
exports.PeriodicHealthMonitor = exports.getEnvBoolean = exports.getEnvNumber = exports.getEnv = exports.useSession = exports.cacheResponse = exports.requireAuth = exports.rateLimit = exports.validateFields = exports.withAuth = exports.withRateLimit = exports.withValidation = exports.withRequestId = exports.withErrorHandler = exports.withLogging = exports.createAuthMiddleware = exports.createRateLimitMiddleware = exports.createValidationMiddleware = exports.createRequestIdMiddleware = exports.createErrorHandler = exports.createLoggingMiddleware = exports.startServerWithShutdown = exports.withGracefulShutdown = exports.createGracefulShutdown = exports.addHealthCheck = exports.withHealthCheck = exports.createHealthCheck = exports.Router = exports.createServer = exports.ExpressServer = void 0;
|
|
4
4
|
// Core server utilities
|
|
5
5
|
var server_1 = require("./server");
|
|
6
6
|
Object.defineProperty(exports, "ExpressServer", { enumerable: true, get: function () { return server_1.ExpressServer; } });
|
|
@@ -35,6 +35,8 @@ Object.defineProperty(exports, "withAuth", { enumerable: true, get: function ()
|
|
|
35
35
|
Object.defineProperty(exports, "validateFields", { enumerable: true, get: function () { return middleware_1.validateFields; } });
|
|
36
36
|
Object.defineProperty(exports, "rateLimit", { enumerable: true, get: function () { return middleware_1.rateLimit; } });
|
|
37
37
|
Object.defineProperty(exports, "requireAuth", { enumerable: true, get: function () { return middleware_1.requireAuth; } });
|
|
38
|
+
Object.defineProperty(exports, "cacheResponse", { enumerable: true, get: function () { return middleware_1.cacheResponse; } });
|
|
39
|
+
Object.defineProperty(exports, "useSession", { enumerable: true, get: function () { return middleware_1.useSession; } });
|
|
38
40
|
// Utility functions
|
|
39
41
|
var utils_1 = require("./utils");
|
|
40
42
|
Object.defineProperty(exports, "getEnv", { enumerable: true, get: function () { return utils_1.getEnv; } });
|
|
@@ -35,3 +35,5 @@ export declare function withAuth(config: AuthConfig): ServerPlugin;
|
|
|
35
35
|
export declare function validateFields(rules: ValidationRule[]): express.RequestHandler;
|
|
36
36
|
export declare function rateLimit(config?: RateLimitConfig): express.RequestHandler;
|
|
37
37
|
export declare function requireAuth(config: AuthConfig): express.RequestHandler;
|
|
38
|
+
export declare function cacheResponse(ttl?: number): express.RequestHandler;
|
|
39
|
+
export declare function useSession(cookieName?: string): express.RequestHandler;
|
|
@@ -15,6 +15,8 @@ exports.withAuth = withAuth;
|
|
|
15
15
|
exports.validateFields = validateFields;
|
|
16
16
|
exports.rateLimit = rateLimit;
|
|
17
17
|
exports.requireAuth = requireAuth;
|
|
18
|
+
exports.cacheResponse = cacheResponse;
|
|
19
|
+
exports.useSession = useSession;
|
|
18
20
|
// Logging middleware
|
|
19
21
|
function createLoggingMiddleware(format = 'simple') {
|
|
20
22
|
return (req, res, next) => {
|
|
@@ -45,9 +47,14 @@ function createErrorHandler() {
|
|
|
45
47
|
? 'Internal Server Error'
|
|
46
48
|
: errorObj.message || 'Unknown error';
|
|
47
49
|
res.status(status).json({
|
|
48
|
-
|
|
50
|
+
success: false,
|
|
49
51
|
message,
|
|
50
|
-
|
|
52
|
+
data: undefined,
|
|
53
|
+
error: {
|
|
54
|
+
message,
|
|
55
|
+
...(process.env.NODE_ENV !== 'production' && { details: { stack: errorObj.stack } })
|
|
56
|
+
},
|
|
57
|
+
meta: null
|
|
51
58
|
});
|
|
52
59
|
};
|
|
53
60
|
}
|
|
@@ -114,9 +121,14 @@ function createValidationMiddleware(rules) {
|
|
|
114
121
|
}
|
|
115
122
|
if (errors.length > 0) {
|
|
116
123
|
return res.status(400).json({
|
|
117
|
-
|
|
124
|
+
success: false,
|
|
118
125
|
message: 'Validation failed',
|
|
119
|
-
|
|
126
|
+
data: undefined,
|
|
127
|
+
error: {
|
|
128
|
+
message: 'Validation failed',
|
|
129
|
+
details: errors
|
|
130
|
+
},
|
|
131
|
+
meta: null
|
|
120
132
|
});
|
|
121
133
|
}
|
|
122
134
|
next();
|
|
@@ -138,9 +150,16 @@ function createRateLimitMiddleware(config = {}) {
|
|
|
138
150
|
}
|
|
139
151
|
if (record.count >= maxRequests) {
|
|
140
152
|
return res.status(429).json({
|
|
141
|
-
|
|
153
|
+
success: false,
|
|
142
154
|
message,
|
|
143
|
-
|
|
155
|
+
data: undefined,
|
|
156
|
+
error: {
|
|
157
|
+
message,
|
|
158
|
+
details: {
|
|
159
|
+
retryAfter: Math.ceil((record.resetTime - now) / 1000)
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
meta: null
|
|
144
163
|
});
|
|
145
164
|
}
|
|
146
165
|
record.count++;
|
|
@@ -160,8 +179,13 @@ function createAuthMiddleware(config) {
|
|
|
160
179
|
const token = tokenExtractor(req);
|
|
161
180
|
if (!token) {
|
|
162
181
|
return res.status(401).json({
|
|
163
|
-
|
|
164
|
-
message: unauthorizedMessage
|
|
182
|
+
success: false,
|
|
183
|
+
message: unauthorizedMessage,
|
|
184
|
+
data: undefined,
|
|
185
|
+
error: {
|
|
186
|
+
message: unauthorizedMessage
|
|
187
|
+
},
|
|
188
|
+
meta: null
|
|
165
189
|
});
|
|
166
190
|
}
|
|
167
191
|
const user = await tokenValidator(token);
|
|
@@ -170,8 +194,13 @@ function createAuthMiddleware(config) {
|
|
|
170
194
|
}
|
|
171
195
|
catch (error) {
|
|
172
196
|
return res.status(401).json({
|
|
173
|
-
|
|
174
|
-
message: unauthorizedMessage
|
|
197
|
+
success: false,
|
|
198
|
+
message: unauthorizedMessage,
|
|
199
|
+
data: undefined,
|
|
200
|
+
error: {
|
|
201
|
+
message: unauthorizedMessage
|
|
202
|
+
},
|
|
203
|
+
meta: null
|
|
175
204
|
});
|
|
176
205
|
}
|
|
177
206
|
};
|
|
@@ -217,3 +246,101 @@ function rateLimit(config = {}) {
|
|
|
217
246
|
function requireAuth(config) {
|
|
218
247
|
return createAuthMiddleware(config);
|
|
219
248
|
}
|
|
249
|
+
// Cache response middleware (per-route opt-in)
|
|
250
|
+
function cacheResponse(ttl) {
|
|
251
|
+
return async (req, res, next) => {
|
|
252
|
+
try {
|
|
253
|
+
if (req.method !== 'GET')
|
|
254
|
+
return next();
|
|
255
|
+
const cache = req.cache || req.app.locals.cache;
|
|
256
|
+
const defaultTTL = req.app.locals.cacheDefaultTTL;
|
|
257
|
+
if (!cache)
|
|
258
|
+
return next();
|
|
259
|
+
const key = `${req.originalUrl}`;
|
|
260
|
+
try {
|
|
261
|
+
const cached = await cache.get(key);
|
|
262
|
+
if (cached !== null && cached !== undefined) {
|
|
263
|
+
res.setHeader('X-Cache', 'HIT');
|
|
264
|
+
return res.json(cached);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (cacheErr) {
|
|
268
|
+
console.error(`[Cache] Failed to retrieve key "${key}":`, cacheErr);
|
|
269
|
+
// Continue without cache hit
|
|
270
|
+
}
|
|
271
|
+
const originalJson = res.json.bind(res);
|
|
272
|
+
res.json = (body) => {
|
|
273
|
+
try {
|
|
274
|
+
const expiry = ttl ?? defaultTTL;
|
|
275
|
+
if (expiry && cache) {
|
|
276
|
+
cache.set(key, body, expiry).catch((err) => {
|
|
277
|
+
console.error(`[Cache] Failed to set key "${key}" with TTL ${expiry}:`, err);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
else if (cache) {
|
|
281
|
+
cache.set(key, body).catch((err) => {
|
|
282
|
+
console.error(`[Cache] Failed to set key "${key}":`, err);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
console.error(`[Cache] Error during cache.set operation:`, e);
|
|
288
|
+
}
|
|
289
|
+
res.setHeader('X-Cache', 'MISS');
|
|
290
|
+
return originalJson(body);
|
|
291
|
+
};
|
|
292
|
+
next();
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
console.error('[Cache] Unexpected error in cacheResponse middleware:', err);
|
|
296
|
+
next();
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// Session middleware helper (attaches sessionStore and helpers to req)
|
|
301
|
+
function useSession(cookieName) {
|
|
302
|
+
return async (req, res, next) => {
|
|
303
|
+
try {
|
|
304
|
+
const store = req.app.locals.sessionStore;
|
|
305
|
+
if (!store)
|
|
306
|
+
return next();
|
|
307
|
+
const name = cookieName || req.app.locals.sessionCookieName || 'sid';
|
|
308
|
+
let sid = req.cookies?.[name] || req.cookies?.[name];
|
|
309
|
+
if (!sid) {
|
|
310
|
+
const cookieHeader = req.headers.cookie;
|
|
311
|
+
if (cookieHeader) {
|
|
312
|
+
const match = cookieHeader.split(';').map(s => s.trim()).find(s => s.startsWith(name + '='));
|
|
313
|
+
if (match)
|
|
314
|
+
sid = match.split('=')[1];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
req.sessionId = sid;
|
|
318
|
+
req.sessionStore = store;
|
|
319
|
+
req.getSession = async () => {
|
|
320
|
+
if (!sid)
|
|
321
|
+
return null;
|
|
322
|
+
try {
|
|
323
|
+
return await store.get(sid);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
console.error(`[Session] Failed to get session "${sid}":`, err);
|
|
327
|
+
throw err;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
req.createSession = async (id, data, ttl) => {
|
|
331
|
+
try {
|
|
332
|
+
return await store.create(id, data, ttl);
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
console.error(`[Session] Failed to create session "${id}":`, err);
|
|
336
|
+
throw err;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
next();
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
console.error('[Session] Unexpected error in useSession middleware:', err);
|
|
343
|
+
next();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|