Skip to main content
Rest Generic Class includes a comprehensive caching layer that works with any Laravel cache store. This guide shows you how to configure caching, understand cache key generation, and manage cache invalidation.

Cache Architecture

The caching system is designed to:
  1. Support any Laravel cache store (Redis, database, file, Memcached, DynamoDB)
  2. Cache read operations (list_all, get_one) automatically
  3. Invalidate cache on writes using versioned keys
  4. Vary by request context (user, tenant, locale, query params)
  5. Allow per-request control with cache and cache_ttl parameters
Cache is disabled by default. You must explicitly enable it in configuration.

Quick Start

1
Enable Cache
2
Set environment variables:
3
REST_CACHE_ENABLED=true
REST_CACHE_STORE=redis
REST_CACHE_TTL=60
REST_CACHE_TTL_LIST=60
REST_CACHE_TTL_ONE=30
4
Configure Cache Store
5
Make sure your chosen cache store is configured in config/cache.php:
6
'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
        'lock_connection' => 'default',
    ],
],
7
Test Cache
8
Make two identical requests and verify the second is faster:
9
# First request (cache miss)
curl -X GET "http://api.example.com/api/v1/products" \
  -H "Content-Type: application/json"

# Second request (cache hit)
curl -X GET "http://api.example.com/api/v1/products" \
  -H "Content-Type: application/json"
10
Monitor Cache
11
Check Redis keys (if using Redis):
12
redis-cli KEYS "rgc:v1:*"

Configuration Reference

config/rest-generic-class.php
'cache' => [
    // Enable/disable cache globally
    'enabled' => env('REST_CACHE_ENABLED', false),
    
    // Laravel cache store name (redis, database, file, memcached, etc.)
    'store' => env('REST_CACHE_STORE', env('CACHE_STORE')),
    
    // Default TTL in seconds
    'ttl' => (int)env('REST_CACHE_TTL', 60),
    
    // Method-specific TTL overrides
    'ttl_by_method' => [
        'list_all' => (int)env('REST_CACHE_TTL_LIST', 60),
        'get_one' => (int)env('REST_CACHE_TTL_ONE', 30),
    ],
    
    // Methods that will be cached
    'cacheable_methods' => ['list_all', 'get_one'],
    
    // Headers to include in cache key (for multi-tenancy/localization)
    'vary' => [
        'headers' => ['Accept-Language', 'X-Tenant-Id'],
    ],
],

Environment Variables

VariableDefaultDescription
REST_CACHE_ENABLEDfalseMaster switch for package cache
REST_CACHE_STORECACHE_STORELaravel store name
REST_CACHE_TTL60Default TTL (seconds)
REST_CACHE_TTL_LIST60TTL for list endpoints
REST_CACHE_TTL_ONE30TTL for single-record endpoints

Cache Store Options

Best for:
  • High-traffic APIs
  • Distributed applications (multiple servers)
  • Sub-millisecond cache hits
.env
REST_CACHE_ENABLED=true
REST_CACHE_STORE=redis
REST_CACHE_TTL=300
Setup Redis:
# Install Redis
sudo apt-get install redis-server

# Install PHP Redis extension
pecl install redis

# Install Laravel Redis package
composer require predis/predis

Database

Best for:
  • Simple deployments
  • Shared hosting without Redis
  • When you need queryable cache data
.env
REST_CACHE_ENABLED=true
REST_CACHE_STORE=database
REST_CACHE_TTL=180
Create cache table:
php artisan cache:table
php artisan migrate

File

Best for:
  • Development
  • Small applications
  • Single-server deployments
.env
REST_CACHE_ENABLED=true
REST_CACHE_STORE=file
REST_CACHE_TTL=120
File cache doesn’t work in multi-server deployments. Each server has its own cache, leading to inconsistencies.

Memcached

Best for:
  • Legacy systems already using Memcached
  • High-performance in-memory caching
.env
REST_CACHE_ENABLED=true
REST_CACHE_STORE=memcached
REST_CACHE_TTL=300

How Cache Keys Work

Cache keys are generated from a fingerprint that includes:
  1. Operation (list_all or get_one)
  2. Model class (e.g., App\Models\Product)
  3. Route (name or path)
  4. HTTP method (GET, POST, etc.)
  5. Query parameters (select, relations, oper, pagination, etc.)
  6. Vary headers (Accept-Language, X-Tenant-Id)
  7. Authenticated user ID
  8. Request parameters (from request body)
  9. Model cache version (for invalidation)
Example fingerprint:
[
    'op' => 'list_all',
    'model' => 'App\\Models\\Product',
    'route' => 'api.v1.products.index',
    'method' => 'GET',
    'query' => ['select' => ['id','name'], 'relations' => ['category']],
    'headers' => ['Accept-Language' => 'en', 'X-Tenant-Id' => null],
    'user' => 42,
    'params' => ['oper' => ['and' => ['status|=|active']]],
    'version' => 5,
]
This is hashed to create the cache key:
rgc:v1:a3f7b2e1c9d4f6a8e2b5c7d9f1a3e5b7
Any change in the fingerprint creates a different cache key. This ensures correct cache isolation.

Multi-Tenancy Support

The vary.headers configuration prevents cache pollution across tenants or locales.

Example: Multi-Tenant SaaS

Configuration:
config/rest-generic-class.php
'cache' => [
    'vary' => [
        'headers' => ['X-Tenant-Id'],
    ],
],
Requests:
# Tenant A request
curl -H "X-Tenant-Id: tenant-a" \
     "http://api.example.com/api/v1/products"

# Tenant B request (different cache key)
curl -H "X-Tenant-Id: tenant-b" \
     "http://api.example.com/api/v1/products"
Each tenant gets a separate cache entry, preventing data leaks.

Example: Multi-Language API

Configuration:
config/rest-generic-class.php
'cache' => [
    'vary' => [
        'headers' => ['Accept-Language'],
    ],
],
Requests:
# English request
curl -H "Accept-Language: en" \
     "http://api.example.com/api/v1/products"

# Spanish request (different cache key)
curl -H "Accept-Language: es" \
     "http://api.example.com/api/v1/products"

Cache Invalidation

Rest Generic Class uses versioned keys for automatic invalidation.

How It Works

  1. Each model has a version number stored in cache
  2. Version is included in every read cache key
  3. On write operations (create, update, destroy), version is bumped
  4. Old cache keys become unreachable (effectively invalidated)
Example:
// Initial state
Product cache version: 1
Cache key: rgc:v1:...{version:1}...abc123

// Update product
PUT /api/v1/products/10

// After update
Product cache version: 2 (bumped)
Old cache key: rgc:v1:...{version:1}...abc123 (orphaned)
New cache key: rgc:v1:...{version:2}...def456 (fresh)
This approach works across all cache stores, including those without tag support (file, database).

Version Storage

Versions are stored with keys like:
rgc:v1:version:App\Models\Product
Versions are stored forever (no TTL), ensuring consistent invalidation.

Manual Invalidation

To manually clear cache for a model:
$service = new ProductService();

// Call the private bumpCacheVersion() method via reflection
$reflection = new \ReflectionClass($service);
$method = $reflection->getMethod('bumpCacheVersion');
$method->setAccessible(true);
$method->invoke($service);
Or clear all cache:
php artisan cache:clear

Per-Request Cache Control

Override cache behavior on a per-request basis.

Disable Cache for One Request

GET /api/v1/products?cache=false
This bypasses cache and queries the database directly (but doesn’t update cache).

Custom TTL for One Request

GET /api/v1/products?cache_ttl=300
This request will be cached for 300 seconds (5 minutes) instead of the default.

Example: Fresh Data on Demand

For admin users who need real-time data:
GET /api/v1/products?cache=false
Authorization: Bearer admin_token
For public users (cached):
GET /api/v1/products
Authorization: Bearer user_token

Performance Impact

Benchmark Results

Test: List 100 products with category relation
Cache StoreFirst Request (Miss)Second Request (Hit)Improvement
No cache45ms45ms-
Redis45ms2ms22.5x faster
Database45ms8ms5.6x faster
File45ms5ms9x faster

When to Use Cache

Enable cache for:
  • Read-heavy endpoints (product listings, category trees)
  • Data that changes infrequently (settings, configurations)
  • High-traffic public APIs
  • Expensive queries (complex filters, multiple relations)
Disable cache for:
  • Write-heavy endpoints (webhooks, real-time updates)
  • User-specific data (shopping carts, notifications)
  • Admin dashboards requiring fresh data
  • Development/testing environments

Monitoring Cache

Redis Monitoring

Check cache hit rate:
redis-cli INFO stats | grep keyspace
List all package keys:
redis-cli KEYS "rgc:v1:*" | wc -l
Monitor cache operations in real-time:
redis-cli MONITOR

Laravel Telescope

Enable cache monitoring in Telescope:
config/telescope.php
'watchers' => [
    Watchers\CacheWatcher::class => true,
],
View cache operations at /telescope/cache.

Custom Logging

Log cache hits/misses:
// In your service override
public function list_all($params, $toJson = true): mixed
{
    $cacheKey = $this->buildCacheKey('list_all', $params);
    $cached = Cache::has($cacheKey);
    
    Log::info('Cache ' . ($cached ? 'HIT' : 'MISS'), [
        'model' => static::class,
        'key' => $cacheKey,
    ]);
    
    return parent::list_all($params, $toJson);
}

Troubleshooting

Cache Not Working

Symptom: Identical requests are always slow Checks:
  1. Is cache enabled? REST_CACHE_ENABLED=true
  2. Is the store configured? Check config/cache.php
  3. Is the method cacheable? Check cacheable_methods config
  4. Are you testing with cache=false?
Debugging:
# Check cache status
php artisan tinker
>>> config('rest-generic-class.cache.enabled')
=> true

# Test cache store
>>> Cache::store('redis')->put('test', 'value', 60);
>>> Cache::store('redis')->get('test');
=> "value"

Stale Data After Updates

Symptom: GET returns old data after PUT/DELETE Cause: Cache version not bumped (transaction rollback?) Solution: Check logs for transaction errors:
tail -f storage/logs/rest-generic-class.log
Manually bump version if needed (see Manual Invalidation above).

Cache Growing Too Large

Symptom: Cache store running out of memory Causes:
  • TTL too high
  • Too many unique requests (different query params)
  • Version keys accumulating
Solutions:
  • Lower TTL: REST_CACHE_TTL=30
  • Use Redis maxmemory policy: maxmemory-policy allkeys-lru
  • Periodically clear old versions:
redis-cli --scan --pattern "rgc:v1:version:*" | xargs redis-cli DEL

Different Cache per Server

Symptom: Inconsistent responses in load-balanced setup Cause: Using file cache with multiple servers Solution: Switch to Redis or Memcached:
.env
REST_CACHE_STORE=redis

Next Steps

Advanced Filtering

Optimize cached queries with efficient filters

Bulk Operations

Understand cache invalidation with bulk updates

Performance Tuning

Advanced performance optimization techniques

Configuration Reference

Complete cache configuration options

Evidence

  • File: src/Core/Services/BaseService.php
    Lines: 1082-1212
    Implements shouldUseCache(), rememberWithCache(), buildCacheKey(), resolveCacheTtl(), getCacheVersion(), bumpCacheVersion()
  • File: config/rest-generic-class.php
    Lines: 37-52
    Defines cache configuration structure
  • File: documentacion/doc-en/02-configuration/03-cache-strategy.md
    Lines: 1-66
    Explains cache strategy and store selection

Build docs developers (and LLMs) love