Skip to content

Commit ed11680

Browse files
authored
[Feature] Redis 설치 및 연결 (#350)
* feat: redis 연결 및 repository 생성 * feat: docker compose redis 추가 * feat: 서버 시작 시, redis 연결 여부 확인
1 parent 3bf8bd3 commit ed11680

File tree

7 files changed

+211
-2
lines changed

7 files changed

+211
-2
lines changed

backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"class-validator": "^0.14.1",
3838
"cookie-parser": "^1.4.7",
3939
"dotenv": "^17.2.3",
40+
"ioredis": "^5.9.2",
4041
"passport": "^0.7.0",
4142
"passport-github2": "^0.1.12",
4243
"passport-jwt": "^4.0.1",
@@ -57,6 +58,7 @@
5758
"@nestjs/testing": "^11.0.1",
5859
"@types/cookie-parser": "^1.4.10",
5960
"@types/express": "^5.0.0",
61+
"@types/ioredis": "^5.0.0",
6062
"@types/jest": "^30.0.0",
6163
"@types/node": "^22.19.3",
6264
"@types/passport": "^1.0.17",

backend/src/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { OauthModule } from './oauth/oauth.module'
77
import { MetricsModule } from './metrics/metrics.module'
88
import { HttpMetricsMiddleware } from './metrics/http-metrics.middleware'
99
import { GeminiModule } from './gemini/gemini.module'
10+
import { RedisModule } from './redis/redis.module'
1011

1112
@Module({
12-
imports: [ConfigModule.forRoot({ isGlobal: true }), GeminiModule, BattlesModule, OauthModule, MetricsModule],
13+
imports: [ConfigModule.forRoot({ isGlobal: true }), RedisModule, GeminiModule, BattlesModule, OauthModule, MetricsModule],
1314
controllers: [AppController],
1415
providers: [AppService],
1516
})

backend/src/redis/redis.const.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const REDIS_CLIENT = 'REDIS_CLIENT'

backend/src/redis/redis.module.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Module, Global } from '@nestjs/common'
2+
import { ConfigService } from '@nestjs/config'
3+
import Redis from 'ioredis'
4+
import { RedisRepository } from './redis.repository'
5+
import { REDIS_CLIENT } from './redis.const'
6+
7+
@Global()
8+
@Module({
9+
providers: [
10+
{
11+
provide: REDIS_CLIENT,
12+
inject: [ConfigService],
13+
useFactory: (configService: ConfigService) => {
14+
return new Redis({
15+
host: configService.get<string>('REDIS_HOST') || 'localhost',
16+
port: configService.get<number>('REDIS_PORT') || 6379,
17+
retryStrategy: times => {
18+
const MAX_RETRIES = 10
19+
const DELAY = 3000
20+
21+
if (times > MAX_RETRIES) {
22+
return null
23+
}
24+
25+
return DELAY
26+
},
27+
})
28+
},
29+
},
30+
RedisRepository,
31+
],
32+
exports: [RedisRepository],
33+
})
34+
export class RedisModule {}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Injectable, Inject, OnModuleDestroy, Logger } from '@nestjs/common'
2+
import Redis from 'ioredis'
3+
import { REDIS_CLIENT } from './redis.const'
4+
@Injectable()
5+
export class RedisRepository implements OnModuleDestroy {
6+
private readonly logger = new Logger(RedisRepository.name)
7+
8+
constructor(@Inject(REDIS_CLIENT) private readonly redisClient: Redis) {
9+
this.bindEvents()
10+
}
11+
12+
private bindEvents() {
13+
this.redisClient.on('connect', () => {
14+
this.logger.log('🚀 Redis connection established')
15+
})
16+
17+
this.redisClient.on('ready', () => {
18+
this.logger.log('✅ Redis client ready to use')
19+
})
20+
21+
this.redisClient.on('error', err => {
22+
this.logger.error(`❌ Redis Error: ${err.message}`, err.stack)
23+
})
24+
25+
this.redisClient.on('close', () => {
26+
this.logger.warn('⚠️ Redis connection closed')
27+
})
28+
}
29+
30+
async onModuleDestroy() {
31+
await this.redisClient.quit()
32+
}
33+
34+
async get(key: string): Promise<string | null> {
35+
return this.redisClient.get(key)
36+
}
37+
38+
async set(key: string, value: string, ttl?: number): Promise<'OK'> {
39+
if (ttl) {
40+
return this.redisClient.setex(key, ttl, value)
41+
}
42+
return this.redisClient.set(key, value)
43+
}
44+
45+
async del(key: string): Promise<number> {
46+
return this.redisClient.del(key)
47+
}
48+
49+
async exists(key: string): Promise<boolean> {
50+
const result = await this.redisClient.exists(key)
51+
return result === 1
52+
}
53+
54+
async hset(key: string, field: string, value: string): Promise<number> {
55+
return this.redisClient.hset(key, field, value)
56+
}
57+
58+
async hget(key: string, field: string): Promise<string | null> {
59+
return this.redisClient.hget(key, field)
60+
}
61+
62+
async hgetall(key: string): Promise<Record<string, string>> {
63+
return this.redisClient.hgetall(key)
64+
}
65+
66+
async lpush(key: string, ...values: string[]): Promise<number> {
67+
return this.redisClient.lpush(key, ...values)
68+
}
69+
70+
async lrange(key: string, start: number, stop: number): Promise<string[]> {
71+
return this.redisClient.lrange(key, start, stop)
72+
}
73+
74+
getRedisClient(): Redis {
75+
return this.redisClient
76+
}
77+
}

docker-compose-prod.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ services:
1919
ports:
2020
- "3000:3000"
2121
restart: unless-stopped
22+
depends_on:
23+
redis:
24+
condition: service_healthy
2225

2326
prometheus:
2427
image: prom/prometheus:latest
@@ -62,4 +65,21 @@ services:
6265
- "80:80"
6366
depends_on:
6467
- backend
65-
restart: unless-stopped
68+
restart: unless-stopped
69+
70+
redis:
71+
image: redis:alpine
72+
command: redis-server --appendonly yes
73+
ports:
74+
- "6379:6379"
75+
volumes:
76+
- redis-data:/data
77+
healthcheck:
78+
test: ["CMD", "redis-cli", "ping"]
79+
interval: 10s
80+
timeout: 5s
81+
retries: 5
82+
restart: unless-stopped
83+
84+
volumes:
85+
redis-data:

pnpm-lock.yaml

Lines changed: 74 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)