외부 API와의 통합
대부분의 현대 웹 애플리케이션은 독립적으로 작동하기보다 다양한 외부 API(Application Programming Interface) 와 연동하여 데이터를 가져오거나 기능을 활용합니다. 예를 들어, 날씨 정보를 가져오기 위해 기상청 API를 사용하거나, 결제를 처리하기 위해 PG(Payment Gateway)사의 API를 호출할 수 있습니다.
Next.js의 API 라우트는 이러한 외부 API와의 통합을 위한 이상적인 서버리스 환경을 제공합니다. 클라이언트(브라우저)에서 직접 외부 API를 호출하는 대신, Next.js API 라우트를 경유하여 호출하는 것이 권장되며, 이는 여러 가지 중요한 이점을 제공합니다.
이 절에서는 Next.js API 라우트를 사용하여 외부 API와 통합하는 방법, 그리고 이 방식이 제공하는 이점 및 고려 사항에 대해 상세히 알아보겠습니다.
왜 API 라우트를 통해 외부 API를 호출하는지?
클라이언트(브라우저)에서 JavaScript를 사용하여 직접 외부 API를 호출할 수도 있지만, Next.js API 라우트를 통해 호출하는 것이 훨씬 안전하고 효율적입니다.
- 보안 (API 키 숨김): 대부분의 외부 API는 API 키(API Key) 를 사용하여 인증합니다. 이 API 키는 민감한 정보이며, 절대 클라이언트 사이드 코드에 노출되어서는 안 됩니다. API 라우트에서 외부 API를 호출하면, API 키를 서버 환경 변수(
.env.local
)에 안전하게 저장하고 서버 측에서만 접근할 수 있어 보안 침해 위험을 줄일 수 있습니다. - CORS(Cross-Origin Resource Sharing) 문제 회피: 브라우저의 보안 정책(동일 출원 정책, Same-Origin Policy) 때문에 다른 도메인의 API를 직접 호출하는 데 제약이 있습니다. API 라우트는 서버 측에서 실행되므로 이러한 CORS 제약을 받지 않습니다. API 라우트가 외부 API로부터 데이터를 가져온 후, 클라이언트는 동일 출원(Same-Origin)인 Next.js API 라우트에만 요청을 보내면 됩니다.
- 데이터 가공 및 전처리: 외부 API에서 반환되는 데이터 형식이 클라이언트에서 원하는 형식과 다를 수 있습니다. API 라우트에서 데이터를 가져온 후 필요한 형태로 가공하거나 필터링하여 클라이언트에게 최적화된 데이터를 제공할 수 있습니다.
- 성능 최적화: 여러 외부 API를 호출하여 데이터를 조합해야 하는 경우, 클라이언트에서 각각 호출하는 것보다 API 라우트에서 한 번에 처리하여 클라이언트-서버 간의 네트워크 왕복(Round Trip) 횟수를 줄일 수 있습니다.
- 에러 처리 및 로깅: 외부 API 호출 시 발생할 수 있는 네트워크 오류나 API 에러를 API 라우트에서 중앙 집중적으로 처리하고 로깅할 수 있습니다.
- 속도 제한(Rate Limiting) 관리: 일부 외부 API는 호출 횟수에 제한을 둡니다. API 라우트에서 호출을 관리하면 이러한 속도 제한을 더 효율적으로 제어하고 재시도 로직 등을 구현할 수 있습니다.
외부 API 호출을 위한 기본 도구: fetch
API
Node.js 환경(Next.js API 라우트)에서는 웹 표준 fetch
API 또는 axios
와 같은 라이브러리를 사용하여 외부 API를 호출할 수 있습니다. 여기서는 기본 제공되는 fetch
API를 사용한 예시를 살펴보겠습니다.
fetch
API의 기본 사용법은 다음과 같습니다.
// fetch API 기본 사용법
async function fetchData(url: string) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json(); // JSON 응답 파싱
// const textData = await response.text(); // 텍스트 응답 파싱
return data;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
실습: 날씨 정보를 가져오는 API 라우트 생성
OpenWeatherMap API를 사용하여 특정 도시의 현재 날씨 정보를 가져오는 Next.js API 라우트를 만들어 보겠습니다.
사전 준비
-
OpenWeatherMap API 키 발급
- OpenWeatherMap 웹사이트(https://openweathermap.org/)에에) 가입합니다.
- 로그인 후 "API keys" 섹션에서 새로운 API 키를 생성합니다. (일반적으로
Current Weather Data
에 대한Free
플랜으로도 충분합니다.) - 발급받은 API 키를 복사해 둡니다.
-
환경 변수 설정: 프로젝트 루트에
.env.local
파일을 생성하고 발급받은 OpenWeatherMap API 키를 추가합니다.# .env.local OPENWEATHER_API_KEY=YOUR_OPENWEATHER_API_KEY_HERE
주의:
YOUR_OPENWEATHER_API_KEY_HERE
부분을 실제 발급받은 키로 대체하세요.
API 라우트 구현 (src/app/api/weather/route.ts
)
// src/app/api/weather/route.ts
import { NextRequest, NextResponse } from 'next/server';
const OPENWEATHER_API_KEY = process.env.OPENWEATHER_API_KEY;
const BASE_URL = 'https://api.openweathermap.org/data/2.5/weather';
export async function GET(request: NextRequest) {
// 1. 쿼리 파라미터에서 도시 이름 가져오기
const searchParams = request.nextUrl.searchParams;
const city = searchParams.get('city');
// 2. 필수 파라미터 검증
if (!city) {
return NextResponse.json(
{ message: '도시 이름(city) 쿼리 파라미터가 필요합니다.' },
{ status: 400 } // Bad Request
);
}
// 3. API 키 유효성 검사 (서버 환경 변수 확인)
if (!OPENWEATHER_API_KEY) {
console.error('OpenWeatherMap API Key is not set in environment variables.');
return NextResponse.json(
{ message: '서버 설정 오류: API 키가 누락되었습니다.' },
{ status: 500 } // Internal Server Error
);
}
try {
// 4. OpenWeatherMap 외부 API 호출 URL 생성 (단위: 섭씨, 언어: 한국어)
const apiUrl = `${BASE_URL}?q=${city}&appid=${OPENWEATHER_API_KEY}&units=metric&lang=kr`;
// 5. 외부 API 호출
const response = await fetch(apiUrl);
// 6. 외부 API 응답 처리
if (!response.ok) {
const errorData = await response.json();
console.error('OpenWeatherMap API Error:', errorData);
// 외부 API의 오류 메시지를 클라이언트에 전달 (또는 일반화된 메시지)
return NextResponse.json(
{ message: `날씨 정보를 가져오는 데 실패했습니다: ${errorData.message || response.statusText}` },
{ status: response.status }
);
}
const weatherData = await response.json();
// 7. 클라이언트에 필요한 형태로 데이터 가공 (선택 사항)
const processedData = {
city: weatherData.name,
country: weatherData.sys.country,
temperature: weatherData.main.temp,
feelsLike: weatherData.main.feels_like,
humidity: weatherData.main.humidity,
description: weatherData.weather[0].description,
icon: `http://openweathermap.org/img/wn/${weatherData.weather[0].icon}.png`,
};
// 8. 가공된 데이터 반환
return NextResponse.json(processedData, { status: 200 });
} catch (error) {
console.error('API 라우트에서 오류 발생:', error);
return NextResponse.json(
{ message: '서버에서 날씨 정보를 가져오는 중 오류가 발생했습니다.' },
{ status: 500 }
);
}
}
테스트 방법
- 개발 서버(
npm run dev
)를 실행합니다. - 웹 브라우저나 Postman/Insomnia에서 다음 URL로 접속하여 테스트합니다:
- 성공 케이스:
http://localhost:3000/api/weather?city=Seoul
- 실패 케이스 (도시 이름 누락):
http://localhost:3000/api/weather
- 실패 케이스 (유효하지 않은 도시 이름):
http://localhost:3000/api/weather?city=InvalidCityName123
- 성공 케이스:
클라이언트 컴포넌트에서 API 라우트 호출
이제 클라이언트 컴포넌트에서 위에서 만든 API 라우트를 호출하여 날씨 정보를 화면에 표시해 보겠습니다.
src/app/weather/page.tsx
(클라이언트 컴포넌트 예시)
// src/app/weather/page.tsx
"use client";
import { useState } from 'react';
interface WeatherData {
city: string;
country: string;
temperature: number;
feelsLike: number;
humidity: number;
description: string;
icon: string;
}
export default function WeatherPage() {
const [city, setCity] = useState('');
const [weather, setWeather] = useState<WeatherData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchWeather = async () => {
if (!city) {
setError('도시 이름을 입력해주세요.');
return;
}
setLoading(true);
setError(null);
setWeather(null); // 이전 날씨 정보 초기화
try {
// Next.js API 라우트를 호출합니다.
const response = await fetch(`/api/weather?city=${city}`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || '날씨 정보를 가져오는 데 실패했습니다.');
}
const data: WeatherData = await response.json();
setWeather(data);
} catch (err) {
setError((err as Error).message);
console.error('클라이언트에서 날씨 API 호출 중 오류:', err);
} finally {
setLoading(false);
}
};
return (
<div style={{ padding: '20px', maxWidth: '600px', margin: '20px auto', border: '1px solid #007bff', borderRadius: '10px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)', textAlign: 'center' }}>
<h1 style={{ color: '#007bff', marginBottom: '20px' }}>날씨 정보 가져오기</h1>
<div style={{ marginBottom: '20px' }}>
<input
type="text"
placeholder="도시 이름을 입력하세요 (예: Seoul)"
value={city}
onChange={(e) => setCity(e.target.value)}
style={{ padding: '10px', marginRight: '10px', border: '1px solid #ccc', borderRadius: '5px', width: '70%' }}
/>
<button
onClick={fetchWeather}
disabled={loading}
style={{ padding: '10px 15px', backgroundColor: '#28a745', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer', opacity: loading ? 0.7 : 1 }}
>
{loading ? '로딩 중...' : '날씨 가져오기'}
</button>
</div>
{error && (
<p style={{ color: '#dc3545', fontWeight: 'bold', marginBottom: '15px' }}>오류: {error}</p>
)}
{weather && (
<div style={{ border: '1px solid #eee', borderRadius: '8px', padding: '20px', backgroundColor: '#f9f9f9' }}>
<h2 style={{ color: '#333', marginBottom: '10px' }}>{weather.city}, {weather.country}</h2>
<img src={weather.icon} alt={weather.description} style={{ width: '80px', height: '80px' }} />
<p style={{ fontSize: '1.2em', margin: '10px 0' }}>온도: {weather.temperature}°C (체감: {weather.feelsLike}°C)</p>
<p>습도: {weather.humidity}%</p>
<p>날씨: {weather.description}</p>
</div>
)}
</div>
);
}
클라이언트에서 호출 시 유의사항
- 클라이언트에서는 항상 Next.js API 라우트의 **상대 경로(
/api/weather?city=Seoul
)**를 사용합니다. 이는 개발 환경(localhost)과 배포 환경에서 URL이 자동으로 맞춰지도록 합니다. - 에러 처리 및 로딩 상태 관리를 통해 사용자에게 더 나은 경험을 제공합니다.
고려 사항
- API 키 관리: 개발 환경(
.env.local
)에서는process.env.VAR_NAME
으로 접근하고, 배포 환경(Vercel, AWS 등)에서는 해당 플랫폼의 환경 변수 설정 기능을 사용하여 API 키를 안전하게 관리해야 합니다. - 보안: 외부 API 호출 시 민감한 데이터를 전송해야 한다면, HTTPS를 사용하는지, 추가적인 인증(OAuth 등)이 필요한지 확인해야 합니다.
- 속도 제한 및 캐싱: 자주 호출되는 외부 API의 경우, API 라우트 내에서 응답을 캐싱하여 외부 API 호출 횟수를 줄이고 성능을 향상시킬 수 있습니다.
- 오류 처리 및 재시도: 외부 API 호출이 실패할 경우를 대비하여 견고한 오류 처리 로직과 필요한 경우 재시도(Retry) 메커니즘을 구현해야 합니다.
- 환경별 설정: 개발, 스테이징, 프로덕션 환경마다 다른 외부 API 엔드포인트나 키를 사용해야 할 경우, 환경 변수를 통해 유연하게 관리해야 합니다.
Next.js API 라우트를 통한 외부 API 통합은 보안, 성능, 개발 효율성 측면에서 많은 이점을 제공합니다. 이를 통해 더욱 강력하고 확장 가능한 웹 애플리케이션을 구축할 수 있습니다.