# Redis Cache System Documentation

## Overview

Global caching system using Redis for API responses, data, and session management.

## File Structure

```
src/cache/
├── redis.config.js          # Redis client configuration & connection
├── cache.service.js         # Global cache service with all operations
├── cache.middleware.js      # Express middleware for automatic caching
└── README.md               # This file
```

## Configuration

### Environment Variables

Add to `.env`:
```env
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=  # Optional, leave empty if no password
```

### Docker (Optional)

```bash
docker run -d --name redis -p 6379:6379 redis:7-alpine
```

## Usage

### 1. Basic Cache Operations

```javascript
import { CacheService } from "../cache/cache.service.js";

// Set a value (no expiry)
await CacheService.set("user:123", { name: "John", email: "john@example.com" });

// Set a value with TTL (time to live in seconds)
await CacheService.set("otp:user123", "123456", 300); // 5 minutes

// Get a value
const user = await CacheService.get("user:123");
// Returns: { name: "John", email: "john@example.com" } or null

// Update a value (preserves TTL)
await CacheService.update("user:123", { name: "Jane", email: "jane@example.com" });

// Delete a value
await CacheService.delete("user:123");

// Delete multiple values
await CacheService.deleteMultiple(["user:123", "user:456"]);

// Check if key exists
const exists = await CacheService.exists("user:123");

// Get TTL remaining
const ttl = await CacheService.ttl("user:123"); // -1 = no expiry, -2 = not found
```

### 2. Advanced Operations

```javascript
// Get multiple values at once
const results = await CacheService.getMultiple(["user:1", "user:2", "user:3"]);
// Returns: { "user:1": {...}, "user:2": {...}, "user:3": {...} }

// Invalidate by pattern
await CacheService.invalidatePattern("user:*");        // All user keys
await CacheService.invalidatePattern("courses:*");     // All course keys

// Get or Set (cache-aside pattern)
const courses = await CacheService.getOrSet(
  "courses:all",
  async () => {
    // This function executes only if not in cache
    return await CourseService.getAll();
  },
  3600 // Cache for 1 hour
);

// Clear all cache (USE WITH CAUTION!)
await CacheService.flushAll();

// Get cache info
const info = await CacheService.info();
```

### 3. Using Cache Middleware

#### Option A: Cache Response (Read-Only)

```javascript
import { cacheMiddleware } from "../cache/cache.middleware.js";

// Cache GET /api/v1/courses for 1 hour
router.get(
  "/courses", 
  cacheMiddleware("courses", 3600),
  CourseController.getAll
);

// Cache GET /api/v1/disciplines for 2 hours
router.get(
  "/disciplines",
  cacheMiddleware("disciplines", 7200),
  DisciplineController.getAll
);
```

#### Option B: Get from Cache or Fetch (Smart Caching)

```javascript
import { getOrCacheMiddleware } from "../cache/cache.middleware.js";

// If cached, return from cache; otherwise fetch and cache
router.get(
  "/courses/:id",
  getOrCacheMiddleware("course", 3600),
  CourseController.getById
);
```

#### Option C: Invalidate Cache After Mutation

```javascript
import { invalidateCacheAfter } from "../cache/cache.middleware.js";

// Clear cache patterns after successful POST/PUT/DELETE
router.post(
  "/courses",
  invalidateCacheAfter(["courses:*", "course:*"]),
  CourseController.create
);

router.put(
  "/courses/:id",
  invalidateCacheAfter(["courses:*", "course:*"]),
  CourseController.update
);

router.delete(
  "/courses/:id",
  invalidateCacheAfter(["courses:*", "course:*"]),
  CourseController.delete
);
```

### 4. Service-Level Caching

```javascript
// Example: CourseService with caching
import { CacheService } from "../cache/cache.service.js";

export class CourseService {
  static async getById(courseId) {
    return await CacheService.getOrSet(
      `course:${courseId}`,
      async () => {
        return await db.Course.findByPk(courseId);
      },
      3600 // Cache for 1 hour
    );
  }

  static async getAll(filters = {}) {
    const cacheKey = `courses:${JSON.stringify(filters)}`;
    
    return await CacheService.getOrSet(
      cacheKey,
      async () => {
        return await db.Course.findAll({ where: filters });
      },
      1800 // Cache for 30 minutes
    );
  }

  static async create(data) {
    const course = await db.Course.create(data);
    
    // Invalidate list cache
    await CacheService.invalidatePattern("courses:*");
    
    return course;
  }

  static async update(courseId, data) {
    const course = await db.Course.update(data, { where: { courseId } });
    
    // Invalidate both specific and list caches
    await CacheService.deleteMultiple([
      `course:${courseId}`,
      "courses:*"
    ]);
    
    return course;
  }
}
```

## Cache Key Naming Conventions

Follow consistent naming patterns for easier management:

```
# Single resource
user:123
course:abc123
discipline:physics

# Collections
users:all
courses:all
courses:active

# Paginated results
courses:page:1:limit:10
users:search:physics:page:1

# Filtered data
courses:stream:engineering:active
users:city:bangalore:active

# Authentication/Sessions
session:token:abc123xyz
otp:user123
refresh-token:user456

# Temporary data
verification-email:user789
password-reset:user999
```

## Cache TTL Guidelines

```javascript
// Sessions/OTP: Short-lived (minutes)
CacheService.set("otp:user123", "123456", 300);        // 5 minutes
CacheService.set("session:token", data, 1800);         // 30 minutes

// API Responses: Medium-lived (minutes to hours)
CacheService.set("courses:all", data, 3600);           // 1 hour
CacheService.set("user:profile:123", data, 1800);      // 30 minutes

// Reference Data: Long-lived (hours to days)
CacheService.set("disciplines:all", data, 86400);      // 1 day
CacheService.set("streams:list", data, 604800);        // 7 days
```

## Performance Tips

1. **Use appropriate TTLs**: Balance between freshness and server load
2. **Cache list endpoints**: Heavy DB queries benefit most
3. **Invalidate on mutations**: Clear cache patterns after POST/PUT/DELETE
4. **Use patterns wisely**: Invalidate related caches together
5. **Monitor cache hit rate**: Use info() to check cache performance
6. **Don't cache sensitive data**: Avoid storing passwords, tokens in cache
7. **Handle cache failures gracefully**: All cache operations are non-blocking

## Monitoring

```javascript
// Check cache health
const health = await checkRedisHealth();

// Get cache statistics
const info = await CacheService.info();
console.log(info);

// Example output:
// {
//   status: "connected",
//   info: { ... redis info output ... }
// }
```

## Error Handling

The cache service is designed to fail gracefully:

- If Redis is unavailable, operations return `null`/`false` instead of throwing
- API calls continue to work without cache
- Errors are logged but don't crash the application
- Automatic fallback to database queries

## Best Practices

✅ **DO:**
- Use `getOrSet()` for automatic cache-aside pattern
- Clear related caches together using patterns
- Set appropriate TTLs based on data freshness requirements
- Monitor cache hit rates

❌ **DON'T:**
- Cache sensitive data (passwords, tokens, PII)
- Use extremely long TTLs for frequently changing data
- Forget to invalidate cache on mutations
- Cache failed responses

## Complete Example

```javascript
// routes/course.routes.js
import express from "express";
import { getOrCacheMiddleware, invalidateCacheAfter } from "../cache/cache.middleware.js";
import CourseController from "../modules/courses/course.controller.js";

const router = express.Router();

// GET endpoints with caching
router.get(
  "/",
  getOrCacheMiddleware("courses", 3600),
  CourseController.getAll
);

router.get(
  "/:id",
  getOrCacheMiddleware("course", 3600),
  CourseController.getById
);

// POST/PUT/DELETE with cache invalidation
router.post(
  "/",
  invalidateCacheAfter(["courses:*"]),
  CourseController.create
);

router.put(
  "/:id",
  invalidateCacheAfter(["courses:*", "course:*"]),
  CourseController.update
);

router.delete(
  "/:id",
  invalidateCacheAfter(["courses:*", "course:*"]),
  CourseController.delete
);

export default router;
```

---

## Startup Flow

```
Server Bootstrap
    ↓
Database Connection
    ↓
Default Setup (Roles, Auth)
    ↓
SMTP Check
    ↓
Razorpay Check
    ↓
Redis Cache Initialize ← NEW
    ↓
Redis Health Check ← NEW
    ↓
HTTP Server Start
```

Server logs during startup:
```
✅ Redis connected successfully
✓ Cached: courses:all (TTL: 3600s)
✓ Cache HIT: courses:all
✗ Cache MISS: user:new
```

---

For more information, check Redis documentation: https://redis.io/
