10) Optional: Redis caching in a Rails API app (why and how)
Even in API-only apps, application-level caching is useful to reduce DB load for expensive queries or aggregated endpoints.
Common patterns
- Low-level caching:
Rails.cache.fetch - Fragment caching: less relevant for API-only (used for views), but you can cache JSON blobs
- Keyed caching with expiration for computed results
Example — caching an expensive query
class Api::V1::ReportsController < ApplicationController
def sales_summary
key = "sales_summary:#{Time.current.utc.strftime('%Y-%m-%d-%H')}"
data = Rails.cache.fetch(key, expires_in: 5.minutes) do
# expensive computation
compute_sales_summary
end
render json: data
end
end
Why Redis?
- Redis is fast, supports expirations, and can be used as Rails.cache store (
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }). - Works well for ephemeral caches that you want to expire automatically.
Invalidation strategies
- Time-based (TTL) — simplest.
- Key-based — when related data changes, evict related keys.
- Example: after a product update, call
Rails.cache.delete("top_offers").
- Example: after a product update, call
- Versioned keys — embed a version or timestamp in the key (e.g.,
products:v3:top) and bump the version on deploy/major change. - Tagging / Key sets — maintain a set of keys per resource to delete them on change (more manual).
Caveat: Don’t rely solely on Redis caching for user-specific sensitive data. Use private caches when needed.
11) Purging caches and CDN invalidation
When hashed assets are used you rarely need to purge because new filenames mean new URLs. For non-hashed assets or CDN caches you may need purge:
- CDN invalidation: Cloudflare / Fastly / CloudFront offer purge by URL or cache key. Use CDN APIs or
surrogate-keyheaders to do group purges. - Surrogate-Control / Surrogate-Key (Fastly): set headers that help map objects to tags for efficient purging.
- Nginx cache purge: if you configure
proxy_cache, you must implement purge endpoints or TTLs.
Recommended approach:
- Prefer hashed filenames for assets so you rarely need invalidation.
- For dynamic content, prefer short TTLs or implement programmatic CDN purges as part of deploy/administration flows.
12) Testing and verifying caching behavior (practical commands)
Check response headers
curl -I https://www.mydomain.com/vite/index-B34XebCm.js
# expect: Cache-Control: public, max-age=31536000, immutable
Conditional request test (ETag)
- Get ETag:
curl -I https://api.mydomain.com/api/v1/products/123
# Look for ETag: "..."
- Re-request with that ETag:
curl -I -H 'If-None-Match: "the-etag-value"' https://api.mydomain.com/api/v1/products/123
# expect: 304 Not Modified (if unchanged)
Check s-maxage / CDN effect
- Use
curl -Iagainst the CDN domain (if applicable) to inspectAgeheader (shows time cached at edge) andX-Cacheheaders from CDN.
Chrome DevTools
- Open Network tab, reload page, inspect a cached resource:
- Status might show
(from disk cache)or(from memory cache)if cached. - For resources with
max-agebut noimmutable, you might see200withfrom disk cacheor network requests with304.
- Status might show
13) Common pitfalls and how to avoid them
- Caching HTML
- Problem: If your
index.htmlorvite.htmlis cached, users can get old asset references and see broken UI. - Avoid: Always
no-cacheyour HTML entry file.
- Problem: If your
- Caching non-hashed assets long-term
- Problem: Logo or content images may not update for users.
- Avoid: Short TTL or rename files when updating (versioning).
- Not using ETag/Last-Modified
- Problem: Clients re-download entire payloads when unchanged — wasted bandwidth.
- Avoid: Use
ETagorLast-Modifiedso clients can get304.
- Caching user-specific responses publicly
- Problem: Data leak (private data served to other users).
- Avoid: Use
private/no-storefor user-specific responses.
- Relying solely on Nginx for dynamic caching
- Problem: Hard to maintain invalidations and complex to configure with Passenger.
- Avoid: Use Rails headers + CDN; or a caching reverse proxy only if necessary and you know how to invalidate.
14) Commands and operational notes for Passenger
Test nginx config
sudo nginx -t
Reload nginx gracefully
sudo systemctl reload nginx
Restart nginx if reload fails
sudo systemctl restart nginx
Restart Passenger (app-level)
- Passenger allows restarting an app without touching the systemd service:
# restart specific app by path
sudo passenger-config restart-app /apps/mydomain/current
- Or restart all apps (be careful):
sudo passenger-config restart-app --ignore-app-not-running
Check Passenger status
passenger-status
Always run
nginx -tbefore reload. Make sure to test caching headers after a reload (curl) before rolling out.
15) Final checklist before/after deploy (practical)
Before deploy:
- Ensure build pipeline fingerprints Vite output (hash in filenames).
- Ensure
/apps/mydomain/current/public/vite/contains hashed assets. - Confirm
vite.htmlis correct and references the hashed file names. - Confirm Nginx snippet for
/vite/long cache is present and not overridden.
After deploy:
- Run
sudo nginx -tandsudo systemctl reload nginx. - Test hashed asset:
curl -I https://www.mydomain.com/vite/index-...js→Cache-Control: public, max-age=31536000 - Test HTML:
curl -I https://www.mydomain.com/vite.html→Cache-Control: no-cache - Test sample API endpoint headers:
curl -I https://api.mydomain.com/api/v1/products→ verifyCache-ControlandETag/Last-Modifiedwhere applicable. - Run smoke tests in browser (Chrome DevTools) to verify resources are cached as expected.
16) Appendix — example Rails snippets (summary)
Set header manually
response.set_header('Cache-Control', 'public, max-age=60, s-maxage=300')
Return 304 using conditional GET
def show
resource = Resource.find(params[:id])
if stale?(etag: resource, last_modified: resource.updated_at)
render json: resource
end
end
Redis caching (Rails.cache)
data = Rails.cache.fetch("top_products_page_#{params[:page]}", expires_in: 5.minutes) do
Product.top.limit(20).to_a
end
render json: data
Conclusion (Part 4)
This part explained where caching belongs in an API-only Rails + Vue application, how Passenger fits into the stack, how to set cache headers for safe API caching, optional Redis caching strategies, and practical testing/operational steps.