Giới thiệu

Trong các ứng dụng web hiện đại, tốc độ phản hồi là yếu tố quyết định trải nghiệm người dùng. Khi API phải truy vấn cơ sở dữ liệu phức tạp hoặc thực hiện các phép tính nặng, thời gian đáp ứng có thể tăng đáng kể. Caching là giải pháp phổ biến để giảm tải cho database và tăng tốc độ trả về. Redis, một store dữ liệu dạng key‑value trong bộ nhớ, được tích hợp dễ dàng với Node.js và Express, giúp chúng ta lưu trữ tạm thời các kết quả truy vấn quan trọng.

Tại sao cần caching

Cache mang lại ba lợi ích chính:

  • Giảm thời gian truy vấn: Dữ liệu được lưu trong RAM, truy cập nhanh hơn hàng chục lần so với disk.
  • Giảm tải cho database: Các truy vấn lặp lại không cần thực hiện lại trên DB, giúp hệ thống chịu tải cao hơn.
  • Tăng khả năng chịu lỗi: Khi database gặp sự cố, các dữ liệu đã được cache vẫn có thể phục vụ người dùng.

Cài đặt Redis và thư viện

Trước tiên, cần cài đặt Redis trên máy chủ (hoặc sử dụng dịch vụ cloud). Sau khi Redis đã chạy, chúng ta thêm thư viện ioredis vào dự án Node.js:

npm install ioredis --save

Triển khai Redis client trong Express

Chúng ta tạo một module riêng để khởi tạo và xuất ra client Redis, giúp tái sử dụng ở mọi nơi trong ứng dụng.

// redisClient.js
const Redis = require('ioredis');
const redis = new Redis({
    host: '127.0.0.1',
    port: 6379,
    // password: 'your_password', // nếu có
});
module.exports = redis;

Sau đó, trong file app.js hoặc server.js, chúng ta import client này để sử dụng.

Cache cho API GET

Giả sử có một endpoint trả về danh sách sản phẩm từ MongoDB. Chúng ta sẽ viết một middleware kiểm tra cache trước khi thực hiện truy vấn.

// cacheMiddleware.js
const redis = require('./redisClient');

function cache(keyPrefix, ttlSeconds) {
    return async function (req, res, next) {
        const key = `${keyPrefix}:${req.originalUrl}`;
        const cached = await redis.get(key);
        if (cached) {
            res.setHeader('X-Cache', 'HIT');
            return res.json(JSON.parse(cached));
        }
        // Ghi lại hàm res.json để lưu cache sau khi có dữ liệu
        const originalJson = res.json.bind(res);
        res.json = async (data) => {
            await redis.setex(key, ttlSeconds, JSON.stringify(data));
            res.setHeader('X-Cache', 'MISS');
            originalJson(data);
        };
        next();
    };
}
module.exports = cache;

Áp dụng middleware vào route:

// productRoutes.js
const express = require('express');
const router = express.Router();
const Product = require('../models/Product');
const cache = require('../cacheMiddleware');

router.get('/products', cache('products', 300), async (req, res) => {
    const products = await Product.find({});
    res.json(products);
});

module.exports = router;

Quản lý TTL và invalidation

Thời gian sống (TTL) của cache nên được thiết lập dựa trên tính chất dữ liệu. Đối với dữ liệu thay đổi thường, TTL ngắn (ví dụ 60‑300 giây) giúp giảm rủi ro dữ liệu lỗi thời. Khi có thao tác cập nhật, xóa hoặc tạo mới, chúng ta cần xóa cache liên quan để đảm bảo tính nhất quán.

// Khi cập nhật sản phẩm
router.put('/products/:id', async (req, res) => {
    const updated = await Product.findByIdAndUpdate(req.params.id, req.body, {new: true});
    // Xóa cache của danh sách sản phẩm
    await redis.del('products:/products');
    res.json(updated);
});

Đối với các endpoint phức tạp, có thể sử dụng pattern key như products:/products?page=2 để xóa toàn bộ các key liên quan bằng lệnh redis.keys (cẩn thận với hiệu suất).

Kết luận

Việc tích hợp Redis cache vào Express không chỉ cải thiện thời gian phản hồi mà còn giảm tải cho database, giúp hệ thống chịu tải tốt hơn trong môi trường sản xuất. Để nắm vững toàn bộ quy trình từ cài đặt, cấu hình đến tối ưu hoá, bạn có thể Tham khảo khóa học "Lập trình Back-End với NodeJS Express" tại đây.