@naman_deep_singh/server-utils 1.1.0 → 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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # @naman_deep_singh/server-utils
2
2
 
3
- **Version:** 1.1.0
3
+ **Version:** 1.2.0 (with integrated cache & session support)
4
4
 
5
- Extensible server utilities for Express.js microservices with multi-protocol support and TypeScript.
5
+ Extensible server utilities for Express.js microservices with multi-protocol support, integrated caching, session management, and TypeScript.
6
6
 
7
7
  ## Installation
8
8
 
@@ -13,13 +13,15 @@ npm install @naman_deep_singh/server-utils
13
13
  ## Features
14
14
 
15
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
16
18
  - ✅ **Express.js integration** with middleware collection
17
- - ✅ **Graceful shutdown** handling
18
- - ✅ **Health checks** with custom checks support
19
+ - ✅ **Graceful shutdown** handling with cache/session cleanup
20
+ - ✅ **Health checks** with custom checks and cache health integration
19
21
  - ✅ **TypeScript support** with full type safety
20
22
  - ✅ **Hybrid exports** - use named imports or namespace imports
21
23
  - ✅ **Plugin architecture** for extensibility
22
- - ✅ **Built-in middleware** - logging, validation, rate limiting, auth
24
+ - ✅ **Built-in middleware** - logging, validation, rate limiting, auth, caching, sessions
23
25
 
24
26
  ## Quick Start
25
27
 
@@ -418,4 +420,252 @@ server.app.get('/users', (req, res) => {
418
420
  const responder = (res as any).responder();
419
421
  return responder.okAndSend({ users: [] });
420
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
+ });
421
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';
package/dist/cjs/index.js CHANGED
@@ -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) => {
@@ -244,3 +246,101 @@ function rateLimit(config = {}) {
244
246
  function requireAuth(config) {
245
247
  return createAuthMiddleware(config);
246
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
+ }
@@ -57,6 +57,7 @@ export declare class ExpressServer implements ServerInstance {
57
57
  private healthMonitor?;
58
58
  constructor(name?: string, version?: string, config?: ServerConfig);
59
59
  private setupMiddleware;
60
+ private setupCacheAndSession;
60
61
  private setupPeriodicHealthMonitoring;
61
62
  start(): Promise<ServerInstance>;
62
63
  stop(): Promise<void>;
@@ -9,6 +9,7 @@ const express_1 = __importDefault(require("express"));
9
9
  const shutdown_1 = require("./shutdown");
10
10
  const periodic_health_1 = require("./periodic-health");
11
11
  const crypto_1 = __importDefault(require("crypto"));
12
+ const cache_1 = require("@naman_deep_singh/cache");
12
13
  class ExpressServer {
13
14
  constructor(name = 'Express Server', version = '1.0.0', config = {}) {
14
15
  this.status = 'stopped';
@@ -28,8 +29,14 @@ class ExpressServer {
28
29
  healthCheck: config.healthCheck ?? true,
29
30
  gracefulShutdown: config.gracefulShutdown ?? true,
30
31
  socketIO: config.socketIO,
31
- periodicHealthCheck: config.periodicHealthCheck || { enabled: false }
32
+ periodicHealthCheck: config.periodicHealthCheck || { enabled: false },
33
+ cache: config.cache || { enabled: false },
34
+ session: config.session || { enabled: false }
32
35
  };
36
+ // Initialize locals for cache/session
37
+ this.app.locals.cache = undefined;
38
+ this.app.locals.sessionStore = undefined;
39
+ this.app.locals.cacheDefaultTTL = config.cache?.defaultTTL;
33
40
  // Apply middleware based on configuration
34
41
  this.setupMiddleware();
35
42
  // Setup periodic health monitoring
@@ -80,17 +87,104 @@ class ExpressServer {
80
87
  // Add health check if enabled
81
88
  if (this.config.healthCheck) {
82
89
  const healthPath = typeof this.config.healthCheck === 'string' ? this.config.healthCheck : '/health';
83
- this.app.get(healthPath, (req, res) => {
84
- res.status(200).json({
90
+ this.app.get(healthPath, async (req, res) => {
91
+ const base = {
85
92
  status: 'healthy',
86
93
  service: this.config.name,
87
94
  version: this.config.version,
88
95
  uptime: Date.now() - this.config.startTime.getTime(),
89
96
  timestamp: new Date().toISOString()
90
- });
97
+ };
98
+ // If cache is enabled, include its health
99
+ const cache = req.app.locals.cache;
100
+ if (cache && typeof cache.isAlive === 'function') {
101
+ try {
102
+ base.cache = await cache.isAlive();
103
+ }
104
+ catch (e) {
105
+ base.cache = { isAlive: false, adapter: 'unknown', timestamp: new Date(), error: e.message };
106
+ }
107
+ }
108
+ res.status(200).json(base);
91
109
  });
92
110
  }
93
111
  }
112
+ async setupCacheAndSession(config, serverName) {
113
+ try {
114
+ // Initialize cache if enabled
115
+ if (config.cache && config.cache.enabled) {
116
+ try {
117
+ const provided = config.cache.options;
118
+ let cacheConfig = provided && typeof provided === 'object' ? provided : undefined;
119
+ if (!cacheConfig) {
120
+ cacheConfig = { adapter: config.cache.adapter || 'memory' };
121
+ }
122
+ console.log(`🔄 [${serverName}] Initializing cache adapter: ${config.cache.adapter || 'memory'}...`);
123
+ // Use createWithFallback to prefer primary and fall back to memory when configured
124
+ const cache = await cache_1.CacheFactory.createWithFallback({
125
+ ...(cacheConfig || {}),
126
+ ttl: cacheConfig?.ttl ?? config.cache?.defaultTTL
127
+ });
128
+ this.app.locals.cache = cache;
129
+ this.cache = cache;
130
+ this.app.locals.cacheDefaultTTL = config.cache?.defaultTTL;
131
+ // attach per-request helper middleware
132
+ this.app.use((req, _res, next) => {
133
+ req.cache = cache;
134
+ next();
135
+ });
136
+ console.log(`✅ [${serverName}] Cache initialized successfully (adapter: ${(cacheConfig.adapter || 'memory')})`);
137
+ }
138
+ catch (err) {
139
+ console.error(`❌ [${serverName}] Failed to initialize cache (fallback to memory if enabled):`, err instanceof Error ? err.message : err);
140
+ // Cache initialization error is critical but we continue to allow graceful fallback
141
+ }
142
+ }
143
+ // Initialize session if enabled
144
+ if (config.session && config.session.enabled) {
145
+ const cookieName = config.session.cookieName || `${serverName.replace(/\s+/g, '_').toLowerCase()}.sid`;
146
+ const ttl = config.session.ttl ?? 3600;
147
+ let cache = this.app.locals.cache;
148
+ if (!cache) {
149
+ // fallback to in-memory cache for session store
150
+ try {
151
+ cache = cache_1.CacheFactory.create({ adapter: 'memory' });
152
+ this.app.locals.cache = cache;
153
+ this.cache = cache;
154
+ console.log(`📝 [${serverName}] Session store using in-memory cache`);
155
+ }
156
+ catch (e) {
157
+ console.error(`❌ [${serverName}] Failed to create in-memory cache for sessions:`, e instanceof Error ? e.message : e);
158
+ }
159
+ }
160
+ else {
161
+ console.log(`📝 [${serverName}] Session store initialized with configured cache adapter`);
162
+ }
163
+ if (!cache) {
164
+ console.error(`❌ [${serverName}] CRITICAL: Session enabled but no cache available to store sessions. Session functionality will be unavailable.`);
165
+ }
166
+ else {
167
+ const store = new cache_1.SessionStore(cache, { ttl });
168
+ this.app.locals.sessionStore = store;
169
+ this.app.locals.sessionCookieName = cookieName;
170
+ this.sessionStore = store;
171
+ // attach session middleware globally so req.sessionStore is available
172
+ try {
173
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
174
+ const { useSession } = require('./middleware');
175
+ this.app.use(useSession(cookieName));
176
+ console.log(`✅ [${serverName}] Session middleware enabled (cookie: ${cookieName}, TTL: ${ttl}s)`);
177
+ }
178
+ catch (err) {
179
+ console.error(`❌ [${serverName}] Session middleware not available:`, err instanceof Error ? err.message : err);
180
+ }
181
+ }
182
+ }
183
+ }
184
+ catch (err) {
185
+ console.error(`❌ [${serverName}] Error during cache/session setup:`, err instanceof Error ? err.message : err);
186
+ }
187
+ }
94
188
  setupPeriodicHealthMonitoring() {
95
189
  if (this.config.periodicHealthCheck?.enabled) {
96
190
  this.healthMonitor = new periodic_health_1.PeriodicHealthMonitor(this.config.periodicHealthCheck, this.config.name);
@@ -98,6 +192,8 @@ class ExpressServer {
98
192
  }
99
193
  async start() {
100
194
  this.status = 'starting';
195
+ // Initialize cache and session before starting the server
196
+ await this.setupCacheAndSession(this.config, this.config.name);
101
197
  return new Promise((resolve, reject) => {
102
198
  try {
103
199
  this.server = this.app.listen(this.config.port, () => {
@@ -111,6 +207,25 @@ class ExpressServer {
111
207
  if (this.healthMonitor) {
112
208
  this.healthMonitor.stop();
113
209
  }
210
+ // Close cache and session store if present
211
+ try {
212
+ const cache = this.app.locals.cache;
213
+ if (cache && typeof cache.close === 'function') {
214
+ await cache.close();
215
+ }
216
+ }
217
+ catch (e) {
218
+ console.warn(`${this.config.name}: Error closing cache`, e);
219
+ }
220
+ try {
221
+ const store = this.app.locals.sessionStore;
222
+ if (store && typeof store.close === 'function') {
223
+ await store.close();
224
+ }
225
+ }
226
+ catch (e) {
227
+ // SessionStore may not have close; ignore
228
+ }
114
229
  }
115
230
  });
116
231
  }
@@ -22,6 +22,23 @@ export interface ServerConfig {
22
22
  periodicHealthCheck?: PeriodicHealthCheckConfig;
23
23
  name?: string;
24
24
  version?: string;
25
+ cache?: {
26
+ enabled?: boolean;
27
+ adapter?: 'redis' | 'memcache' | 'memory';
28
+ options?: unknown;
29
+ defaultTTL?: number;
30
+ };
31
+ session?: {
32
+ enabled?: boolean;
33
+ cookieName?: string;
34
+ ttl?: number;
35
+ cookieOptions?: {
36
+ path?: string;
37
+ httpOnly?: boolean;
38
+ secure?: boolean;
39
+ sameSite?: 'lax' | 'strict' | 'none';
40
+ };
41
+ };
25
42
  }
26
43
  export interface PeriodicHealthCheckConfig {
27
44
  enabled?: boolean;
@@ -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';
package/dist/esm/index.js CHANGED
@@ -7,7 +7,7 @@ export { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
7
7
  // Graceful shutdown utilities
8
8
  export { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
9
9
  // Middleware utilities
10
- export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth } from './middleware';
10
+ export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, cacheResponse, useSession } from './middleware';
11
11
  // Utility functions
12
12
  export { getEnv, getEnvNumber, getEnvBoolean } from './utils';
13
13
  // Periodic health monitoring
@@ -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;
@@ -227,3 +227,101 @@ export function rateLimit(config = {}) {
227
227
  export function requireAuth(config) {
228
228
  return createAuthMiddleware(config);
229
229
  }
230
+ // Cache response middleware (per-route opt-in)
231
+ export function cacheResponse(ttl) {
232
+ return async (req, res, next) => {
233
+ try {
234
+ if (req.method !== 'GET')
235
+ return next();
236
+ const cache = req.cache || req.app.locals.cache;
237
+ const defaultTTL = req.app.locals.cacheDefaultTTL;
238
+ if (!cache)
239
+ return next();
240
+ const key = `${req.originalUrl}`;
241
+ try {
242
+ const cached = await cache.get(key);
243
+ if (cached !== null && cached !== undefined) {
244
+ res.setHeader('X-Cache', 'HIT');
245
+ return res.json(cached);
246
+ }
247
+ }
248
+ catch (cacheErr) {
249
+ console.error(`[Cache] Failed to retrieve key "${key}":`, cacheErr);
250
+ // Continue without cache hit
251
+ }
252
+ const originalJson = res.json.bind(res);
253
+ res.json = (body) => {
254
+ try {
255
+ const expiry = ttl ?? defaultTTL;
256
+ if (expiry && cache) {
257
+ cache.set(key, body, expiry).catch((err) => {
258
+ console.error(`[Cache] Failed to set key "${key}" with TTL ${expiry}:`, err);
259
+ });
260
+ }
261
+ else if (cache) {
262
+ cache.set(key, body).catch((err) => {
263
+ console.error(`[Cache] Failed to set key "${key}":`, err);
264
+ });
265
+ }
266
+ }
267
+ catch (e) {
268
+ console.error(`[Cache] Error during cache.set operation:`, e);
269
+ }
270
+ res.setHeader('X-Cache', 'MISS');
271
+ return originalJson(body);
272
+ };
273
+ next();
274
+ }
275
+ catch (err) {
276
+ console.error('[Cache] Unexpected error in cacheResponse middleware:', err);
277
+ next();
278
+ }
279
+ };
280
+ }
281
+ // Session middleware helper (attaches sessionStore and helpers to req)
282
+ export function useSession(cookieName) {
283
+ return async (req, res, next) => {
284
+ try {
285
+ const store = req.app.locals.sessionStore;
286
+ if (!store)
287
+ return next();
288
+ const name = cookieName || req.app.locals.sessionCookieName || 'sid';
289
+ let sid = req.cookies?.[name] || req.cookies?.[name];
290
+ if (!sid) {
291
+ const cookieHeader = req.headers.cookie;
292
+ if (cookieHeader) {
293
+ const match = cookieHeader.split(';').map(s => s.trim()).find(s => s.startsWith(name + '='));
294
+ if (match)
295
+ sid = match.split('=')[1];
296
+ }
297
+ }
298
+ req.sessionId = sid;
299
+ req.sessionStore = store;
300
+ req.getSession = async () => {
301
+ if (!sid)
302
+ return null;
303
+ try {
304
+ return await store.get(sid);
305
+ }
306
+ catch (err) {
307
+ console.error(`[Session] Failed to get session "${sid}":`, err);
308
+ throw err;
309
+ }
310
+ };
311
+ req.createSession = async (id, data, ttl) => {
312
+ try {
313
+ return await store.create(id, data, ttl);
314
+ }
315
+ catch (err) {
316
+ console.error(`[Session] Failed to create session "${id}":`, err);
317
+ throw err;
318
+ }
319
+ };
320
+ next();
321
+ }
322
+ catch (err) {
323
+ console.error('[Session] Unexpected error in useSession middleware:', err);
324
+ next();
325
+ }
326
+ };
327
+ }
@@ -57,6 +57,7 @@ export declare class ExpressServer implements ServerInstance {
57
57
  private healthMonitor?;
58
58
  constructor(name?: string, version?: string, config?: ServerConfig);
59
59
  private setupMiddleware;
60
+ private setupCacheAndSession;
60
61
  private setupPeriodicHealthMonitoring;
61
62
  start(): Promise<ServerInstance>;
62
63
  stop(): Promise<void>;
@@ -2,6 +2,7 @@ import express from 'express';
2
2
  import { createGracefulShutdown } from './shutdown';
3
3
  import { PeriodicHealthMonitor } from './periodic-health';
4
4
  import crypto from 'crypto';
5
+ import { CacheFactory, SessionStore } from '@naman_deep_singh/cache';
5
6
  export class ExpressServer {
6
7
  constructor(name = 'Express Server', version = '1.0.0', config = {}) {
7
8
  this.status = 'stopped';
@@ -21,8 +22,14 @@ export class ExpressServer {
21
22
  healthCheck: config.healthCheck ?? true,
22
23
  gracefulShutdown: config.gracefulShutdown ?? true,
23
24
  socketIO: config.socketIO,
24
- periodicHealthCheck: config.periodicHealthCheck || { enabled: false }
25
+ periodicHealthCheck: config.periodicHealthCheck || { enabled: false },
26
+ cache: config.cache || { enabled: false },
27
+ session: config.session || { enabled: false }
25
28
  };
29
+ // Initialize locals for cache/session
30
+ this.app.locals.cache = undefined;
31
+ this.app.locals.sessionStore = undefined;
32
+ this.app.locals.cacheDefaultTTL = config.cache?.defaultTTL;
26
33
  // Apply middleware based on configuration
27
34
  this.setupMiddleware();
28
35
  // Setup periodic health monitoring
@@ -73,17 +80,104 @@ export class ExpressServer {
73
80
  // Add health check if enabled
74
81
  if (this.config.healthCheck) {
75
82
  const healthPath = typeof this.config.healthCheck === 'string' ? this.config.healthCheck : '/health';
76
- this.app.get(healthPath, (req, res) => {
77
- res.status(200).json({
83
+ this.app.get(healthPath, async (req, res) => {
84
+ const base = {
78
85
  status: 'healthy',
79
86
  service: this.config.name,
80
87
  version: this.config.version,
81
88
  uptime: Date.now() - this.config.startTime.getTime(),
82
89
  timestamp: new Date().toISOString()
83
- });
90
+ };
91
+ // If cache is enabled, include its health
92
+ const cache = req.app.locals.cache;
93
+ if (cache && typeof cache.isAlive === 'function') {
94
+ try {
95
+ base.cache = await cache.isAlive();
96
+ }
97
+ catch (e) {
98
+ base.cache = { isAlive: false, adapter: 'unknown', timestamp: new Date(), error: e.message };
99
+ }
100
+ }
101
+ res.status(200).json(base);
84
102
  });
85
103
  }
86
104
  }
105
+ async setupCacheAndSession(config, serverName) {
106
+ try {
107
+ // Initialize cache if enabled
108
+ if (config.cache && config.cache.enabled) {
109
+ try {
110
+ const provided = config.cache.options;
111
+ let cacheConfig = provided && typeof provided === 'object' ? provided : undefined;
112
+ if (!cacheConfig) {
113
+ cacheConfig = { adapter: config.cache.adapter || 'memory' };
114
+ }
115
+ console.log(`🔄 [${serverName}] Initializing cache adapter: ${config.cache.adapter || 'memory'}...`);
116
+ // Use createWithFallback to prefer primary and fall back to memory when configured
117
+ const cache = await CacheFactory.createWithFallback({
118
+ ...(cacheConfig || {}),
119
+ ttl: cacheConfig?.ttl ?? config.cache?.defaultTTL
120
+ });
121
+ this.app.locals.cache = cache;
122
+ this.cache = cache;
123
+ this.app.locals.cacheDefaultTTL = config.cache?.defaultTTL;
124
+ // attach per-request helper middleware
125
+ this.app.use((req, _res, next) => {
126
+ req.cache = cache;
127
+ next();
128
+ });
129
+ console.log(`✅ [${serverName}] Cache initialized successfully (adapter: ${(cacheConfig.adapter || 'memory')})`);
130
+ }
131
+ catch (err) {
132
+ console.error(`❌ [${serverName}] Failed to initialize cache (fallback to memory if enabled):`, err instanceof Error ? err.message : err);
133
+ // Cache initialization error is critical but we continue to allow graceful fallback
134
+ }
135
+ }
136
+ // Initialize session if enabled
137
+ if (config.session && config.session.enabled) {
138
+ const cookieName = config.session.cookieName || `${serverName.replace(/\s+/g, '_').toLowerCase()}.sid`;
139
+ const ttl = config.session.ttl ?? 3600;
140
+ let cache = this.app.locals.cache;
141
+ if (!cache) {
142
+ // fallback to in-memory cache for session store
143
+ try {
144
+ cache = CacheFactory.create({ adapter: 'memory' });
145
+ this.app.locals.cache = cache;
146
+ this.cache = cache;
147
+ console.log(`📝 [${serverName}] Session store using in-memory cache`);
148
+ }
149
+ catch (e) {
150
+ console.error(`❌ [${serverName}] Failed to create in-memory cache for sessions:`, e instanceof Error ? e.message : e);
151
+ }
152
+ }
153
+ else {
154
+ console.log(`📝 [${serverName}] Session store initialized with configured cache adapter`);
155
+ }
156
+ if (!cache) {
157
+ console.error(`❌ [${serverName}] CRITICAL: Session enabled but no cache available to store sessions. Session functionality will be unavailable.`);
158
+ }
159
+ else {
160
+ const store = new SessionStore(cache, { ttl });
161
+ this.app.locals.sessionStore = store;
162
+ this.app.locals.sessionCookieName = cookieName;
163
+ this.sessionStore = store;
164
+ // attach session middleware globally so req.sessionStore is available
165
+ try {
166
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
167
+ const { useSession } = require('./middleware');
168
+ this.app.use(useSession(cookieName));
169
+ console.log(`✅ [${serverName}] Session middleware enabled (cookie: ${cookieName}, TTL: ${ttl}s)`);
170
+ }
171
+ catch (err) {
172
+ console.error(`❌ [${serverName}] Session middleware not available:`, err instanceof Error ? err.message : err);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ catch (err) {
178
+ console.error(`❌ [${serverName}] Error during cache/session setup:`, err instanceof Error ? err.message : err);
179
+ }
180
+ }
87
181
  setupPeriodicHealthMonitoring() {
88
182
  if (this.config.periodicHealthCheck?.enabled) {
89
183
  this.healthMonitor = new PeriodicHealthMonitor(this.config.periodicHealthCheck, this.config.name);
@@ -91,6 +185,8 @@ export class ExpressServer {
91
185
  }
92
186
  async start() {
93
187
  this.status = 'starting';
188
+ // Initialize cache and session before starting the server
189
+ await this.setupCacheAndSession(this.config, this.config.name);
94
190
  return new Promise((resolve, reject) => {
95
191
  try {
96
192
  this.server = this.app.listen(this.config.port, () => {
@@ -104,6 +200,25 @@ export class ExpressServer {
104
200
  if (this.healthMonitor) {
105
201
  this.healthMonitor.stop();
106
202
  }
203
+ // Close cache and session store if present
204
+ try {
205
+ const cache = this.app.locals.cache;
206
+ if (cache && typeof cache.close === 'function') {
207
+ await cache.close();
208
+ }
209
+ }
210
+ catch (e) {
211
+ console.warn(`${this.config.name}: Error closing cache`, e);
212
+ }
213
+ try {
214
+ const store = this.app.locals.sessionStore;
215
+ if (store && typeof store.close === 'function') {
216
+ await store.close();
217
+ }
218
+ }
219
+ catch (e) {
220
+ // SessionStore may not have close; ignore
221
+ }
107
222
  }
108
223
  });
109
224
  }
@@ -22,6 +22,23 @@ export interface ServerConfig {
22
22
  periodicHealthCheck?: PeriodicHealthCheckConfig;
23
23
  name?: string;
24
24
  version?: string;
25
+ cache?: {
26
+ enabled?: boolean;
27
+ adapter?: 'redis' | 'memcache' | 'memory';
28
+ options?: unknown;
29
+ defaultTTL?: number;
30
+ };
31
+ session?: {
32
+ enabled?: boolean;
33
+ cookieName?: string;
34
+ ttl?: number;
35
+ cookieOptions?: {
36
+ path?: string;
37
+ httpOnly?: boolean;
38
+ secure?: boolean;
39
+ sameSite?: 'lax' | 'strict' | 'none';
40
+ };
41
+ };
25
42
  }
26
43
  export interface PeriodicHealthCheckConfig {
27
44
  enabled?: boolean;
@@ -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';
@@ -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;
@@ -57,6 +57,7 @@ export declare class ExpressServer implements ServerInstance {
57
57
  private healthMonitor?;
58
58
  constructor(name?: string, version?: string, config?: ServerConfig);
59
59
  private setupMiddleware;
60
+ private setupCacheAndSession;
60
61
  private setupPeriodicHealthMonitoring;
61
62
  start(): Promise<ServerInstance>;
62
63
  stop(): Promise<void>;
@@ -22,6 +22,23 @@ export interface ServerConfig {
22
22
  periodicHealthCheck?: PeriodicHealthCheckConfig;
23
23
  name?: string;
24
24
  version?: string;
25
+ cache?: {
26
+ enabled?: boolean;
27
+ adapter?: 'redis' | 'memcache' | 'memory';
28
+ options?: unknown;
29
+ defaultTTL?: number;
30
+ };
31
+ session?: {
32
+ enabled?: boolean;
33
+ cookieName?: string;
34
+ ttl?: number;
35
+ cookieOptions?: {
36
+ path?: string;
37
+ httpOnly?: boolean;
38
+ secure?: boolean;
39
+ sameSite?: 'lax' | 'strict' | 'none';
40
+ };
41
+ };
25
42
  }
26
43
  export interface PeriodicHealthCheckConfig {
27
44
  enabled?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naman_deep_singh/server-utils",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Extensible server utilities for Express.js microservices with TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -27,16 +27,17 @@
27
27
  "author": "Naman Deep Singh",
28
28
  "license": "ISC",
29
29
  "dependencies": {
30
- "express": "^5.1.0",
31
- "cors": "^2.8.5",
32
- "helmet": "^8.1.0",
30
+ "@naman_deep_singh/cache": "^1.2.0",
31
+ "@types/express": "^5.0.5",
33
32
  "cookie-parser": "^1.4.6",
34
- "@types/express": "^5.0.5"
33
+ "cors": "^2.8.5",
34
+ "express": "^5.1.0",
35
+ "helmet": "^8.1.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/cors": "^2.8.19",
38
- "typescript": "^5.9.3",
39
- "rimraf": "^5.0.5"
39
+ "rimraf": "^5.0.5",
40
+ "typescript": "^5.9.3"
40
41
  },
41
42
  "scripts": {
42
43
  "build": "pnpm run build:types && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json",