Caching is a technique to store a copy of the data in a faster storage system (i.e. cache) to speed up the data retrieval.
Caching is used to store the data that the users frequently access. It helps reduce the load on the main storage system and improves the application’s performance.
Redis is an open-source in-memory data structure store that can be used as a database, cache, and message broker.
It supports various data structures such as strings, hashes, lists, sets, sorted sets, bitmaps, hyperloglogs, and geospatial indexes with radius queries.
If you have not installed Redis, you can use https://console.upstash.com/ to create a Redis server.
Get the connection string from the dashboard.
Create a new node.js application with express.
Or you can use my node starter template Node-ENV
redis
package using yarn.1yarn add ioredis @types/ioredis
note: i use yarn
because it’s faster than npm and can install packages in parallel.
serve the Redis server connection string in the .env
file or use the connection string directly in the code.
Create a new file redis.ts
and connect to the Redis server.
1import dotenv from 'dotenv';
2import Redis from 'ioredis';
3import { LOGGER } from '../logging';
4
5dotenv.config({ path: `${process.cwd()}/.env` });
6
7const { REDIS_URL } = process.env;
8
9if (!REDIS_URL) {
10 LOGGER.error('Please make sure that you have a valid Redis URL in your .env file');
11 process.exit(1);
12}
13
14export const client = new Redis(REDIS_URL);
15
16client.on('connect', () => {
17 LOGGER.info('Connected to Redis');
18});
19
20client.on('error', (err) => {
21 LOGGER.error(`Redis error: ${err}`);
22 process.exit(1);
23});
If you aren’t familiar with logging, you can use console.log
instead.
cache.ts
and implement the caching functionality. 1import mongoose, { Document } from 'mongoose';
2import { client } from '../database/redis';
3
4declare module 'mongoose' {
5 interface Query<ResultType, DocType, THelpers, RawDocType = DocType> {
6 useCache: boolean;
7 hashKey: string;
8 cache(options?: { key?: string }): this;
9 }
10}
11
12const exec: typeof mongoose.Query.prototype.exec = mongoose.Query.prototype.exec;
13
14mongoose.Query.prototype.cache = function (options = {}) {
15 this.useCache = true;
16 this.hashKey = JSON.stringify(options.key || '');
17 return this;
18};
19
20mongoose.Query.prototype.exec = async function <T extends Document>(): Promise<T[]> {
21 if (!this.useCache) {
22 return exec.call(this);
23 }
24
25 const key = JSON.stringify(
26 Object.assign({}, this.getQuery(), {
27 collection: this.model.collection.name,
28 })
29 );
30
31 const cacheValue = await client.hget(this.hashKey, key);
32
33 if (cacheValue) {
34 const doc = JSON.parse(cacheValue);
35 return Array.isArray(doc)
36 ? doc.map((d: any) => new this.model(d))
37 : [new this.model(doc)];
38 }
39
40 const result = await exec.call(this);
41 await client.hset(this.hashKey, key, JSON.stringify(result), 'EX', 1000);
42
43 return result;
44};
45
46export const clearHash = (hashKey: string): void => {
47 client.del(JSON.stringify(hashKey));
48};
Let’s understand the code above.
First, we are extending the mongoose.Query
interface to add the cache
method.
The cache
method is used to enable caching for the query.
The hashKey
is used to store the cache data in the Redis server.
The exec
method is overridden to implement the caching functionality.
If the useCache
is set to true
, then we check if the data is available in the cache.
If the data is available in the cache, then we return the data from the cache.
If the data is not available in the cache, then we execute the original exec
method and store the data in the cache.
The clearHash
method is used to clear the cache data for a specific hashKey
.
cleanCache.ts
and implement the clean cache middleware.1import { NextFunction, Response } from 'express';
2import { clearHash } from '../services/cache.service';
3
4export default async (req: Request, res: Response, next: NextFunction) => {
5 await next();
6 clearHash((req as any).user.id.toString());
7};
Let’s understand the code above.
The cleanCache
middleware is used to clear the cache data after the request is completed.
await next()
is used to execute the next middleware then we use the clearHash
method to clear the cache data for the specific hashKey
.
We are using the clearHash
method to clear the cache data for the specific hashKey
.