Cache product prices
Load a product price once, reuse it from Redis, and verify that it loads again after expiration.
Add a product-price lookup to your application. You’ll load a price, reuse it for one minute, and verify that it loads again after expiration. This example uses a fixed price, so it needs no database.
1. Configure caching
Install the local Redis integration from your app root:
From this guide’s application/package root, install only its selected requirements:
bun add @relkit/docker@0.7.12 @relkit/redis@0.8.1Keep these owner-specific versions and the lockfile together. Runtime SDKs belong in dependencies; build/type tooling belongs in devDependencies. Installing an adapter does not supply its optional SDK peers or enable the feature’s configuration.
In relkit.config.ts, import redis from @relkit/redis, docker from @relkit/docker, and
env as binding from @relkit/app/config. Create a cache object inside defineApp() if the
app has none, then add this requests entry. It is the only cache profile needed here:
requests: docker(redis({ url: binding.secret("REQUESTS_REDIS_URL") })),The excerpt is from the larger commerce example, which also has a timeline profile. Do not add
that second profile here. requests starts a Docker Redis recipe during local development;
orders.prices selects it explicitly. Connected or cloud-owned profiles are covered in
Define a cache.
2. Define the prices cache
/** Declares integer SKU prices in the requests cache profile with a one-minute default and five-minute maximum TTL. */
import { defineCache } from "@relkit/app/cache";
import { z } from "@relkit/app/schema";
const prices = defineCache({
id: "orders.prices",
profile: "requests",
key: z.object({ sku: z.string() }),
value: z.number().int().nonnegative(),
defaultTtlMs: 60_000,
maxTtlMs: 300_000,
});
export default prices;Each product code has its own cached price, which expires one minute after it is saved.
3. Add the lookup function
/** Exercises the real prices dependency with a logged cache-miss producer and validated SKU/price results. */
import { defineFunction } from "@relkit/app/functions";
import { z } from "@relkit/app/schema";
import prices from "@app/services/orders/cache/prices.cache.js";
const getPrice = defineFunction({
id: "orders.get-price",
input: z.object({ sku: z.string().min(1) }),
output: z.object({ sku: z.string(), unitPriceCents: z.number().int().nonnegative() }),
dependencies: { cache: { prices } },
handler: async ({ sku }, context) => {
const unitPriceCents = await context.cache.prices.getOrSet({ sku }, async () => {
// Runs on a cache miss. Replace the demo price with your database lookup.
context.log.info("price.loaded", { sku });
return 1_000;
});
return { sku, unitPriceCents };
},
});
export default getPrice;The price.loaded log runs only when the callback loads a price.
4. Expose a price endpoint
In src/services/orders/service.ts, import the function and add getPrice to the existing
functions object. Keep the service’s other members:
import getPrice from "./functions/get-price.function.js";Create src/routes/prices/route.ts:
import { defineServiceRoutes } from "@relkit/app/routes";
import orders from "@app/services/orders/service.js";
export const { GET } = defineServiceRoutes(orders, {
GET: { member: "getPrice" },
});5. Try a miss and a hit
Check and start the app:
bun run checkbun run devDocker is contacted only after the checked plan exists. To prove there is no hidden fallback, use
bun run dev --local=off; startup then requires the named connected/infrastructure values.
In a second terminal, send the same request twice:
curl http://localhost:3000/prices\?sku=book-demo
curl http://localhost:3000/prices\?sku=book-demoExpect this output:
{ "sku": "book-demo", "unitPriceCents": 1000 }Look for price.loaded with sku=book-demo in the app terminal. The second request within one minute returns the same output without another price.loaded log. Use a new SKU if this key was already cached from an earlier run.
6. Inspect expiration
Optionally complete Inspector setup. Open Cache, select orders.prices, and find the key containing book-demo. Its remaining TTL is the time before the cached value expires. Click View value to see 1000.
Once the minute has passed, repeat the curl request with the same SKU. A new price.loaded log confirms the price was loaded again. The next request performs the reload; nothing refreshes in the background.
Tests must pass an explicit replacement for the requests profile; no test environment selects
a fake for you. In your app, replace the fixed price with a database lookup and
invalidate the cached price after updates. Before deploying, review which
profiles are connected and which are infrastructure-owned in Integrations.