Skip to content

Commit 1fe5984

Browse files
authored
Merge pull request #121 from okekefrancis112/perf/issue-61-lru-cache-prices
perf: implement LRU cache for /api/v1/prices endpoint
2 parents f43332a + a904c9b commit 1fe5984

File tree

7 files changed

+108
-14
lines changed

7 files changed

+108
-14
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"express-rate-limit": "^8.3.1",
4848
"ioredis": "^5.10.1",
4949
"jsonwebtoken": "^9.0.3",
50+
"lru-cache": "^11.2.7",
5051
"node-cron": "^3.0.3",
5152
"pg": "^8.11.0",
5253
"prisma": "^5.19.1",

src/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import { OgModule } from './og/og.module';
1414
import { RequireJwtMiddleware } from './common/middleware/require-jwt.middleware';
1515
import { ConfigModule } from './config/config.module';
1616
import { PoolsModule } from './pools/pools.module';
17+
import { PricesModule } from './prices/prices.module';
1718

1819
@Module({
19-
imports: [PrismaModule, HealthModule, RiskModule, AuthModule, AnalyticsModule, SwapModule, TokensModule, OgModule, ConfigModule, PoolsModule],
20+
imports: [PrismaModule, HealthModule, RiskModule, AuthModule, AnalyticsModule, SwapModule, TokensModule, OgModule, ConfigModule, PoolsModule, PricesModule],
2021
controllers: [AppController],
2122
providers: [
2223
AppService,

src/prices/prices.controller.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Controller, Get } from '@nestjs/common';
2+
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
3+
import { PricesService } from './prices.service';
4+
5+
@ApiTags('prices')
6+
@Controller('api/v1/prices')
7+
export class PricesController {
8+
constructor(private readonly pricesService: PricesService) {}
9+
10+
@Get()
11+
@ApiOperation({ summary: 'Get current token prices' })
12+
@ApiResponse({ status: 200, description: 'Token prices retrieved successfully' })
13+
async getPrices() {
14+
const { data, cached } = await this.pricesService.getPrices();
15+
return {
16+
success: true,
17+
message: 'Prices retrieved successfully',
18+
data,
19+
meta: { count: data.length, cached },
20+
};
21+
}
22+
}

src/prices/prices.module.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Module } from '@nestjs/common';
2+
import { PricesController } from './prices.controller';
3+
import { PricesService } from './prices.service';
4+
5+
@Module({
6+
controllers: [PricesController],
7+
providers: [PricesService],
8+
})
9+
export class PricesModule {}

src/prices/prices.service.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Injectable, Logger } from '@nestjs/common';
2+
import axios from 'axios';
3+
4+
const cache = require('../../../utils/cache');
5+
6+
const CACHE_KEY = 'prices:all';
7+
8+
const MOCK_PRICES = [
9+
{ symbol: 'USDC', price: 1.0 },
10+
{ symbol: 'XLM', price: 0.12 },
11+
];
12+
13+
@Injectable()
14+
export class PricesService {
15+
private readonly logger = new Logger(PricesService.name);
16+
17+
async getPrices(): Promise<{ data: { symbol: string; price: number }[]; cached: boolean }> {
18+
const cached = cache.get(CACHE_KEY);
19+
20+
if (cached) {
21+
this.logger.log('🎯 Cache HIT - Returning cached prices');
22+
return { data: cached, cached: true };
23+
}
24+
25+
this.logger.log('❌ Cache MISS - Fetching prices from external API');
26+
const prices = await this.fetchPrices();
27+
cache.set(CACHE_KEY, prices);
28+
29+
return { data: prices, cached: false };
30+
}
31+
32+
private async fetchPrices(): Promise<{ symbol: string; price: number }[]> {
33+
try {
34+
const response = await axios.get(
35+
'https://api.coingecko.com/api/v3/simple/price?ids=stellar,usd-coin&vs_currencies=usd',
36+
);
37+
38+
return [
39+
{ symbol: 'USDC', price: response.data['usd-coin']?.usd ?? 1.0 },
40+
{ symbol: 'XLM', price: response.data['stellar']?.usd ?? 0.12 },
41+
];
42+
} catch (error) {
43+
this.logger.error('Failed to fetch prices from CoinGecko, using fallback data', error.message);
44+
return MOCK_PRICES;
45+
}
46+
}
47+
}

utils/cache.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const { LRUCache } = require('lru-cache');
2+
3+
const cache = new LRUCache({
4+
max: 500,
5+
ttl: 60 * 1000, // 60 seconds in milliseconds
6+
});
7+
8+
module.exports = cache;

0 commit comments

Comments
 (0)