In this third part of the NestJS learning series, we will go over how to enable caching and integrating Azure Redis Cache.
This is the third part of the NestJS learning series.
In this part, we will learn about caching. This part of the series will cover the following topics:
NestJS offers a built-in CacheModule that enables easy integration of caching functionality. Caching can be applied at different levels:
It supports various caching stores, including:
By default, NestJS uses an in-memory cache. To get started with caching, begin by installing the cache manager package in your NestJS project.
npm install @nestjs/cache-manager cache-manager
After installing, to use the in-memory store, import and register CacheModule in AppModule.
import { CacheModule } from '@nestjs/cache-manager';
Next, import the CacheModule in the AppModule as shown below,
@Module({
imports: [
CacheModule.register({
ttl:30000
}),
],
controllers: [AppController, ProductController, BookController],
providers: [AppService, ProductService, BookService],
})
export class AppModule {}
Here, we’ve set the Time-To-Live (TTL) value to 30,000 milliseconds (30 seconds). This means NestJS will cache the data for that duration before sending a new request to the database.
Caching can be enabled at the route level by using the @UseInterceptors()
decorator and passing CacheInterceptor
as its argument.
@Get()
@UseInterceptors(CacheInterceptor)
async findAll() {
try {
const books = await this.bookService.findAll();
return books;
} catch (error) {
throw new Error('Error fetching books');
}
}
Now, instead of querying the database on every request, the /book
endpoint caches the data for 30,000 milliseconds (30 seconds) and only makes a new database call after the cache expires.
To customize caching behavior, various configurations can be set for the CacheModule
. They are as follows:
ttl
– Time to live for cache itemsmax
– Maximum number of items in cacheisGlobal
– If true, the cache is available globally in the applicationstore
– The cache store to use; it could be in-memory or an external storeBesides the above properties, other settings, such as port and password, of the external store can also be configured here.
@Module({
imports: [
CacheModule.register({
ttl:30000,
max: 100,
isGlobal: true,
store: 'memory',
}),
],
controllers: [AppController, BookController],
providers: [AppService, BookService],
})
export class AppModule {}
If required, you can override the global cache setting at the route level. For example, to override the TTL value, use the @CacheTTL()
decorator.
@Get()
@UseInterceptors(CacheInterceptor)
@CacheTTL(60)
async findAll() {
try {
return await this.bookService.findAll();
} catch (error) {
throw new Error('Error fetching books');
}
}
Two important points you should keep in mind are that:
GET()
endpoints are cached.@Res()
cannot use the Cache Interceptor.As the native response object @Res()
cannot be used with caching and you are following the NestJS article series, make sure to update the BookController
from Part 2 to avoid using @Res
, as shown below:
import { Body, Controller, Delete, Get, HttpStatus, Param, Post, Put, Res, UseInterceptors } from '@nestjs/common';
import { BookService } from './book.service';
import { Book } from './book.entity';
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
@Controller('book')
export class BookController {
constructor(private readonly bookService: BookService) {}
@Post()
async create(@Body() bookData: Partial<Book>) {
try {
return await this.bookService.create(bookData);
} catch (error) {
throw new Error('Error creating book');
}
}
@Get()
@UseInterceptors(CacheInterceptor)
@CacheTTL(60)
async findAll() {
try {
return await this.bookService.findAll();
} catch (error) {
throw new Error('Error fetching books');
}
}
@Get(':id')
async findOne(@Param('id') id: string) {
try {
const book = await this.bookService.findOne(Number(id));
if (book) {
return book;
} else {
throw new Error('Book not found');
}
} catch (error) {
throw new Error('Error fetching book');
}
}
@Delete(':id')
async remove(@Param('id') id: string) {
try {
const result = await this.bookService.remove(Number(id));
if (result.affected && result.affected > 0) {
return { message: 'Book deleted' };
} else {
throw new Error('Book not found');
}
} catch (error) {
throw new Error('Error deleting book');
}
}
@Put(':id')
async update(
@Param('id') id: string,
@Body() updateData: Partial<Book>
) {
try {
const updatedBook = await this.bookService.update(Number(id), updateData);
if (updatedBook) {
return updatedBook;
} else {
throw new Error('Book not found');
}
} catch (error) {
throw new Error('Error updating book');
}
}
}
If you do not update the BookController
to avoid using the @Res
object, caching may not work as expected.
To integrate Azure Cache for Redis, configure Azure Cache for Redis in the Azure Portal.
You should have Redis cache configured as below:
You will need the following details:
In the AppModule, register the CacheModule
using the Azure Cache host name, port and password as shown below:
CacheModule.register({
store: 'redis',
host: your host name ,
port: 10000,
password: primary key value',
tls: {},
ttl: 50000,
isGlobal: false,
}),
This is all the configuration you need to use Azure Redis Cache for caching data in a NestJS API.
Until now, we have used default keys for caching. However, using custom keys offers several advantages.
Using keys in Redis provides precise, efficient and flexible control over cached data, helping your application run faster and remain easier to manage.
Let’s see how custom keys can be used for caching. We will modify the FindAll()
method in the BookService
to use a custom key for caching.
First, inject the CACHE_MANAGER
in the BookService
as shown below :
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache
) { }
Also, do not forget to import CacheManager
and Cache
in the BookService
.
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
Next, define a variable for the custom key:
private readonly bookCacheKey = 'my_custom_books_key';
Next, modify the findAll()
method to retrieve data from the cache, and if no cached data is available, read data from the database.
async findAll(): Promise<Book[]> {
// Try to get from cache first
let books = await this.cacheManager.get<Book[]>(this.bookCacheKey);
if (books) {
return books;
}
// If not cached, fetch from DB and cache it
books = await this.bookRepository.find();
await this.cacheManager.set(this.bookCacheKey, books, 1000);
return books;
}
In the BookController
, to use updated FindAll()
function from the service:
@CacheKey()
CacheInterceptor
Since caching is now handled within the service, the BookController’s findAll()
method becomes as simple as shown below:
@Get()
async findAll() {
try {
return await this.bookService.findAll();
} catch (error) {
throw new Error('Error fetching books');
}
}
In this way, you can use a custom key for the caching.
Caching is a powerful technique for enhancing API performance. In this article, you learned the fundamentals of implementing caching in NestJS, equipping you to integrate it into your API projects. I hope you found it helpful—thanks for reading!
Dhananjay Kumar is a well-known trainer and developer evangelist. He is the founder of NomadCoder, a company that focuses on creating job-ready developers through training in technologies such as Angular, Node.js, Python, .NET, Azure, GenAI and more. He is also the founder of ng-India, one of the world’s largest Angular communities. He lives in Gurgaon, India, and is currently writing his second book on Angular. You can reach out to him for training, evangelism and consulting opportunities.