Cache Service Integration Guide - Wiz-DevTech/prettygirllz GitHub Wiki

Cache Service Integration Guide

API Overview

The Cache Service provides a REST API for retrieving cached server-side rendered (SSR) HTML content. It serves as a fallback mechanism when primary services are unavailable.

Base URL

http://localhost:3000

Endpoints

1. Test Database Connection

GET /test-db

Description: Returns all cached entries for debugging and verification purposes.

Request:

GET /test-db HTTP/1.1
Host: localhost:3000

Response:

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "id": 1,
    "route": "/home",
    "html": "<html>...</html>",
    "expiry": "2025-05-17T10:00:00.000Z",
    "created_at": "2025-05-16T10:00:00.000Z"
  }
]

Error Response:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "error": "relation \"ssr_cache\" does not exist"
}

2. Get Cached Content

GET /fallback/:route

Description: Retrieves cached HTML content for a specific route.

Parameters:

  • route (path parameter): The route to retrieve (e.g., "home", "about", "products")

Request:

GET /fallback/home HTTP/1.1
Host: localhost:3000

Response (Success):

HTTP/1.1 200 OK
Content-Type: text/html

<html>
  <head><title>Home</title></head>
  <body>Cached home page content</body>
</html>

Response (No Cache):

HTTP/1.1 200 OK
Content-Type: text/html

<div>No cached version</div>

Response (Error):

HTTP/1.1 500 Internal Server Error
Content-Type: text/html

Database error

Integration Patterns

Frontend Integration

JavaScript Fetch Example

// Function to get cached content with fallback
async function getCachedContent(route) {
  try {
    const response = await fetch(`http://localhost:3000/fallback/${route}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const html = await response.text();
    return html;
  } catch (error) {
    console.error('Failed to fetch cached content:', error);
    return '<div>Content temporarily unavailable</div>';
  }
}

// Usage
getCachedContent('home').then(html => {
  document.getElementById('content').innerHTML = html;
});

React Component Example

import React, { useState, useEffect } from 'react';

function CachedContent({ route }) {
  const [html, setHtml] = useState('');
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchCached() {
      try {
        const response = await fetch(`/api/fallback/${route}`);
        const content = await response.text();
        setHtml(content);
      } catch (error) {
        setHtml('<div>Content unavailable</div>');
      } finally {
        setLoading(false);
      }
    }

    fetchCached();
  }, [route]);

  if (loading) return <div>Loading...</div>;
  
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

Security Considerations

Current Limitations

  • No Authentication: The API is currently open to all requests
  • No Rate Limiting: No protection against abuse
  • No Input Validation: Route parameters are not validated
  • No HTTPS: Plain HTTP communication

Recommended Security Enhancements

1. Authentication Implementation

// Add JWT middleware (example)
const jwt = require('jsonwebtoken');

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.sendStatus(401);
  }

  jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

// Apply to protected routes
app.get('/fallback/:route', authenticateToken, async (req, res) => {
  // ... existing route logic
});

2. Input Validation

// Add route validation
app.get('/fallback/:route', (req, res, next) => {
  const route = req.params.route;
  
  // Validate route format
  if (!/^[a-zA-Z0-9-_/]+$/.test(route)) {
    return res.status(400).send('Invalid route format');
  }
  
  // Prevent path traversal
  if (route.includes('..') || route.includes('//')) {
    return res.status(400).send('Invalid route');
  }
  
  next();
}, async (req, res) => {
  // ... existing route logic
});

3. Rate Limiting

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP'
});

app.use('/fallback', limiter);

Error Handling

Error Response Format

All errors should follow a consistent format:

interface ErrorResponse {
  error: string;
  code?: string;
  details?: any;
  timestamp: string;
}

Implementation Example

// Global error handler
app.use((err, req, res, next) => {
  const errorResponse = {
    error: err.message || 'Internal Server Error',
    code: err.code || 'UNKNOWN_ERROR',
    timestamp: new Date().toISOString()
  };
  
  // Log error
  console.error('API Error:', err);
  
  // Respond with appropriate status
  const status = err.status || 500;
  res.status(status).json(errorResponse);
});

Performance Optimization

Caching Headers

app.get('/fallback/:route', async (req, res) => {
  try {
    // Set cache headers
    res.set({
      'Cache-Control': 'public, max-age=300', // 5 minutes
      'ETag': `W/"${Date.now()}"`,
      'Last-Modified': new Date().toUTCString()
    });
    
    // ... existing logic
  } catch (err) {
    res.status(500).send('Database error');
  }
});

Compression

const compression = require('compression');
app.use(compression());

Connection Pooling

const pool = new Pool({
  user: 'gateway',
  password: 'secret',
  host: 'localhost',
  database: 'gateway_cache',
  port: 5432,
  // Connection pool configuration
  max: 20,                    // Maximum number of clients
  idleTimeoutMillis: 30000,   // Close idle clients after 30 seconds
  connectionTimeoutMillis: 2000, // Return error after 2 seconds if no connection
});

Documentation Standards

OpenAPI/Swagger Specification

openapi: 3.0.0
info:
  title: Cache Service API
  version: 1.0.0
  description: API for retrieving cached SSR content

paths:
  /test-db:
    get:
      summary: Test database connection
      responses:
        '200':
          description: List of cached entries
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    route:
                      type: string
                    html:
                      type: string
                    expiry:
                      type: string
                      format: date-time

  /fallback/{route}:
    get:
      summary: Get cached content for route
      parameters:
        - name: route
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Cached HTML content
          content:
            text/html:
              schema:
                type: string

Integration Best Practices

1. Circuit Breaker Pattern

class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED';
    this.failures = 0;
    this.nextAttempt = Date.now();
  }

  async call(request) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await request();
      this.reset();
      return result;
    } catch (error) {
      this.recordFailure();
      throw error;
    }
  }

  recordFailure() {
    this.failures++;
    if (this.failures >= this.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }

  reset() {
    this.failures = 0;
    this.state = 'CLOSED';
  }
}

2. Retry Logic with Exponential Backoff

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetch(url, options);
    } catch (error) {
      lastError = error;
      
      if (i < maxRetries - 1) {
        const delay = Math.min(1000 * Math.pow(2, i), 10000);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError;
}

3. Health Check Endpoint

app.get('/health', async (req, res) => {
  try {
    // Check database connectivity
    await pool.query('SELECT 1');
    
    // Check cache table
    const result = await pool.query('SELECT COUNT(*) FROM ssr_cache');
    
    res.json({
      status: 'healthy',
      database: 'connected',
      cache_entries: result.rows[0].count,
      timestamp: new Date().toISOString()
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message,
      timestamp: new Date().toISOString()
    });
  }
});

Testing Strategy

Unit Tests

// Using Jest
describe('Cache Service', () => {
  test('should return cached content for valid route', async () => {
    const response = await request(app)
      .get('/fallback/home')
      .expect(200)
      .expect('Content-Type', /html/);
    
    expect(response.text).toBeDefined();
  });

  test('should return no cached version for invalid route', async () => {
    const response = await request(app)
      .get('/fallback/nonexistent')
      .expect(200);
    
    expect(response.text).toBe('<div>No cached version</div>');
  });
});

Integration Tests

describe('Integration Tests', () => {
  beforeAll(async () => {
    // Setup test database
    await setupTestDB();
  });

  afterAll(async () => {
    // Cleanup test database
    await cleanupTestDB();
  });

  test('full workflow test', async () => {
    // Insert test data
    await pool.query(
      'INSERT INTO ssr_cache (route, html, expiry) VALUES ($1, $2, $3)',
      ['/test', '<div>Test Content</div>', new Date(Date.now() + 86400000)]
    );

    // Test retrieval
    const response = await request(app)
      .get('/fallback/test')
      .expect(200);
    
    expect(response.text).toBe('<div>Test Content</div>');
  });
});

Deployment Considerations

Docker Configuration

FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]

Environment Variables

# Production environment variables
DB_HOST=postgres-host
DB_PORT=5432
DB_NAME=gateway_cache
DB_USER=gateway
DB_PASSWORD=secure_password
NODE_ENV=production
PORT=3000

Monitoring

  • Set up application performance monitoring (APM)
  • Configure log aggregation
  • Implement metrics collection
  • Set up alerting for critical errors
⚠️ **GitHub.com Fallback** ⚠️