What is Caching?
Before we start, please note that you can find the github repository with the completed project
Caching is a fairly old technique designed to improve your application’s performance and reliability.
Caching involves saving frequently requested data in an intermediary store called the “cache store” to avoid unnecessary calls to the primary database.
An HTTP request asking for data cached by the server will receive it directly from the cache store instead of getting it from a database. Which is much faster!
Why do you need caching?
Any web application that has some success will eventually run into bottlenecks. The most common bottleneck is usually related to how information is fetched from a primary database, like Postgres or MySQL.
Indeed, as the number of users grows, so does the number of HTTP requests made to the server. This results in the same data being fetched all over again and again. Optimizing your application for speed and efficiency is important by caching frequently requested data.
Since most relational databases involve structured data, they are optimised for reliability and not for speed. That means the data they store on a disk is many times slower than the RAM. Using a NoSQL database does not bring any tremendous performance gains either.
The solution is to use an in-memory cache-store.
In this tutorial, we will implement caching in NestJs and ultimately scale it with Redis, a fast in-memory database that is perfect for this use case.
Pre-requisites
- A NestJs starter project ready
- Node version 16 or greater
- Docker
Add an in-memory cache using the NestJs Cache Module
We will start by implementing the in-memory cache manager provided by NestJs, it will save the cache into the server’s RAM. Once ready, we will transition to Redis for a more scalable caching solution.
The NestJs CacheModule is included in the `@nestjs/commonpackage. You will need to add it to your app.module.ts file.
app.module.ts
import { CacheModule, Module } from "@nestjs/common";
import { AppController } from "./app.controller";@Module({
imports: [
CacheModule.register({
isGlobal: true,
}),
],
controllers: [AppController],
})
export class AppModule {}
Note that we declare the module as global with isGlobal set to true. This way we don’t need to re-import the caching module if we want to use it in a specific service or controller.
The Cache Module handles a lot of cache configuration for us, and we will customize it later. Let’s just point out that we can use caching with two different approaches:
- The Interceptor approach
- The Cache Manager approach with dependency injection
Let’s briefly go through the pros and cons of each of them
When to use Interceptor vs Cache Manager in NestJs?
The interceptor approach is cleaner, but the cache manager approach gives you more flexibility with some overhead.
As a rule of thumb, you will use the Cache Interceptor If you need an endpoint to return cached data from the primary database in a traditional CRUD app.
However, if you need more control or do not necessarily want to return cached data, you will use the cache manager service as dependency injection.
So to summarise…
You will use the Cache Manager if you need more control, like:
- Deleting from cache
- Updating cache
- Manually fetching data from the cache store
- A combination of the above 👆🏻
To give a practical example, if you need to get a list of posts and you have an endpoint that fetches that list from the database. You need to use a cache interceptor.
For anything more complex, the cache manager will be required.
Caching in NestJs using the Cache Interceptor
Let’s start with the interceptor, as it allows you to auto-cache responses from your API. You can apply the cache interceptor to any endpoint that you want to cache.
We’ll create an `src/utils.tsfile that will store a getter function with a small timeout to simulate some database delay.
utils.ts
// gets an array of dogs after 1 second delay
export function getDogs() {
return new Promise((resolve, ) => {
setTimeout(() => {
resolve([
{
id: 1,
name: "Luna",
breed: "Caucasian Shepherd",
},
{
id: 2,
name: "Ralph",
breed: "Husky",
},
]);
}, 1000);
});
}
Now that we have a getter function for our dogs, we can use it in the app.controller.ts
app.controller.ts
import { Controller, Get } from "@nestjs/common";
import { getDogs } from "./utils";@Controller()
export class AppController {
@Get("dogs")
getDogs() {
return getDogs();
}
}
Let’s add some cache! Adding caching with interceptors is as simple as this 👇🏻
app.controller.ts
import {
CacheInterceptor,
Controller,
Get,
UseInterceptors,
} from "@nestjs/common";
import { getDogs } from "./utils";@Controller()
export class AppController {
@UseInterceptors(CacheInterceptor)
@Get("dogs")
getDogs() {
return getDogs();
}
}