visit
The full code is available on github
Before diving into this post, it is assumed that you have a basic understanding of NestJS and Redis. Familiarity with JavaScript generators is also recommended, as they will be used in the examples to listen to the Redis stream continuously.
We will also use Docker and Docker Compose to create and run our Redis server and application.
NestJS is a typescript framework, so you should be familiar with it too. To familiarize yourself with these things, I recommend:
To get started, we will need to create a new NestJS application. This can be done using the NestJS CLI, which can be installed via npm by running npm i -g @nestjs/cli
. Once installed, you can create a new application by running nest new redis-streams
, where "redis-streams" is the name of your application.
To simplify things, we are going to use docker-compose.yml
file to include both our app and Redis server:
# docker-compose.yml
version: '3'
services:
app:
container_name: app
image: node:18
environment:
HTTPS_METHOD: noredirect
ports:
- 8081:3000
volumes:
- ./:/usr/src/app/
working_dir: /usr/src/app
command: npm run start:dev
redis:
container_name: redis
image: redis:7
restart: always
Now you can run docker-compose up -d
to build and run your services. To see console output, use docker-compose logs
LOG [NestApplication] Nest application successfully started +7ms
node-redis
client library
$ npm i redis
There are also other libraries, e.g. ioredis
is a noteworthy alternative. You can see the list of clients
$ nest g module redis
// redis.module.ts
import { Module } from '@nestjs/common';
@Module({
providers: [],
exports: [],
})
export class RedisModule {}
To use RedisClient
from node-redis
library we are going to create a factory provider for it:
// redis-client.factory.ts
import { FactoryProvider } from '@nestjs/common';
import { createClient } from 'redis';
import { RedisClient, REDIS_CLIENT } from './redis-client.type';
export const redisClientFactory: FactoryProvider<Promise<RedisClient>> = {
provide: REDIS_CLIENT,
useFactory: async () => {
const client = createClient({ url: 'redis://redis:6379/0' });
await client.connect();
return client;
},
};
We create a FactoryProvider
that will call async function provided in useFactory
:
// redis-client.factory.ts
// --snip--
useFactory: async () => {
const client = createClient({ url: 'redis://redis:6379/0' });
await client.connect();
return client;
},
// --snip--
createClient
function from redis
library, and pass it the URL consisting of {protocol}://{host}:{port}/{database}
, where:redis
redis
- this is specified in docker-compose.yml
with container_host: redis
. Usually, you would create an environment variable with your Redis instance IP and use it here.6379
- default Redis port0
default databaseWe connect to the Redis server await client.connect();
You may have noticed that we did not provide an instance of RedisClient
type but REDIS_CLIENT
, which is our injection token. Also, RedisClient
is our custom type, not redis
.
This is due to node-redis
on v4 not exporting the RedisClient
type, so we need to create our own in /redis-client.type.ts
file:
export type RedisClient = ReturnType<typeof createClient>;
export const REDIS_CLIENT = Symbol('REDIS_CLIENT');
// redis.module.ts
// --snip--
@Module({
providers: [redisClientFactory],
})
export class RedisModule {}
Next, let's add a RedisService
that will create a layer of abstraction from RedisClient
.
$ nest g service redis
In there we will inject our RedisClient
// redis.service.ts
// --snip--
@Injectable()
export class RedisService implements OnModuleDestroy {
public constructor(
@Inject(REDIS_CLIENT) private readonly redis: RedisClient,
) {}
onModuleDestroy() {
this.redis.quit();
}
}
Not to leave hanging connections, we are going to close the connection to the Redis by calling this.redis.quit()
on OnModuleDestroy
lifecycle event.
To check that we have successfully connected to Redis, let's add an API endpoint that calls call Redis ping
// redis.service.ts
ping() {
return this.redis.ping();
}
// redis.module.ts
// --snip--
exports: [RedisService],
// --snip--
Now we will import it into AppService
and pass through our call to ping redis:
// app.service.ts
@Injectable()
export class AppService {
// --snip--
constructor(
private readonly redisService: RedisService,
) {}
redisPing() {
return this.redisService.ping();
}
// --snip--
// app.controller.ts
// --snip--
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('redis-ping')
redisPing() {
return this.appService.redisPing();
}
}
Now, with our app and Redis server running, we can open our /redis-ping
endpoint , and you should see the response: