logo

Database Query Caching

Cache expensive database queries using the @cached decorator.

Basic Pattern

import { cached } from '@humanspeak/memory-cache'

class UserRepository {
    @cached<User | null>({ ttl: 60000 })  // 1 minute
    async findById(id: string): Promise<User | null> {
        return await prisma.user.findUnique({
            where: { id },
            include: { profile: true, posts: true }
        })
    }

    @cached<User[]>({ ttl: 30000, maxSize: 50 })  // 30 seconds
    async findByOrganization(orgId: string): Promise<User[]> {
        return await prisma.user.findMany({
            where: { organizationId: orgId },
            orderBy: { createdAt: 'desc' }
        })
    }
}

const repo = new UserRepository()

// First call hits database
const user = await repo.findById('user-123')

// Second call returns cached result
const cachedUser = await repo.findById('user-123')
import { cached } from '@humanspeak/memory-cache'

class UserRepository {
    @cached<User | null>({ ttl: 60000 })  // 1 minute
    async findById(id: string): Promise<User | null> {
        return await prisma.user.findUnique({
            where: { id },
            include: { profile: true, posts: true }
        })
    }

    @cached<User[]>({ ttl: 30000, maxSize: 50 })  // 30 seconds
    async findByOrganization(orgId: string): Promise<User[]> {
        return await prisma.user.findMany({
            where: { organizationId: orgId },
            orderBy: { createdAt: 'desc' }
        })
    }
}

const repo = new UserRepository()

// First call hits database
const user = await repo.findById('user-123')

// Second call returns cached result
const cachedUser = await repo.findById('user-123')

With Hooks for Query Monitoring

import { cached } from '@humanspeak/memory-cache'

class UserRepository {
    @cached<User | null>({
        ttl: 60000,
        hooks: {
            onHit: ({ key }) => {
                metrics.increment('db.cache.hit', { query: 'findById' })
            },
            onMiss: ({ key }) => {
                metrics.increment('db.cache.miss', { query: 'findById' })
            }
        }
    })
    async findById(id: string): Promise<User | null> {
        const start = Date.now()
        const result = await prisma.user.findUnique({ where: { id } })
        metrics.timing('db.query.duration', Date.now() - start)
        return result
    }
}
import { cached } from '@humanspeak/memory-cache'

class UserRepository {
    @cached<User | null>({
        ttl: 60000,
        hooks: {
            onHit: ({ key }) => {
                metrics.increment('db.cache.hit', { query: 'findById' })
            },
            onMiss: ({ key }) => {
                metrics.increment('db.cache.miss', { query: 'findById' })
            }
        }
    })
    async findById(id: string): Promise<User | null> {
        const start = Date.now()
        const result = await prisma.user.findUnique({ where: { id } })
        metrics.timing('db.query.duration', Date.now() - start)
        return result
    }
}

Key Considerations

  • TTL: Short TTLs (30s-2min) for frequently changing data
  • Invalidation: Clear cache after writes to the same data
  • Key Generation: The decorator uses JSON.stringify(args) for keys