Skip to main content

Caching

Baeta provides a powerful and flexible caching system with support for multiple storage adapters. The caching system offers automatic cache invalidation, parent-based invalidation, and type-safe cache operations.

Key Features

  • Type-safe cache operations
  • Easy updates for stale cache
  • Automatic schema based invalidation
  • Multiple storage adapters
  • TTL support

Installation

yarn add @baeta/extension-cache

Storage Adapters

Baeta supports several storage adapters for different use cases:

Best for production environments with high query volumes.

yarn add @baeta/extension-cache-redis ioredis
import { RedisStore } from "@baeta/extension-cache-redis";
import Redis from "ioredis";

const redis = new Redis("redis://localhost:6379");
const store = new RedisStore(redis);

Optimized for serverless environments.

yarn add @baeta/extension-cache-upstash @upstash/redis
import { UpstashStore } from "@baeta/extension-cache-upstash";
import { Redis } from "@upstash/redis";

const redis = new Redis({
url: "UPSTASH_REDIS_URL",
token: "UPSTASH_REDIS_TOKEN",
});
const store = new UpstashStore(redis);

Cloudflare Durable Objects

TBA

Keyv (Limited)

Simple key-value storage, suitable for development or small applications.

note

Not recommended for production with high query volumes.

yarn add @baeta/extension-cache-keyv keyv
import { KeyvStore } from "@baeta/extension-cache-keyv";
import Keyv from "keyv";

const keyv = new Keyv();
const store = new KeyvStore(keyv);

Basic Setup

  1. Configure Cache Extension
import { cacheExtension } from "@baeta/extension-cache";
import { RedisStore } from "@baeta/extension-cache-redis";
import Redis from "ioredis";

const redis = new Redis("redis://localhost:6379");
const redisStore = new RedisStore(redis);

export const cacheExt = cacheExtension(redisStore, {
ttl: 3600, // TTL in seconds (defaults to 1 hour)
});
  1. Create Type-Specific Cache
import { getUserModule } from "./typedef.ts";

const { User, Query, Mutation } = getUserModule();

export const userCache = User.$createCache({
revision: 2, // Optional, cache version
ttl: 3600, // Optional, overrides default TTL
});
tip

When you modify type fields, the store will be automatically invalidated (its hash changes). For scenarios where you want to invalidate the store manually, you can use the revision option.

  1. Register the Extension
import { createExtensions } from "@baeta/core";
import { cacheExt } from "./cache-extension";

export default createExtensions(
//... other extensions
cacheExt,
//... other extensions
);

Caching Examples

Basic Query Caching

// Simple approach
Query.user.$useCache(userCache);
Query.users.$useCache(userCache);

// Or alternatively. This is same as above
Query.users.$use(userCache.createMiddleware(Query.users.$cacheRef));

Mutation Handling

Saving an entity to the store will also update all queries that reference it.

Mutation.updateUser.$use(async (params, next) => {
const user = await next();
await userCache.save(user);
return user;
});

Cache Clearing

Because some queries that return lists have sorting or pagination capabilities, when inserting or deleting entities it is very difficult to compute how an update changes them. It is easier to wipe them entirely.

await Query.users.$cacheClear(userCache);

// Or alternatively. This, again, is the same as above
await userCache.deleteQueries(Query.users.$cacheRef);

For certain use cases, ie. an application where entities are scoped into organizations, it might be useful to clear queries that match only certain arguments.

await Query.users.$cacheClear(userCache, {
args: {
where: {
organizationId: "specific-org",
},
},
});

Parent-Based Cache Clearing

For relationship queries, we can invalidate only the queries that reference a certain parentId.

Mutation.createUserPhoto(async ({ args }) => {
const result = await db.userPhoto.create({
data: {
...args.data,
userId: args.userId,
},
});

User.photos.$cacheClear(userPhotoCache, {
parentRef: args.userId,
});

return result;
});

Best Practices

  1. Choose the Right Adapter
  • Use Redis for production environments
  • Use Upstash for serverless applications
  • Avoid Keyv for high-traffic applications
  1. Cache Invalidation Strategy
  • Use revision numbers to manually invalidate when needed
  • Implement parent-based invalidation for nested data
  • Clear list caches on mutations that affect ordering
  1. Performance Optimization
  • Set appropriate TTL values
  • Configure Redis to evacuate least used keys
  • Use selective cache clearing when possible

For detailed examples, see the Baeta caching example.

There is also an advanced example that showcases caching for weirdly shaped queries (like relay connectors).