Redis
You can use Redis inside your AdonisJS applications using the @adonisjs/redis
package. The package is a thin wrapper on top of ioredis with better DX around Pub/Sub and automatic management of multiple redis connections.
Installation
Install and configure the package using the following command :
node ace add @adonisjs/redis
-
Installs the
@adonisjs/redis
package using the detected package manager. -
Registers the following service provider inside the
adonisrc.ts
file.{providers: [// ...other providers() => import('@adonisjs/redis/redis_provider')]} -
Create
config/redis.ts
file. This file contains the connection configuration for your redis server. -
Define following environment variables and their validation rules.
REDIS_HOST=127.0.0.1REDIS_PORT=6379REDIS_PASSWORD=
Configuration
The configuration for the Redis package is stored inside the config/redis.ts
file.
See also: Config file stub
import env from '#start/env'
import { defineConfig } from '@adonisjs/redis'
const redisConfig = defineConfig({
connection: 'main',
connections: {
main: {
host: env.get('REDIS_HOST'),
port: env.get('REDIS_PORT'),
password: env.get('REDIS_PASSWORD', ''),
db: 0,
keyPrefix: '',
},
},
})
export default redisConfig
-
connection
-
The
connection
property defines the connection to use by default. When you run redis commands without choosing an explicit connection, they will be executed against the default connection. -
connections
-
The
connections
property is a collection of multiple named connections. You can define one or more connections inside this object and switch between them using theredis.connection()
method.Every named connection config is identical to the config accepted by ioredis.
Configuring clusters
The @adonisjs/redis
package will create a cluster connection if you define an array of hosts inside the connection config. For example:
const redisConfig = defineConfig({
connections: {
main: {
clusters: [
{ host: '127.0.0.1', port: 6380 },
{ host: '127.0.0.1', port: 6381 },
],
clusterOptions: {
scaleReads: 'slave',
slotsRefreshTimeout: 10 * 1000,
},
},
},
})
Configuring sentinels
You can configure a redis connection to use sentinels by defining an array of sentinel nodes within the connection config. For example:
See also: IORedis docs on Sentinels config
const redisConfig = defineConfig({
connections: {
main: {
sentinels: [
{ host: 'localhost', port: 26379 },
{ host: 'localhost', port: 26380 },
],
name: 'mymaster',
},
},
})
Usage
You can run redis commands using the redis
service exported by the package. The redis service is a singleton object configured using the configuration you have defined inside the config/redis.ts
file.
Consult the ioredis documentation to view the list of available methods. Since we are a wrapper on top of IORedis, the commands API is identical.
import redis from '@adonisjs/redis/services/main'
await redis.set('username', 'virk')
const username = await redis.get('username')
Switching between connections
Commands executed using the redis
service are invoked against the default connection defined inside the config file. However, you can execute commands on a specific connection by first getting an instance of it.
The .connection()
method creates and caches a connection instance for the process's lifetime.
import redis from '@adonisjs/redis/services/main'
// Get connection instance
const redisMain = redis.connection('main')
await redisMain.set('username', 'virk')
const username = await redisMain.get('username')
Quitting connections
The connections are long-lived, and you will get the same instance every time you call the .connection()
method. You can quit the connection using the quit
method. Use the disconnect
method to end the connection forcefully.
import redis from '@adonisjs/redis/services/main'
await redis.quit('main') // Quit the main connection
await redis.disconnect('main') // Force quit the main connection
import redis from '@adonisjs/redis/services/main'
const redisMain = redis.connection('main')
redisMain.quit() // Quit using connection instance
redisMain.disconnect() // Force quit using connection instance
Error handling
Redis connections can fail anytime during the lifecycle of your application. Therefore it is essential to capture the errors and have a retry strategy.
By default, AdonisJS will log the redis connection errors using the application logger and retry a connection ten times before closing it permanently. The retry strategy is defined for every connection within the config/redis.ts
file.
See also: IORedis docs on auto reconnect
{
main: {
host: env.get('REDIS_HOST'),
port: env.get('REDIS_PORT'),
password: env.get('REDIS_PASSWORD', ''),
retryStrategy(times) {
return times > 10 ? null : times * 50
},
},
}
You can disable the default error reporter using the .doNotLogErrors
method. Doing so will remove the error
event listener from the redis connection.
import redis from '@adonisjs/redis/services/main'
/**
* Disable default error reporter
*/
redis.doNotLogErrors()
redis.on('connection', (connection) => {
/**
* Make sure always to have an error listener defined.
* Otherwise, the app will crash
*/
connection.on('error', (error) => {
console.log(error)
})
})
Pub/Sub
Redis needs multiple connections to publish and subscribe to channels. The subscriber connection cannot perform operations other than subscribing to new channels/patterns and unsubscribing.
When using the @adonisjs/redis
package, you do not have to create a subscriber connection manually; we will handle that for you. When you call the subscribe
method for the first time, we will automatically create a new subscriber connection.
import redis from '@adonisjs/redis/services/main'
redis.subscribe('user:add', function (message) {
console.log(message)
})
API differences between IORedis and AdonisJS
When using ioredis
, you must use two different APIs to subscribe to a channel and listen for new messages. However, with the AdonisJS wrapper, the subscribe
method takes care of both.
redis.on('message', (channel, messages) => {
console.log(message)
})
redis.subscribe('user:add', (error, count) => {
if (error) {
console.log(error)
}
})
redis.subscribe('user:add', (message) => {
console.log(message)
},
{
onError(error) {
console.log(error)
},
onSubscription(count) {
console.log(count)
},
})
Publishing messages
You can publish messages using the publish
method. The method accepts the channel name as the first parameter and the data to publish as the second parameter.
redis.publish(
'user:add',
JSON.stringify({
id: 1,
username: 'virk',
})
)
Subscribing to patterns
You can subscribe to patterns using the psubscribe
method. Similar to the subscribe
method, it will create a subscriber connection (if one does not exist).
redis.psubscribe('user:*', (channel, message) => {
console.log(channel)
console.log(message)
})
redis.publish(
'user:add',
JSON.stringify({
id: 1,
username: 'virk',
})
)
Unsubscribing
You can unsubscribe from channels or patterns using the unsubscribe
and punsubscribe
methods.
await redis.unsubscribe('user:add')
await redis.punsubscribe('user:*add*')
Using Lua scripts
You can register Lua Scripts as commands with the redis service, and they will be applied to all the connections.
See also: IORedis docs on Lua Scripting
import redis from '@adonisjs/redis/services/main'
redis.defineCommand('release', {
numberOfKeys: 2,
lua: `
redis.call('zrem', KEYS[2], ARGV[1])
redis.call('zadd', KEYS[1], ARGV[2], ARGV[1])
return true
`,
})
Once you have defined a command, you can execute it using the runCommand
method. First, all the keys are defined, and then the arguments.
redis.runCommand(
'release', // command name
'jobs:completed', // key 1
'jobs:running', // key 2
'11023', // argv 1
100 // argv 2
)
The same command can be executed on an explicit connection.
redis.connection('jobs').runCommand(
'release', // command name
'jobs:completed', // key 1
'jobs:running', // key 2
'11023', // argv 1
100 // argv 2
)
Finally, you can also define commands with a specific connection instance. For example:
redis.on('connection', (connection) => {
if (connection.connectionName === 'jobs') {
connection.defineCommand('release', {
numberOfKeys: 2,
lua: `
redis.call('zrem', KEYS[2], ARGV[1])
redis.call('zadd', KEYS[1], ARGV[2], ARGV[1])
return true
`,
})
}
})
Transforming arguments and replies
You can define the arguments transformer and the reply transformer using the redis.Command
property. The API is identical to the IORedis API.
import redis from '@adonisjs/redis/services/main'
redis.Command.setArgumentTransformer('hmset', (args) => {
if (args.length === 2) {
if (args[1] instanceof Map) {
// utils is an internal module of ioredis
return [args[0], ...utils.convertMapToArray(args[1])]
}
if (typeof args[1] === 'object' && args[1] !== null) {
return [args[0], ...utils.convertObjectToArray(args[1])]
}
}
return args
})
import redis from '@adonisjs/redis/services/main'
redis.Command.setReplyTransformer('hgetall', (result) => {
if (Array.isArray(result)) {
const obj = {}
for (let i = 0; i < result.length; i += 2) {
obj[result[i]] = result[i + 1]
}
return obj
}
return result
})
Events
Following is the list of events emitted by a Redis connection instance.
connect / subscriber:connect
The event is emitted when a connection is made. The subscriber:connect
event is emitted when a subscriber connection is made.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('connect', () => {})
connection.on('subscriber:connect', () => {})
})
wait
Emitted when the connection is in wait
mode because the lazyConnect
option is set inside the config. After executing the first command, the connection will be moved from the wait
state.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('wait', () => {})
})
ready / subscriber:ready
The event will be emitted immediately after the connect
event unless you have enabled the enableReadyCheck
flag inside the config. In that case, we will wait for the Redis server to report it is ready to accept commands.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('ready', () => {})
connection.on('subscriber:ready', () => {})
})
error / subscriber:error
The event is emitted when unable to connect to the redis server. See error handling to learn how AdonisJS handles connection errors.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('error', () => {})
connection.on('subscriber:error', () => {})
})
close / subscriber:close
The event is emitted when a connection is closed. IORedis might retry establishing a connection after emitting the close
event, depending upon the retry strategy.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('close', () => {})
connection.on('subscriber:close', () => {})
})
reconnecting / subscriber:reconnecting
The event is emitted when trying to reconnect to the redis server after the close
event.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('reconnecting', ({ waitTime }) => {
console.log(waitTime)
})
connection.on('subscriber:reconnecting', ({ waitTime }) => {
console.log(waitTime)
})
})
end / subscriber:end
The event is emitted when the connection has been closed, and no further reconnections will be made. It should be the end of the connection lifecycle.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('end', () => {})
connection.on('subscriber:end', () => {})
})
node:added
The event is emitted when connected to a new cluster node (Applicable to cluster instances only).
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('node:added', () => {})
})
node:removed
The event is emitted when a cluster node is removed (Applicable to cluster instances only).
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('node:removed', () => {})
})
node:error
The event is emitted when unable to connect to a cluster node (Applicable to cluster instances only).
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('node:error', ({ error, address }) => {
console.log(error, address)
})
})
subscription:ready / psubscription:ready
The event is emitted when a subscription on a given channel or a pattern has been established.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('subscription:ready', ({ count }) => {
console.log(count)
})
connection.on('psubscription:ready', ({ count }) => {
console.log(count)
})
})
subscription:error / psubscription:error
The event is emitted when unable to subscribe to a channel or a pattern.
import redis from '@adonisjs/redis/services/main'
redis.on('connection', (connection) => {
connection.on('subscription:error', () => {})
connection.on('psubscription:error', () => {})
})