- Published on
Riot API 연동하기: 문제 해결과 팁
- Authors
- Name
- TtokTool
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
Riot API 연동 프로젝트 소개
리그 오브 레전드 전적 검색 서비스를 개발하면서 Riot API를 연동하는 과정에서 겪은 다양한 문제들과 해결 방법을 공유하고자 합니다. 이 프로젝트는 Next.js와 TypeScript를 사용하여 개발되었습니다.
주요 문제점과 해결 방법
1. API 키 인증 문제
문제:
API 키 에러: {
status: 403,
message: "Forbidden",
rateLimits: "..."
}
해결:
const headers = {
'User-Agent': 'Mozilla/5.0 ...',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Accept-Charset': 'application/x-www-form-urlencoded; charset=UTF-8',
Origin: 'https://developer.riotgames.com',
'X-Riot-Token': process.env.RIOT_API_KEY,
}
- API 키는 반드시 환경 변수로 관리
- 적절한 헤더 설정으로 인증 문제 해결
- 개발용/배포용 API 키 분리 관리
2. 한글 닉네임 처리
문제: 한글 닉네임 검색 시 404 에러 발생
해결:
const encodedName = encodeURIComponent(gameName.trim())
const encodedTag = encodeURIComponent(tagLine.trim())
const accountData = await riotApiCall<RiotAccount>(
`/riot/account/v1/accounts/by-riot-id/${encodedName}/${encodedTag}`,
'asia'
)
encodeURIComponent
로 한글 닉네임 인코딩- 닉네임과 태그라인 분리 처리
- 공백 제거(trim)로 사용자 입력 오류 방지
3. Rate Limit 관리
문제: API 호출 제한으로 인한 429 에러
해결:
// Redis를 활용한 캐싱
async function getCachedData(key: string) {
const cachedData = await redis.get(key)
if (cachedData) {
return JSON.parse(cachedData)
}
return null
}
async function setCachedData(key: string, data: any) {
await redis.set(key, JSON.stringify(data), 'EX', 300) // 5분 캐싱
}
- Redis를 활용한 데이터 캐싱
- API 호출 횟수 제한 모니터링
- 에러 발생 시 적절한 사용자 피드백 제공
4. 매치 데이터 처리
문제: 매치 데이터 조회 시 일부 데이터 누락
해결:
const matches = await Promise.all(
matchIds.map(async (matchId) => {
try {
return await riotApi.getMatchDetail(matchId)
} catch (error) {
console.error(`매치 ID ${matchId} 조회 실패:`, error)
return null
}
})
)
// null 값 필터링
const validMatches = matches.filter((match) => match !== null)
- 에러 발생 시 null 반환 및 필터링
- Promise.all을 사용한 병렬 처리
- 상세한 에러 로깅
5. 실시간 게임 정보 처리
문제: 게임 중이 아닌 유저 조회 시 404 에러
해결:
async getCurrentGame(puuid: string) {
try {
const gameData = await riotApiCall<CurrentGame>(
`/lol/spectator/v5/active-games/by-summoner/${puuid}`,
'kr'
);
return gameData;
} catch (error: any) {
if (error.response?.status === 404) {
return null; // 게임 중이 아님
}
throw error;
}
}
- 404 에러를 정상 케이스로 처리
- 명확한 사용자 피드백 제공
- 에러와 정상 케이스 구분
프로젝트 구조 개선
1. API 요청 중앙화
// services/riotApi.ts
class RiotApiService {
private async riotApiCall<T>(endpoint: string, region: string = 'kr'): Promise<T> {
try {
const baseUrl = region === 'asia' ? ASIA_API_URL : KR_API_URL
const url = `${baseUrl}${endpoint}`
console.log('API 요청:', {
endpoint,
region,
url: url,
})
const response = await axios<T>({
method: 'get',
url: url,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Accept-Charset': 'application/x-www-form-urlencoded; charset=UTF-8',
Origin: 'https://developer.riotgames.com',
'X-Riot-Token': process.env.RIOT_API_KEY,
},
})
return response.data
} catch (error: any) {
if (error.response?.status === 403) {
console.error('API 키 에러:', {
status: error.response.status,
message: error.response.data?.status?.message,
rateLimits: error.response.headers?.['x-app-rate-limit'],
rateLimitCount: error.response.headers?.['x-app-rate-limit-count'],
})
throw new Error('API 키가 만료되었거나 권한이 없습니다.')
}
throw error
}
}
async getSummoner(summonerName: string): Promise<Summoner> {
try {
// 캐시 확인
const cachedData = await cacheService.getCachedSummoner(summonerName)
if (cachedData) return cachedData
// 1. riot account API로 puuid 조회
const [gameName, tagLine = 'KR1'] = summonerName.split('#')
const encodedName = encodeURIComponent(gameName.trim())
const encodedTag = encodeURIComponent(tagLine.trim())
const accountData = await this.riotApiCall<RiotAccount>(
`/riot/account/v1/accounts/by-riot-id/${encodedName}/${encodedTag}`,
'asia'
)
// 2. puuid로 소환사 정보 조회
const summonerData = await this.riotApiCall<Summoner>(
`/lol/summoner/v4/summoners/by-puuid/${accountData.puuid}`,
'kr'
)
// 캐시 저장
await cacheService.setCachedSummoner(summonerName, summonerData)
return summonerData
} catch (error: any) {
console.error('소환사 검색 에러:', {
status: error.response?.status,
message: error.response?.data?.status?.message,
})
if (error.response?.status === 404) {
throw new Error('소환사를 찾을 수 없습니다.')
}
throw error
}
}
async getMatches(puuid: string, start = 0, count = 20) {
try {
// 매치 ID 목록 조회
const matchIds = await this.riotApiCall<string[]>(
`/lol/match/v5/matches/by-puuid/${puuid}/ids?start=${start}&count=${count}`,
'asia'
)
// 각 매치의 상세 정보 병렬 조회
const matches = await Promise.all(
matchIds.map(async (matchId) => {
try {
// 캐시 확인
const cachedMatch = await cacheService.getCachedMatch(matchId)
if (cachedMatch) return cachedMatch
// API 호출
const matchData = await this.riotApiCall<Match>(
`/lol/match/v5/matches/${matchId}`,
'asia'
)
// 캐시 저장
await cacheService.setCachedMatch(matchId, matchData)
return matchData
} catch (error) {
console.error(`매치 상세 정보 조회 실패 (${matchId}):`, error)
return null
}
})
)
// null 값 필터링
return matches.filter((match) => match !== null)
} catch (error) {
console.error('매치 목록 조회 실패:', error)
throw error
}
}
}
2. 에러 처리 통합
// types/error.ts
class RiotApiError extends Error {
constructor(
public status: number,
public message: string,
public endpoint: string
) {
super(message)
this.name = 'RiotApiError'
}
}
// 에러 처리 미들웨어
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof RiotApiError) {
res.status(err.status).json({
error: err.message,
endpoint: err.endpoint,
})
return
}
next(err)
})
결론
Riot API를 사용하면서 가장 중요한 것은:
- 적절한 에러 처리
- 캐싱 전략
- 사용자 피드백
- 코드 구조화
이러한 요소들을 고려하여 개발하면 안정적인 서비스를 제공할 수 있습니다.