Schema Design
Learn how to design effective cache schemas for your application
Designing Your Cache Schema
The schema is the foundation of next-cool-cache. A well-designed schema makes your caching strategy intuitive and maintainable.
Understanding the Schema Structure
Think of your schema as a tree with three types of nodes:
- Root - The top level of your schema
- Branch nodes - Intermediate nodes that group related resources
- Leaf nodes - End points where you actually cache and invalidate data
const schema = {
// Branch: groups blog-related resources
blog: {
// Branch: groups post operations
posts: {
list: {}, // Leaf: no params
byId: { _params: ['id'] as const }, // Leaf: with params
},
categories: {
list: {}, // Leaf: no params
},
},
// Leaf at root level
config: {},
} as const;Leaf Nodes
Leaf nodes are where you define cacheable resources. They come in two forms:
Without Parameters
Use an empty object {} for resources that don't require identification:
const schema = {
users: {
list: {}, // Get all users
count: {}, // Get user count
},
settings: {
global: {}, // Get global settings
},
} as const;Usage:
cache.admin.users.list.cacheTag(); // No params needed
cache.admin.users.list.revalidateTag();With Parameters
Use { _params: [...] as const } for resources that require identification:
const schema = {
users: {
byId: { _params: ['id'] as const },
byEmail: { _params: ['email'] as const },
},
posts: {
bySlug: { _params: ['slug'] as const },
byAuthorAndYear: { _params: ['authorId', 'year'] as const }, // Multiple params
},
} as const;Usage:
cache.admin.users.byId.cacheTag({ id: '123' });
cache.admin.posts.byAuthorAndYear.revalidateTag({ authorId: 'a1', year: '2024' });The _params key is reserved. Don't use it for anything else in your schema.
Branch Nodes
Branch nodes group related resources and enable bulk invalidation:
const schema = {
blog: { // Branch
posts: { // Branch
list: {}, // Leaf
byId: { _params: ['id'] as const }, // Leaf
},
drafts: { // Branch
list: {},
byId: { _params: ['id'] as const },
},
},
} as const;Branch nodes automatically get revalidateTag() and updateTag() methods:
// Invalidate all blog data
cache.admin.blog.revalidateTag();
// Invalidate all posts (but not drafts)
cache.admin.blog.posts.revalidateTag();
// Invalidate specific post
cache.admin.blog.posts.byId.revalidateTag({ id: '123' });Naming Conventions
Consistent naming makes your schema intuitive:
For Collections
const schema = {
users: {
list: {}, // All items
count: {}, // Count of items
recent: {}, // Recent items
},
} as const;For Lookups
Use by prefix to indicate the lookup key:
const schema = {
users: {
byId: { _params: ['id'] as const },
byEmail: { _params: ['email'] as const },
byUsername: { _params: ['username'] as const },
},
} as const;For Multiple Parameters
Use And to join parameter names:
const schema = {
comments: {
byPostAndUser: { _params: ['postId', 'userId'] as const },
},
projects: {
byOwnerAndSlug: { _params: ['ownerId', 'slug'] as const },
},
} as const;Schema Evolution
Adding New Resources
Adding new resources is always safe - it's backward compatible:
// Before
const schema = {
users: { list: {}, byId: { _params: ['id'] as const } },
} as const;
// After - added posts
const schema = {
users: { list: {}, byId: { _params: ['id'] as const } },
posts: { list: {}, byId: { _params: ['id'] as const } }, // New!
} as const;Renaming Resources
When you rename a resource, TypeScript shows you every location that needs updating:
// Changed 'byId' to 'byUserId'
// TypeScript error at every usage:
cache.admin.users.byId.cacheTag({ id: '123' });
// ^^^^
// Property 'byId' does not exist. Did you mean 'byUserId'?Adding Parameters
If you add a parameter to an existing resource, TypeScript catches missing params:
// Before
byId: { _params: ['id'] as const }
// After - added tenantId
byId: { _params: ['tenantId', 'id'] as const }
// TypeScript error:
cache.admin.users.byId.cacheTag({ id: '123' });
// Property 'tenantId' is missingBest Practices
1. Mirror Your Data Model
Your schema should reflect your data structure:
// If your database has:
// - users table
// - posts table (belongs to user)
// - comments table (belongs to post)
const schema = {
users: {
list: {},
byId: { _params: ['id'] as const },
},
posts: {
list: {},
byId: { _params: ['id'] as const },
byAuthor: { _params: ['authorId'] as const },
},
comments: {
byPost: { _params: ['postId'] as const },
byId: { _params: ['id'] as const },
},
} as const;2. Keep Schemas Shallow When Possible
Deep nesting adds complexity. Only nest when it provides clear organizational value:
// Prefer this
const schema = {
users: { list: {}, byId: { _params: ['id'] as const } },
posts: { list: {}, byId: { _params: ['id'] as const } },
} as const;
// Over this (unless you need blog.revalidateTag())
const schema = {
blog: {
users: { list: {}, byId: { _params: ['id'] as const } },
posts: { list: {}, byId: { _params: ['id'] as const } },
},
} as const;3. Group for Bulk Invalidation
Use branches when you need to invalidate related resources together:
const schema = {
// Good: can invalidate all dashboard data at once
dashboard: {
stats: {},
recentActivity: {},
notifications: {},
},
} as const;
// cache.admin.dashboard.revalidateTag() invalidates all three4. Use Descriptive Parameter Names
Parameter names appear in your code, so make them clear:
// Less clear
byId: { _params: ['id'] as const }
cache.admin.comments.byId.cacheTag({ id: commentId });
// More clear
byCommentId: { _params: ['commentId'] as const }
cache.admin.comments.byCommentId.cacheTag({ commentId });Real-World Example
Here's a complete schema for a blog platform:
const schema = {
blog: {
posts: {
list: {},
featured: {},
byId: { _params: ['id'] as const },
bySlug: { _params: ['slug'] as const },
byAuthor: { _params: ['authorId'] as const },
byCategory: { _params: ['categoryId'] as const },
},
drafts: {
list: {},
byId: { _params: ['id'] as const },
byAuthor: { _params: ['authorId'] as const },
},
categories: {
list: {},
byId: { _params: ['id'] as const },
bySlug: { _params: ['slug'] as const },
},
},
authors: {
list: {},
byId: { _params: ['id'] as const },
byUsername: { _params: ['username'] as const },
},
comments: {
byPost: { _params: ['postId'] as const },
byAuthor: { _params: ['authorId'] as const },
},
media: {
list: {},
byId: { _params: ['id'] as const },
},
} as const;