npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fastify-core/redis

v1.0.0

Published

TypeScript Redis cache wrapper with TTL, prefix support, and type-safe API

Downloads

87

Readme

@fasify-core/redis

Redis cache wrapper được xây dựng bằng TypeScript với hỗ trợ TTL, distributed lock, prefix, và type-safe API.

✨ Tính năng chính:

  • ✅ Type-safe với TypeScript
  • ✅ TTL (Time To Live) cho cache
  • ✅ Prefix namespace cho key
  • ✅ Auto serialize/deserialize JSON
  • Distributed Lock (ngăn thundering herd)
  • ✅ Local single-flight (cache request deduplication)
  • ✅ Factory pattern (getOrSet/remember)
  • ✅ Connection pooling & auto-renewal
  • ✅ Comprehensive error handling

📦 Cài đặt

npm install @fasify-core/redis
# hoặc
pnpm add @fasify-core/redis
# hoặc  
yarn add @fasify-core/redis

Yêu cầu: Node.js >= 24.x, Redis >= 6.x


🚀 Quick Start

1. Khởi tạo đơn giản

import { RedisCache } from '@fasify-core/redis';

const cache = new RedisCache();
await cache.connect();

// Lưu dữ liệu
await cache.set('user:1', { id: 1, name: 'John' }, { ttl: 3600 });

// Lấy dữ liệu
const user = await cache.get('user:1');

// Đóng kết nối
await cache.disconnect();

2. Cấu hình đầy đủ

const cache = new RedisCache({
    // URL kết nối Redis
    url: 'redis://:password@localhost:6379',
    
    // Prefix cho tất cả key (optional)
    prefix: 'myapp:',
    
    // TTL mặc định cho cache (giây)
    defaultTTL: 3600,
    
    // Bật distributed lock cho getOrSet()
    distributedLock: true,
    
    // TTL của lock (giây, mặc định 30s)
    lockTTL: 30,
    
    // Delay giữa các lần retry lock (ms, mặc định 10ms)
    lockRetryDelay: 10,
    
    // Số lần retry tối đa (mặc định 50)
    lockRetryAttempts: 50,
    
    // Jitter random để tránh lock conflict (ms, mặc định 50ms)
    lockRetryJitter: 50,
    
    // Tùy chọn Redis client
    clientOptions: {
        // ...Redis client options
    }
});

await cache.connect();

📖 API Reference

| Method | Mô tả | |--------|-------| | connect() | Kết nối đến Redis server | | disconnect() | Đóng kết nối Redis | | set<T>(key, value, options?) | Lưu dữ liệu với TTL tùy chọn | | get<T>(key) | Lấy dữ liệu, trả về null nếu không tồn tại | | has(key) | Kiểm tra key có tồn tại | | update<T>(key, value, options?) | Cập nhật giá trị cache | | delete(key) | Xóa một key | | deleteMany(keys) | Xóa nhiều key cùng lúc | | ttl(key) | Lấy TTL còn lại (giây) | | clear() | Xóa tất cả key theo prefix | | getOrSet<T>(key, factory, options?) | Cache-aside + lock | | remember<T>(key, factory, options?) | Alias của getOrSet | | getClient() | Truy cập Redis client trực tiếp |


💡 Hướng dẫn chi tiết

💡 Hướng dẫn chi tiết

1️⃣ Lưu dữ liệu vào cache

// Lưu string
await cache.set('username', 'john');

// Lưu object
await cache.set('user', { id: 1, name: 'John', email: '[email protected]' });

// Lưu với TTL tùy chỉnh (300 giây)
await cache.set('session:abc123', { token: 'xxx', userId: 1 }, { ttl: 300 });

// Lưu mà không có TTL (permanent)
await cache.set('config:app', { version: '1.0.0' });

// Sử dụng TTL mặc định từ constructor
const cache = new RedisCache({ defaultTTL: 3600 });
await cache.set('data', { foo: 'bar' }); // Auto TTL 3600s

2️⃣ Lấy dữ liệu từ cache

// Lấy dữ liệu cơ bản
const username = await cache.get<string>('username');
console.log(username); // 'john'

// Lấy object với type-safe
interface User {
    id: number;
    name: string;
    email: string;
}

const user = await cache.get<User>('user');
console.log(user?.name); // 'John'

// Nếu key không tồn tại, trả về null
const notFound = await cache.get<User>('user:999');
console.log(notFound); // null

// Ép kiểu
const data = await cache.get<any>('anything');

3️⃣ Kiểm tra cache tồn tại

const exists = await cache.has('username');
if (exists) {
    console.log('Cache found!');
}

// Kết hợp với get
const user = await cache.get<User>('user');
if (user) {
    // Sử dụng dữ liệu
}

4️⃣ Cập nhật cache

// Cập nhật với TTL mặc định
await cache.update('user', { id: 1, name: 'Jane' });

// Cập nhật với TTL tùy chỉnh
await cache.update('user', { id: 1, name: 'Jane' }, { ttl: 1800 });

5️⃣ Xóa cache

// Xóa một key
const deleted = await cache.delete('user');
console.log(deleted); // true nếu xóa thành công, false nếu key không tồn tại

// Xóa nhiều key
const count = await cache.deleteMany(['user', 'session', 'config']);
console.log(count); // 2 (số lượng key đã xóa)

// Xóa tất cả cache theo prefix
const cache = new RedisCache({ prefix: 'myapp:' });
const deletedAll = await cache.clear();
console.log(deletedAll); // tổng số key đã xóa

6️⃣ Lấy TTL của key

const ttl = await cache.ttl('session');

// Kết quả:
// - số dương: thời gian sống còn (giây)
// - -1: key tồn tại nhưng không có TTL
// - -2: key không tồn tại
if (ttl > 0) {
    console.log(`Cache sẽ hết hạn trong ${ttl}s`);
} else if (ttl === -1) {
    console.log('Cache tồn tại vĩnh viễn');
} else {
    console.log('Cache không tồn tại');
}

7️⃣ Distributed Locking (⭐ Advanced)

Vấn đề: Khi nhiều request cùng lúc truy vấn data không có trong cache, tất cả sẽ gọi database → Thundering Herd Problem

Giải pháp: Sử dụng getOrSet() với distributed lock

// Nếu cache miss, chỉ 1 request sẽ gọi factory
// Request khác sẽ chờ kết quả từ request đầu tiên
const user = await cache.getOrSet<User>(
    'user:1',
    async () => {
        console.log('Fetching from database...');
        const data = await db.users.findById(1);
        return data;
    },
    { ttl: 3600 }
);

// Hoặc sử dụng alias 'remember'
const user = await cache.remember<User>(
    'user:1',
    async () => {
        return await db.users.findById(1);
    },
    { ttl: 3600 }
);

Cách hoạt động:

  1. Request A: Cache miss → acquire lock → gọi factory → lưu cache → release lock
  2. Request B,C,D: Cache miss → chờ lock → lấy cache từ Request A
  3. Request E: Cache hit → trả về dữ liệu
// Ví dụ: Fetch API từ external service
const githubUser = await cache.remember<GitHubUser>(
    `github:${username}`,
    async () => {
        const res = await fetch(`https://api.github.com/users/${username}`);
        if (!res.ok) throw new Error('GitHub API error');
        return res.json();
    },
    { ttl: 1800 } // Cache 30 phút
);

8️⃣ Override lock options per-call

// Bật lock cho call này (mặc định từ constructor)
const data = await cache.getOrSet(
    'expensive:data',
    () => computeExpensive(),
    {
        ttl: 3600,
        lock: true,              // Override
        lockTTL: 60,            // TTL lock khác
        lockRetryDelay: 20,     // Delay giữa retry khác
        lockRetryAttempts: 100, // Attempts khác
        lockRetryJitter: 100    // Jitter khác
    }
);

9️⃣ Truy cập Redis client trực tiếp

const client = cache.getClient();

// Sử dụng Redis commands không được wrap
await client.incrby('counter', 1);
await client.lpush('queue', 'item1', 'item2');
const len = await client.llen('queue');

// Thực hiện transaction
const result = await client
    .multi()
    .set('key1', 'value1')
    .set('key2', 'value2')
    .exec();

🔟 Connection Lifecycle

import { RedisCache } from '@fasify-core/redis';

const cache = new RedisCache({ prefix: 'myapp:' });

try {
    // Kết nối lần đầu
    await cache.connect();
    
    // Sử dụng cache
    const data = await cache.get('key');
    
} catch (error) {
    console.error('Redis error:', error);
} finally {
    // Luôn đóng kết nối khi xong
    await cache.disconnect();
}

// Graceful shutdown
process.on('SIGINT', async () => {
    console.log('Shutting down...');
    await cache.disconnect();
    process.exit(0);
});


🎯 Real-world Examples

📝 Caching User Data với Invalidation

import { RedisCache } from '@fasify-core/redis';

const cache = new RedisCache({ prefix: 'users:', defaultTTL: 1800 });
await cache.connect();

interface User {
    id: number;
    name: string;
    email: string;
    role: string;
}

// Lấy user với caching
async function getUser(userId: number): Promise<User | null> {
    return cache.remember<User>(
        `user:${userId}`,
        async () => {
            // Fetch từ database nếu cache miss
            const user = await db.query(
                'SELECT * FROM users WHERE id = $1',
                [userId]
            );
            return user;
        },
        { ttl: 3600 } // Cache 1 giờ
    );
}

// Cập nhật user và invalidate cache
async function updateUser(userId: number, data: Partial<User>): Promise<User> {
    // Update database
    const user = await db.query(
        'UPDATE users SET ? WHERE id = $1 RETURNING *',
        [data, userId]
    );
    
    // Xóa cache
    await cache.delete(`user:${userId}`);
    
    return user;
}

// Xóa user và cache
async function deleteUser(userId: number): Promise<void> {
    await db.query('DELETE FROM users WHERE id = $1', [userId]);
    await cache.delete(`user:${userId}`);
}

// Sử dụng
const user = await getUser(1);
console.log(user);

await updateUser(1, { name: 'New Name' });

🔐 Caching Session/Auth

const cache = new RedisCache({ prefix: 'session:' });
await cache.connect();

interface Session {
    userId: number;
    token: string;
    expiresAt: number;
}

async function createSession(userId: number): Promise<Session> {
    const session: Session = {
        userId,
        token: generateToken(),
        expiresAt: Date.now() + 24 * 60 * 60 * 1000 // 24 hours
    };
    
    // Cache session với TTL 24 giờ
    await cache.set(session.token, session, { ttl: 86400 });
    
    return session;
}

async function getSession(token: string): Promise<Session | null> {
    return cache.get<Session>(token);
}

async function revokeSession(token: string): Promise<void> {
    await cache.delete(token);
}

🌐 Caching API Responses

const cache = new RedisCache({ prefix: 'api:' });
await cache.connect();

interface GitHubUser {
    login: string;
    name: string;
    public_repos: number;
}

async function fetchGitHubUser(username: string): Promise<GitHubUser> {
    return cache.remember<GitHubUser>(
        `github:${username}`,
        async () => {
            const res = await fetch(`https://api.github.com/users/${username}`);
            
            if (!res.ok) {
                throw new Error(`GitHub API error: ${res.statusText}`);
            }
            
            return res.json();
        },
        { ttl: 3600 } // Cache 1 giờ
    );
}

// Ví dụ sử dụng
const user = await fetchGitHubUser('torvalds');
console.log(`${user.name} has ${user.public_repos} public repos`);

📊 Rate Limiting Counter

const cache = new RedisCache({ prefix: 'ratelimit:' });
await cache.connect();

async function checkRateLimit(userId: number, limit: number, window: number): Promise<boolean> {
    const key = `user:${userId}`;
    
    // Lấy client để sử dụng INCR
    const client = cache.getClient();
    const count = await client.incr(key);
    
    // Đặt TTL nếu là lần đầu
    if (count === 1) {
        await client.expire(key, window);
    }
    
    return count <= limit;
}

// Sử dụng
const allowed = await checkRateLimit(123, 100, 3600); // 100 requests per hour
if (!allowed) {
    return res.status(429).send('Too many requests');
}

⚙️ Type Definitions

RedisCacheOptions

interface RedisCacheOptions {
    // Redis URL (default: redis://localhost:6379)
    url?: string;
    
    // Prefix cho tất cả key
    prefix?: string;
    
    // TTL mặc định (giây)
    defaultTTL?: number;
    
    // Redis client options
    clientOptions?: RedisClientOptions;
    
    // Bật distributed lock (default: false)
    distributedLock?: boolean;
    
    // TTL của lock (giây, default: 30)
    lockTTL?: number;
    
    // Delay giữa retry (ms, default: 10)
    lockRetryDelay?: number;
    
    // Max attempts (default: 50)
    lockRetryAttempts?: number;
    
    // Random jitter (ms, default: 50)
    lockRetryJitter?: number;
}

SetOptions

interface SetOptions {
    // TTL cho operation này (override defaultTTL)
    ttl?: number;
}

GetOrSetOptions

interface GetOrSetOptions extends SetOptions {
    // Override distributedLock
    lock?: boolean;
    
    // Override lock TTL
    lockTTL?: number;
    
    // Override retry delay
    lockRetryDelay?: number;
    
    // Override attempts
    lockRetryAttempts?: number;
    
    // Override jitter
    lockRetryJitter?: number;
}

🛡️ Error Handling

import { RedisCache } from '@fasify-core/redis';

const cache = new RedisCache();

try {
    await cache.connect();
    
    const data = await cache.get('key');
    if (!data) {
        console.log('Cache miss');
    }
    
} catch (error) {
    console.error('Redis error:', error);
    
    // Xử lý lỗi
    if (error instanceof Error) {
        if (error.message.includes('ECONNREFUSED')) {
            console.error('Redis server không khả dụng');
        }
    }
} finally {
    await cache.disconnect();
}

📋 Best Practices

1. Luôn sử dụng prefix để namespace

// ✅ Good
const userCache = new RedisCache({ prefix: 'users:' });
const postCache = new RedisCache({ prefix: 'posts:' });

// ❌ Bad - Dễ conflict
const cache1 = new RedisCache();
const cache2 = new RedisCache();

2. Chọn TTL phù hợp

// Static data: 1-24 hours
await cache.set('config', data, { ttl: 86400 });

// Semi-static: 10-60 minutes
await cache.set('user:profile', data, { ttl: 1800 });

// Dynamic: 1-5 minutes
await cache.set('user:settings', data, { ttl: 300 });

// Real-time: No TTL or very short
await cache.set('live:counter', data, { ttl: 10 });

3. Invalidate cache khi update data

async function updateUser(id: number, data: any) {
    // Update database
    await db.users.update(id, data);
    
    // Invalidate cache
    await cache.delete(`user:${id}`);
}

4. Sử dụng getOrSet để tránh thundering herd

// ✅ Good - Tự động lock
const data = await cache.remember('expensive', async () => {
    return await expensiveOperation();
});

// ❌ Bad - Tất cả request gọi DB
const cached = await cache.get('expensive');
if (!cached) {
    const data = await expensiveOperation();
    await cache.set('expensive', data);
}

5. Bật distributed lock cho concurrent traffic

const cache = new RedisCache({
    distributedLock: true, // Bật lock
    lockTTL: 30,
    lockRetryAttempts: 50
});

6. Graceful shutdown

let cache: RedisCache;

async function start() {
    cache = new RedisCache();
    await cache.connect();
}

async function stop() {
    if (cache) {
        await cache.disconnect();
    }
}

// Hook vào process
process.on('SIGTERM', stop);
process.on('SIGINT', stop);

📚 Dependencies

  • redis ^4.0.0

📄 License

MIT

🤝 Contributing

Contributions welcome! Vui lòng submit PR.


Cần giúp đỡ? Mở GitHub Issues