Express, Fastify & NestJS APIs
Patterns for integrating Cachemate into Node.js HTTP frameworks for endpoint-level caching and mutation invalidation.
1. Express Middleware Pattern
Create a reusable endpoint cache helper in Express:
src/middleware/cache.ts
import type { Request, Response, NextFunction } from 'express';
import { client } from '../lib/cache';
export function cacheEndpoint(tables: string[], ttlSeconds = 60) {
return async (req: Request, res: Response, next: NextFunction) => {
// Only cache GET requests
if (req.method !== 'GET') {
return next();
}
const filters = {
path: req.baseUrl + req.path,
query: req.query,
params: req.params,
};
try {
const data = await client.cache({
tables,
filters,
ttlSeconds,
loader: async () => {
// Wrap original res.json to capture downstream controller response
return await new Promise((resolve) => {
const originalJson = res.json.bind(res);
res.json = (body: any) => {
resolve(body);
return originalJson(body);
};
next();
});
},
});
// If resolved from cache without calling next():
if (!res.headersSent) {
res.setHeader('X-Cache-Status', 'HIT');
res.json(data);
}
} catch (err) {
next(err);
}
};
}
Usage in Express Routes
src/routes/users.ts
import { Router } from 'express';
import { client } from '../lib/cache';
import { cacheEndpoint } from '../middleware/cache';
export const userRouter = Router();
// GET /api/users/:id - Cached against 'users' table
userRouter.get('/:id', cacheEndpoint(['users'], 300), async (req, res) => {
const user = await db.users.findUnique({ where: { id: req.params.id } });
res.json(user);
});
// POST /api/users/:id - Mutates user and invalidates cache
userRouter.patch('/:id', async (req, res) => {
const updated = await db.users.update({
where: { id: req.params.id },
data: req.body,
});
// Automatically invalidates all user endpoint caches
await client.invalidate('users');
res.json(updated);
});
2. Fastify Integration
src/plugins/cachemate.ts
import fp from 'fastify-plugin';
import type { FastifyPluginAsync } from 'fastify';
import { Client } from 'cachemate';
export const cachematePlugin: FastifyPluginAsync = fp(async (fastify) => {
const client = new Client({
memoryMaxMb: 128,
redisUrls: [process.env.REDIS_URL!],
registry: {
entries: {
products: { tag: 'p', versionKey: 'cm:ver:products' },
},
},
});
fastify.decorate('cachemate', client);
fastify.addHook('onClose', async () => {
await client.close();
});
});
3. NestJS Interceptor Pattern
src/cache/cachemate.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable, from } from 'rxjs';
import { Client } from 'cachemate';
@Injectable()
export class CachemateInterceptor implements NestInterceptor {
constructor(
private readonly client: Client,
private readonly tables: string[],
private readonly ttlSeconds = 60
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const http = context.switchToHttp();
const req = http.getRequest();
if (req.method !== 'GET') {
return next.handle();
}
const filters = { url: req.url, query: req.query, params: req.params };
return from(
this.client.cache({
tables: this.tables,
filters,
ttlSeconds: this.ttlSeconds,
loader: async () => {
return await next.handle().toPromise();
},
})
);
}
}