Skip to content
RELKIT
Esc
↑↓navigate↵open⌘Jpreview
On this page

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.1

Keep 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 check
bun run dev

Docker 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-demo

Expect 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.