Built-in caching system with support for multiple backends and policies for server methods and general purpose caching in @hapi/hapi.
Create cache policies for storing and retrieving data with configurable expiration and backend storage.
/**
* Create a cache policy
* @param options - Cache policy configuration
* @returns Cache policy instance
*/
cache(options: CachePolicyOptions): CachePolicy;
interface CachePolicyOptions {
/** Cache segment name */
segment?: string;
/** Cache partition name */
partition?: string;
/** Shared cache policy name */
policy?: string;
/** Cache expiration in milliseconds */
expiresIn?: number;
/** Cache expiration at specific time */
expiresAt?: string;
/** Stale cache usage time in milliseconds */
staleIn?: number;
/** Stale cache timeout in milliseconds */
staleTimeout?: number;
/** Generate cache key function */
generateFunc?: Function;
/** Generate cache timeout in milliseconds */
generateTimeout?: number;
/** Drop on generate error */
dropOnError?: boolean;
/** Pending generate timeout in milliseconds */
pendingGenerateTimeout?: number;
/** Cache provider */
cache?: string;
}
interface CachePolicy {
/** Get value from cache */
get(key: string): Promise<CacheResult>;
/** Set value in cache */
set(key: string, value: any, ttl?: number): Promise<void>;
/** Remove value from cache */
drop(key: string): Promise<void>;
/** Check if key exists in cache */
isReady(): boolean;
/** Cache statistics */
stats: CacheStats;
}Usage Examples:
const server = Hapi.server({ port: 3000 });
// Create basic cache policy
const cache = server.cache({
segment: 'user-cache',
expiresIn: 60 * 1000 // 1 minute
});
// Use cache in route handler
server.route({
method: 'GET',
path: '/users/{id}',
handler: async (request, h) => {
const userId = request.params.id;
const cacheKey = `user-${userId}`;
// Try to get from cache first
const cached = await cache.get(cacheKey);
if (cached) {
return cached.item;
}
// Fetch from database
const user = await database.getUser(userId);
// Store in cache
await cache.set(cacheKey, user);
return user;
}
});
// Cache with custom key generation
const sessionCache = server.cache({
segment: 'sessions',
expiresIn: 30 * 60 * 1000, // 30 minutes
generateFunc: async (id) => {
return await database.getSession(id);
},
generateTimeout: 5000
});Cache server methods with automatic key generation and cache invalidation.
/**
* Register server method with caching
* @param name - Method name or method configuration
* @param method - Method function (if name is string)
* @param options - Method options including caching
*/
method(name: string | MethodObject, method?: Function, options?: MethodOptions): void;
interface MethodOptions {
/** Context binding */
bind?: object;
/** Caching configuration */
cache?: MethodCacheOptions;
/** Generate key function */
generateKey?: Function;
}
interface MethodCacheOptions {
/** Cache expiration in milliseconds */
expiresIn?: number;
/** Cache expiration at specific time */
expiresAt?: string;
/** Stale cache usage time */
staleIn?: number;
/** Stale timeout */
staleTimeout?: number;
/** Generate function timeout */
generateTimeout?: number;
/** Generate on error flag */
generateOnError?: boolean;
/** Pending generate timeout */
pendingGenerateTimeout?: number;
/** Cache segment */
segment?: string;
/** Cache partition */
partition?: string;
}Usage Examples:
// Server method with caching
server.method('getUser', async (id) => {
console.log('Fetching user from database:', id);
return await database.getUser(id);
}, {
cache: {
expiresIn: 60 * 1000, // 1 minute
generateTimeout: 5000
},
generateKey: (id) => `user:${id}`
});
// Use cached server method
server.route({
method: 'GET',
path: '/users/{id}',
handler: async (request, h) => {
// This will use cache automatically
const user = await server.methods.getUser(request.params.id);
return user;
}
});
// Server method with complex caching
server.method('getPopularPosts', async (category, limit) => {
return await database.getPopularPosts(category, limit);
}, {
cache: {
expiresIn: 10 * 60 * 1000, // 10 minutes
staleIn: 5 * 60 * 1000, // Allow stale for 5 minutes
staleTimeout: 1000, // 1 second stale timeout
segment: 'popular-posts'
},
generateKey: (category, limit) => `popular:${category}:${limit}`
});Configure cache providers and global cache settings.
interface ServerCacheOptions {
/** Cache provider configuration */
provider?: CacheProvider | string;
/** Provider options */
options?: object;
/** Cache engine name */
engine?: string;
/** Cache partition */
partition?: string;
/** Shared cache instances */
shared?: boolean;
}
interface CacheProvider {
/** Provider constructor */
constructor: Function;
/** Provider options */
options?: object;
}Usage Examples:
// Server with Redis cache
const server = Hapi.server({
port: 3000,
cache: [
{
name: 'redis-cache',
provider: {
constructor: require('@hapi/catbox-redis'),
options: {
host: 'localhost',
port: 6379,
database: 0
}
}
},
{
name: 'memory-cache',
provider: {
constructor: require('@hapi/catbox-memory'),
options: {
maxByteSize: 100 * 1024 * 1024 // 100MB
}
}
}
]
});
// Use specific cache provider
const redisCache = server.cache({
cache: 'redis-cache',
segment: 'user-sessions',
expiresIn: 24 * 60 * 60 * 1000 // 24 hours
});Handle cache results including stale data and cache misses.
interface CacheResult {
/** Cached item */
item: any;
/** Item stored timestamp */
stored: number;
/** Item TTL in milliseconds */
ttl: number;
/** Whether item is stale */
isStale: boolean;
}Usage Examples:
server.route({
method: 'GET',
path: '/data/{id}',
handler: async (request, h) => {
const key = `data-${request.params.id}`;
const result = await cache.get(key);
if (result) {
const response = h.response(result.item);
// Add cache metadata headers
response.header('X-Cache', 'HIT');
response.header('X-Cache-Stored', new Date(result.stored).toISOString());
response.header('X-Cache-TTL', Math.round(result.ttl / 1000));
if (result.isStale) {
response.header('X-Cache-Stale', 'true');
// Refresh cache in background
refreshDataInBackground(request.params.id, key);
}
return response;
}
// Cache miss - fetch and cache
const data = await fetchData(request.params.id);
await cache.set(key, data);
return h.response(data).header('X-Cache', 'MISS');
}
});Monitor cache performance and statistics.
interface CacheStats {
/** Cache hits */
hits: number;
/** Cache misses */
misses: number;
/** Cache sets */
sets: number;
/** Cache gets */
gets: number;
/** Cache errors */
errors: number;
}Usage Examples:
// Monitor cache performance
server.route({
method: 'GET',
path: '/cache/stats',
handler: (request, h) => {
const stats = cache.stats;
const hitRate = stats.hits / (stats.hits + stats.misses) * 100;
return {
hits: stats.hits,
misses: stats.misses,
hitRate: `${hitRate.toFixed(2)}%`,
sets: stats.sets,
gets: stats.gets,
errors: stats.errors
};
}
});
// Reset cache stats
server.route({
method: 'POST',
path: '/cache/reset-stats',
handler: (request, h) => {
cache.stats.reset();
return { message: 'Cache stats reset' };
}
});Warm cache and manage cached data programmatically.
Usage Examples:
// Cache warming on server start
server.ext('onPostStart', async () => {
console.log('Warming cache...');
// Pre-populate frequently accessed data
const popularUsers = await database.getPopularUsers();
for (const user of popularUsers) {
await cache.set(`user-${user.id}`, user);
}
console.log(`Warmed cache with ${popularUsers.length} users`);
});
// Cache invalidation
server.route({
method: 'PUT',
path: '/users/{id}',
handler: async (request, h) => {
const userId = request.params.id;
// Update user
const updatedUser = await database.updateUser(userId, request.payload);
// Invalidate cache
await cache.drop(`user-${userId}`);
// Optionally warm cache with new data
await cache.set(`user-${userId}`, updatedUser);
return updatedUser;
}
});
// Bulk cache operations
server.route({
method: 'DELETE',
path: '/cache/users',
handler: async (request, h) => {
const userIds = request.payload.userIds;
// Drop multiple cache entries
const promises = userIds.map(id => cache.drop(`user-${id}`));
await Promise.all(promises);
return { message: `Invalidated cache for ${userIds.length} users` };
}
});Complex caching scenarios and patterns.
Usage Examples:
// Cache with dependency invalidation
const createDependentCache = (server) => {
const userCache = server.cache({
segment: 'users',
expiresIn: 60 * 60 * 1000 // 1 hour
});
const postCache = server.cache({
segment: 'posts',
expiresIn: 30 * 60 * 1000 // 30 minutes
});
return {
async invalidateUser(userId) {
await userCache.drop(`user-${userId}`);
// Also invalidate user's posts
const userPosts = await database.getUserPostIds(userId);
for (const postId of userPosts) {
await postCache.drop(`post-${postId}`);
}
}
};
};
// Layered caching (L1 memory, L2 Redis)
const createLayeredCache = (server) => {
const l1Cache = server.cache({
cache: 'memory-cache',
segment: 'l1',
expiresIn: 5 * 60 * 1000 // 5 minutes
});
const l2Cache = server.cache({
cache: 'redis-cache',
segment: 'l2',
expiresIn: 60 * 60 * 1000 // 1 hour
});
return {
async get(key) {
// Try L1 first
let result = await l1Cache.get(key);
if (result) {
return result.item;
}
// Try L2
result = await l2Cache.get(key);
if (result) {
// Populate L1
await l1Cache.set(key, result.item);
return result.item;
}
return null;
},
async set(key, value) {
await Promise.all([
l1Cache.set(key, value),
l2Cache.set(key, value)
]);
}
};
};interface CacheProvisionOptions {
/** Provider name */
provider: string | CacheProvider;
/** Provider options */
options?: object;
}
interface MethodObject {
/** Method name */
name: string;
/** Method function */
method: Function;
/** Method options */
options?: MethodOptions;
}