JCC Express

Digging Deeper

Cache

Introduction

JCC Express MVC ships with a Laravel-style cache API.

Core capabilities:

  • Multiple stores (memory, file, redis)
  • Key/value operations (get, put, forget, has)
  • Batch operations (many, putMany)
  • Numeric ops (increment, decrement)
  • Helpers (remember, rememberForever, pull, add)
  • Tag-based invalidation (tags([...]).flush())
  • Static facade usage via CacheFacade (aliased as Cache from jcc-express-mvc/Core/Cache)

Configuration and bootstrapping

Cache is configured in app/Config/cache.ts and merged in app/Config/index.ts. At runtime, CacheServiceProvider reads app.config.cache and initializes the cache singleton.

TypeScript
import path from "path";
import appRootPath from "app-root-path";
import { config } from "jcc-express-mvc";
import type { CacheConfig } from "jcc-express-mvc";

export const cache: CacheConfig = {
  default: "memory",
  stores: {
    memory: { driver: "memory" },
    file: {
      driver: "file",
      cacheDir: path.join(
        appRootPath.path,
        config.get("CACHE_PATH", "storage/framework/cache")
      ),
    },
    redis: {
      driver: "redis",
      host: config.get("REDIS_HOST", "127.0.0.1"),
      port: Number(config.get("REDIS_PORT", "6379")),
      password: config.get("REDIS_PASSWORD", "") || undefined,
      database: Number(config.get("REDIS_CACHE_DB", "1")),
    },
  },
};

You can still call Cache.getInstance(customConfig) manually (useful in tests), but for app runtime prefer provider-driven config.


Basic operations

TypeScript
import { Cache } from "jcc-express-mvc/Core/Cache";

await Cache.put("user:1", { name: "Abdou" }, 60); // 60 seconds
const user = await Cache.get("user:1");
const exists = await Cache.has("user:1");

await Cache.forever("app:name", "JCC");
await Cache.forget("user:1");
await Cache.flush(); // clear current store

Remember helpers

TypeScript
const posts = await Cache.remember("posts.home", 120, async () => {
  return await Post.latest().limit(10).get();
});

const settings = await Cache.rememberForever("settings", async () => {
  return await Settings.first();
});

remember only runs the callback when the key is missing.


Numeric and atomic-style helpers

TypeScript
await Cache.put("counter", 10, 60);
await Cache.increment("counter"); // 11
await Cache.decrement("counter", 2); // 9

const firstWrite = await Cache.add("once:key", "value", 30); // true/false
const pulled = await Cache.pull("once:key"); // get + forget

Batch operations

TypeScript
await Cache.putMany(
  {
    "user:1": { id: 1 },
    "user:2": { id: 2 },
  },
  60,
);

const users = await Cache.many(["user:1", "user:2"]);

Tagged cache

Tagged keys are namespaced and can be flushed by tag group:

TypeScript
await Cache.tags(["people", "artists"]).put("john", "Lennon", 60);
await Cache.tags(["people", "authors"]).put("anne", "Frank", 60);

await Cache.tags(["people"]).flush();

Store selection

Use the default store or explicitly pick one:

TypeScript
const redisStore = Cache.store("redis");
await redisStore.put("health", { ok: true }, 30);

Driver notes

  • memory is process-local (fast, reset on restart).
  • redis serializes payloads as JSON and supports TTL with setEx.
  • Invalid inputs throw explicit errors (missing key, null value, invalid TTL, etc.).

Summary

  • Configure stores in app/Config/cache.ts and let CacheServiceProvider initialize cache.
  • Use the facade (jcc-express-mvc/Core/Cache) for get/put/remember convenience.
  • Use tags(...) when invalidation needs grouping.
  • Prefer redis for shared cache across processes/instances.