무료 API 완벽 가이드 | 개인 프로젝트용 공공 API 100+ 총정리
이 글의 핵심
개인 프로젝트에 바로 사용할 수 있는 무료 공공 API 100개 이상. 날씨, 지도, 금융, AI, 이미지, 뉴스, 게임, 공공데이터까지. 인증 방법, 사용 예제, 제한사항 완벽 정리.
들어가며: “프로젝트에 실제 데이터를 넣고 싶어요”
토이 프로젝트의 고민
포트폴리오용 날씨 앱을 만들고 싶은데 실제 날씨 데이터가 없거나, 지도 서비스를 구현하고 싶은데 지도 API가 비싸거나, 주식 차트를 그리고 싶은데 금융 데이터를 구할 수 없어 고민한 적 있으신가요? 다행히도 수많은 기업과 정부 기관이 무료 공공 API를 제공하여 개인 개발자도 실전 같은 프로젝트를 만들 수 있습니다.
이 글에서 다루는 것:
- 100개 이상의 무료 API 카테고리별 정리
- 각 API의 특징, 제한사항, 사용 예제
- API 키 발급 및 인증 방법
- 실전 프로젝트 아이디어
목차
- 날씨 API
- 지도 및 위치 API
- 금융 및 암호화폐 API
- AI 및 머신러닝 API
- 이미지 및 미디어 API
- 뉴스 및 소셜 API
- 게임 및 엔터테인먼트 API
- 한국 공공데이터 API
- 기타 유용한 API
- API 사용 베스트 프랙티스
1. 날씨 API
OpenWeatherMap
특징: 현재 날씨, 5일 예보, 16일 예보, 과거 데이터
제한: 60 calls/min, 1,000,000 calls/month (무료)
인증: API Key
// 현재 날씨
const API_KEY = 'your_api_key';
const city = 'Seoul';
fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=kr`)
.then(res => res.json())
.then(data => {
console.log(`도시: ${data.name}`);
console.log(`온도: ${data.main.temp}°C`);
console.log(`날씨: ${data.weather[0].description}`);
console.log(`습도: ${data.main.humidity}%`);
});
// 5일 예보
fetch(`https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${API_KEY}&units=metric&lang=kr`)
.then(res => res.json())
.then(data => {
data.list.forEach(item => {
console.log(`${item.dt_txt}: ${item.main.temp}°C, ${item.weather[0].description}`);
});
});
회원가입: https://openweathermap.org/api
WeatherAPI
특징: 실시간 날씨, 14일 예보, 천문 데이터, 공기질
제한: 1,000,000 calls/month (무료)
인증: API Key
import requests
API_KEY = 'your_api_key'
city = 'Seoul'
# 현재 날씨
url = f'http://api.weatherapi.com/v1/current.json?key={API_KEY}&q={city}&lang=ko'
response = requests.get(url)
data = response.json()
print(f"위치: {data['location']['name']}")
print(f"온도: {data['current']['temp_c']}°C")
print(f"날씨: {data['current']['condition']['text']}")
# 7일 예보
url = f'http://api.weatherapi.com/v1/forecast.json?key={API_KEY}&q={city}&days=7&lang=ko'
response = requests.get(url)
data = response.json()
for day in data['forecast']['forecastday']:
print(f"{day['date']}: 최고 {day['day']['maxtemp_c']}°C, 최저 {day['day']['mintemp_c']}°C")
회원가입: https://www.weatherapi.com/
기상청 API (한국)
특징: 초단기예보, 단기예보, 중기예보, 특보
제한: 일 1,000건 (무료)
인증: 공공데이터포털 API Key
import requests
from datetime import datetime
API_KEY = 'your_api_key'
base_url = 'http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst'
# 초단기실황
params = {
'serviceKey': API_KEY,
'numOfRows': 10,
'pageNo': 1,
'dataType': 'JSON',
'base_date': datetime.now().strftime('%Y%m%d'),
'base_time': '0600',
'nx': 60, # 격자 X (서울)
'ny': 127 # 격자 Y
}
response = requests.get(base_url, params=params)
data = response.json()
for item in data['response']['body']['items']['item']:
category = item['category']
value = item['obsrValue']
print(f"{category}: {value}")
회원가입: https://www.data.go.kr/
2. 지도 및 위치 API
Google Maps API
특징: 지도, 경로, 장소 검색, Geocoding, Street View
제한: $200/month 무료 크레딧 (약 28,000 지도 로드)
인증: API Key
// Geocoding (주소 → 좌표)
const address = '서울특별시 강남구 테헤란로';
const API_KEY = 'your_api_key';
fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${API_KEY}`)
.then(res => res.json())
.then(data => {
const location = data.results[0].geometry.location;
console.log(`위도: ${location.lat}, 경도: ${location.lng}`);
});
// 장소 검색
fetch(`https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=37.5665,126.9780&radius=1000&type=restaurant&key=${API_KEY}`)
.then(res => res.json())
.then(data => {
data.results.forEach(place => {
console.log(`${place.name}: ${place.vicinity}`);
});
});
<!-- 지도 임베드 -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>
<div id="map" style="height: 400px;"></div>
<script>
function initMap() {
const seoul = { lat: 37.5665, lng: 126.9780 };
const map = new google.maps.Map(document.getElementById('map'), {
zoom: 13,
center: seoul
});
new google.maps.Marker({
position: seoul,
map: map,
title: '서울'
});
}
initMap();
</script>
회원가입: https://developers.google.com/maps
Kakao Map API (한국)
특징: 지도, 검색, 경로, 주소 변환 (한국 특화)
제한: 일 300,000건 (무료)
인증: REST API Key
// 주소 검색
const REST_API_KEY = 'your_api_key';
const address = '서울특별시 강남구 테헤란로 152';
fetch(`https://dapi.kakao.com/v2/local/search/address.json?query=${encodeURIComponent(address)}`, {
headers: {
'Authorization': `KakaoAK ${REST_API_KEY}`
}
})
.then(res => res.json())
.then(data => {
const result = data.documents[0];
console.log(`주소: ${result.address_name}`);
console.log(`좌표: ${result.x}, ${result.y}`);
});
// 키워드 검색
fetch(`https://dapi.kakao.com/v2/local/search/keyword.json?query=카페&x=126.9780&y=37.5665&radius=1000`, {
headers: {
'Authorization': `KakaoAK ${REST_API_KEY}`
}
})
.then(res => res.json())
.then(data => {
data.documents.forEach(place => {
console.log(`${place.place_name}: ${place.address_name}`);
});
});
회원가입: https://developers.kakao.com/
OpenStreetMap (Nominatim)
특징: 무료 오픈소스 지도, Geocoding
제한: 1 request/sec (무료)
인증: 불필요 (User-Agent 필수)
import requests
import time
def geocode(address):
"""주소 → 좌표"""
url = 'https://nominatim.openstreetmap.org/search'
params = {
'q': address,
'format': 'json',
'limit': 1
}
headers = {
'User-Agent': 'MyApp/1.0 ([email protected])' # 필수!
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
if data:
return {
'lat': float(data[0]['lat']),
'lon': float(data[0]['lon']),
'display_name': data[0]['display_name']
}
return None
result = geocode('Seoul, South Korea')
print(result)
time.sleep(1) # Rate limit 준수
문서: https://nominatim.org/release-docs/latest/api/Overview/
3. 금융 및 암호화폐 API
Alpha Vantage (주식, 환율)
특징: 실시간 주식, 환율, 암호화폐, 기술 지표
제한: 25 requests/day (무료)
인증: API Key
const API_KEY = 'your_api_key';
// 주식 시세 (실시간)
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=AAPL&apikey=${API_KEY}`)
.then(res => res.json())
.then(data => {
const quote = data['Global Quote'];
console.log(`Symbol: ${quote['01. symbol']}`);
console.log(`Price: $${quote['05. price']}`);
console.log(`Change: ${quote['09. change']} (${quote['10. change percent']})`);
});
// 일일 시계열 (최근 100일)
fetch(`https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=AAPL&apikey=${API_KEY}`)
.then(res => res.json())
.then(data => {
const timeSeries = data['Time Series (Daily)'];
Object.entries(timeSeries).slice(0, 5).forEach(([date, values]) => {
console.log(`${date}: Open $${values['1. open']}, Close $${values['4. close']}`);
});
});
// 환율
fetch(`https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=USD&to_currency=KRW&apikey=${API_KEY}`)
.then(res => res.json())
.then(data => {
const rate = data['Realtime Currency Exchange Rate'];
console.log(`1 USD = ${rate['5. Exchange Rate']} KRW`);
});
회원가입: https://www.alphavantage.co/support/#api-key
CoinGecko (암호화폐)
특징: 암호화폐 시세, 시가총액, 거래량, 차트
제한: 10-50 calls/min (무료)
인증: 불필요 (API Key 선택적)
import requests
# 암호화폐 시세
url = 'https://api.coingecko.com/api/v3/simple/price'
params = {
'ids': 'bitcoin,ethereum,ripple',
'vs_currencies': 'usd,krw',
'include_24hr_change': 'true'
}
response = requests.get(url, params=params)
data = response.json()
for coin, prices in data.items():
print(f"{coin.upper()}:")
print(f" USD: ${prices['usd']:,.2f} ({prices['usd_24h_change']:+.2f}%)")
print(f" KRW: ₩{prices['krw']:,.0f} ({prices['krw_24h_change']:+.2f}%)")
# 시가총액 상위 10개
url = 'https://api.coingecko.com/api/v3/coins/markets'
params = {
'vs_currency': 'usd',
'order': 'market_cap_desc',
'per_page': 10,
'page': 1
}
response = requests.get(url, params=params)
coins = response.json()
for i, coin in enumerate(coins, 1):
print(f"{i}. {coin['name']} ({coin['symbol'].upper()}): ${coin['current_price']:,.2f}")
문서: https://www.coingecko.com/en/api
ExchangeRate-API (환율)
특징: 실시간 환율, 161개 통화
제한: 1,500 requests/month (무료)
인증: API Key
const API_KEY = 'your_api_key';
// 환율 조회
fetch(`https://v6.exchangerate-api.com/v6/${API_KEY}/latest/USD`)
.then(res => res.json())
.then(data => {
console.log(`1 USD = ${data.conversion_rates.KRW} KRW`);
console.log(`1 USD = ${data.conversion_rates.JPY} JPY`);
console.log(`1 USD = ${data.conversion_rates.EUR} EUR`);
});
// 환전 계산
const amount = 100;
fetch(`https://v6.exchangerate-api.com/v6/${API_KEY}/pair/USD/KRW/${amount}`)
.then(res => res.json())
.then(data => {
console.log(`$${amount} = ₩${data.conversion_result}`);
});
회원가입: https://www.exchangerate-api.com/
4. AI 및 머신러닝 API
OpenAI API
특징: GPT-4, GPT-3.5, DALL-E, Whisper
제한: $5 무료 크레딧 (신규 가입)
인증: API Key
import openai
openai.api_key = 'your_api_key'
# ChatGPT
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Python으로 피보나치 수열 구현해줘"}
]
)
print(response.choices[0].message.content)
# 이미지 생성 (DALL-E)
response = openai.Image.create(
prompt="A futuristic city with flying cars",
n=1,
size="1024x1024"
)
image_url = response.data[0].url
print(f"Image URL: {image_url}")
회원가입: https://platform.openai.com/
Hugging Face API
특징: 수천 개의 AI 모델 (텍스트, 이미지, 음성)
제한: 30,000 requests/month (무료)
인증: API Token
import requests
API_TOKEN = 'your_api_token'
headers = {"Authorization": f"Bearer {API_TOKEN}"}
# 감정 분석
API_URL = "https://api-inference.huggingface.co/models/distilbert-base-uncased-finetuned-sst-2-english"
data = {"inputs": "I love this product!"}
response = requests.post(API_URL, headers=headers, json=data)
result = response.json()
print(result)
# [{'label': 'POSITIVE', 'score': 0.9998}]
# 텍스트 생성
API_URL = "https://api-inference.huggingface.co/models/gpt2"
data = {"inputs": "Once upon a time"}
response = requests.post(API_URL, headers=headers, json=data)
print(response.json()[0]['generated_text'])
# 이미지 분류
API_URL = "https://api-inference.huggingface.co/models/google/vit-base-patch16-224"
with open('image.jpg', 'rb') as f:
data = f.read()
response = requests.post(API_URL, headers=headers, data=data)
print(response.json())
회원가입: https://huggingface.co/
Google Gemini API
특징: Gemini Pro, 멀티모달 (텍스트, 이미지)
제한: 60 requests/min (무료)
인증: API Key
import google.generativeai as genai
genai.configure(api_key='your_api_key')
model = genai.GenerativeModel('gemini-pro')
# 텍스트 생성
response = model.generate_content('Python으로 웹 크롤러 만드는 방법 설명해줘')
print(response.text)
# 멀티모달 (이미지 + 텍스트)
model = genai.GenerativeModel('gemini-pro-vision')
image = PIL.Image.open('photo.jpg')
response = model.generate_content(['이 이미지에 뭐가 있나요?', image])
print(response.text)
회원가입: https://makersuite.google.com/app/apikey
5. 이미지 및 미디어 API
Unsplash API
특징: 고품질 무료 이미지 (200만+ 장)
제한: 50 requests/hour (무료)
인증: Access Key
const ACCESS_KEY = 'your_access_key';
// 랜덤 이미지
fetch(`https://api.unsplash.com/photos/random?client_id=${ACCESS_KEY}`)
.then(res => res.json())
.then(data => {
console.log(`Image URL: ${data.urls.regular}`);
console.log(`Author: ${data.user.name}`);
});
// 검색
fetch(`https://api.unsplash.com/search/photos?query=nature&client_id=${ACCESS_KEY}`)
.then(res => res.json())
.then(data => {
data.results.forEach(photo => {
console.log(`${photo.alt_description}: ${photo.urls.small}`);
});
});
회원가입: https://unsplash.com/developers
Pexels API
특징: 무료 이미지 및 비디오
제한: 200 requests/hour (무료)
인증: API Key
import requests
API_KEY = 'your_api_key'
headers = {'Authorization': API_KEY}
# 이미지 검색
url = 'https://api.pexels.com/v1/search'
params = {'query': 'nature', 'per_page': 10}
response = requests.get(url, headers=headers, params=params)
data = response.json()
for photo in data['photos']:
print(f"Photo: {photo['src']['medium']}")
print(f"Photographer: {photo['photographer']}")
# 비디오 검색
url = 'https://api.pexels.com/videos/search'
params = {'query': 'ocean', 'per_page': 5}
response = requests.get(url, headers=headers, params=params)
data = response.json()
for video in data['videos']:
print(f"Video: {video['video_files'][0]['link']}")
회원가입: https://www.pexels.com/api/
Lorem Picsum (플레이스홀더 이미지)
특징: 랜덤 이미지 (테스트용)
제한: 무제한
인증: 불필요
<!-- 특정 크기 -->
<img src="https://picsum.photos/800/600" alt="Random">
<!-- 특정 이미지 ID -->
<img src="https://picsum.photos/id/237/800/600" alt="Dog">
<!-- 그레이스케일 -->
<img src="https://picsum.photos/800/600?grayscale" alt="Grayscale">
<!-- 블러 -->
<img src="https://picsum.photos/800/600?blur=5" alt="Blurred">
6. 뉴스 및 소셜 API
NewsAPI
특징: 전 세계 뉴스, 80,000+ 소스
제한: 100 requests/day (무료)
인증: API Key
const API_KEY = 'your_api_key';
// 헤드라인
fetch(`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${API_KEY}`)
.then(res => res.json())
.then(data => {
data.articles.forEach(article => {
console.log(`${article.title}`);
console.log(`Source: ${article.source.name}`);
console.log(`URL: ${article.url}\n`);
});
});
// 키워드 검색
fetch(`https://newsapi.org/v2/everything?q=artificial intelligence&sortBy=publishedAt&apiKey=${API_KEY}`)
.then(res => res.json())
.then(data => {
console.log(`Total results: ${data.totalResults}`);
data.articles.slice(0, 5).forEach(article => {
console.log(`${article.title} (${article.publishedAt})`);
});
});
회원가입: https://newsapi.org/
Reddit API
특징: Reddit 게시글, 댓글, 서브레딧
제한: 60 requests/min (무료)
인증: OAuth2 (또는 불필요)
import requests
# 인증 없이 공개 데이터 조회
url = 'https://www.reddit.com/r/programming/hot.json'
headers = {'User-Agent': 'MyApp/1.0'}
response = requests.get(url, headers=headers)
data = response.json()
for post in data['data']['children'][:10]:
p = post['data']
print(f"Title: {p['title']}")
print(f"Score: {p['score']} | Comments: {p['num_comments']}")
print(f"URL: https://reddit.com{p['permalink']}\n")
# 검색
url = 'https://www.reddit.com/search.json'
params = {'q': 'python tutorial', 'limit': 10}
response = requests.get(url, headers=headers, params=params)
data = response.json()
문서: https://www.reddit.com/dev/api/
7. 게임 및 엔터테인먼트 API
PokeAPI (포켓몬)
특징: 포켓몬 데이터베이스 (1,000+ 포켓몬)
제한: 무제한 (캐싱 권장)
인증: 불필요
// 포켓몬 정보
fetch('https://pokeapi.co/api/v2/pokemon/pikachu')
.then(res => res.json())
.then(data => {
console.log(`Name: ${data.name}`);
console.log(`Height: ${data.height}`);
console.log(`Weight: ${data.weight}`);
console.log(`Types: ${data.types.map(t => t.type.name).join(', ')}`);
console.log(`Abilities: ${data.abilities.map(a => a.ability.name).join(', ')}`);
console.log(`Sprite: ${data.sprites.front_default}`);
});
// 포켓몬 목록
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(res => res.json())
.then(data => {
data.results.forEach((pokemon, i) => {
console.log(`${i+1}. ${pokemon.name}`);
});
});
RAWG (비디오 게임 데이터베이스)
특징: 800,000+ 게임 정보, 리뷰, 스크린샷
제한: 20,000 requests/month (무료)
인증: API Key
import requests
API_KEY = 'your_api_key'
# 게임 검색
url = 'https://api.rawg.io/api/games'
params = {
'key': API_KEY,
'search': 'witcher',
'page_size': 5
}
response = requests.get(url, params=params)
data = response.json()
for game in data['results']:
print(f"Title: {game['name']}")
print(f"Rating: {game['rating']}/5")
print(f"Released: {game['released']}")
print(f"Platforms: {', '.join([p['platform']['name'] for p in game['platforms']])}\n")
# 게임 상세 정보
game_id = 3328 # The Witcher 3
url = f'https://api.rawg.io/api/games/{game_id}'
params = {'key': API_KEY}
response = requests.get(url, params=params)
game = response.json()
print(f"Description: {game['description_raw'][:200]}...")
회원가입: https://rawg.io/apidocs
The Movie Database (TMDB)
특징: 영화, TV 프로그램, 배우 정보
제한: 무제한 (Rate limit 있음)
인증: API Key
const API_KEY = 'your_api_key';
// 인기 영화
fetch(`https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}&language=ko-KR`)
.then(res => res.json())
.then(data => {
data.results.forEach(movie => {
console.log(`${movie.title} (${movie.release_date})`);
console.log(`Rating: ${movie.vote_average}/10`);
console.log(`Poster: https://image.tmdb.org/t/p/w500${movie.poster_path}\n`);
});
});
// 영화 검색
fetch(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=Inception&language=ko-KR`)
.then(res => res.json())
.then(data => {
const movie = data.results[0];
console.log(`Title: ${movie.title}`);
console.log(`Overview: ${movie.overview}`);
});
회원가입: https://www.themoviedb.org/settings/api
8. 한국 공공데이터 API
공공데이터포털
제공 API:
- 국토교통부: 아파트 실거래가, 건축물 대장
- 환경부: 대기오염 정보, 미세먼지
- 행정안전부: 주소 검색, 우편번호
- 문화체육관광부: 관광 정보, 축제
- 보건복지부: 병원 정보, 약국 위치
import requests
API_KEY = 'your_api_key'
# 아파트 실거래가
url = 'http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev'
params = {
'serviceKey': API_KEY,
'LAWD_CD': '11110', # 지역코드 (서울 종로구)
'DEAL_YMD': '202604', # 거래년월
'numOfRows': 10
}
response = requests.get(url, params=params)
# XML 파싱 필요
# 미세먼지 정보
url = 'http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty'
params = {
'serviceKey': API_KEY,
'returnType': 'json',
'numOfRows': 1,
'stationName': '종로구',
'dataTerm': 'DAILY'
}
response = requests.get(url, params=params)
data = response.json()
item = data['response']['body']['items'][0]
print(f"측정소: {item['stationName']}")
print(f"미세먼지(PM10): {item['pm10Value']}㎍/㎥")
print(f"초미세먼지(PM2.5): {item['pm25Value']}㎍/㎥")
print(f"통합대기환경지수: {item['khaiValue']} ({item['khaiGrade']})")
회원가입: https://www.data.go.kr/
서울 열린데이터광장
제공 API:
- 실시간 지하철 도착 정보
- 공공자전거 대여소 정보
- 문화행사 정보
- CCTV 교통 정보
import requests
API_KEY = 'your_api_key'
# 지하철 실시간 도착 정보
url = f'http://swopenapi.seoul.go.kr/api/subway/{API_KEY}/json/realtimeStationArrival/0/5/서울'
response = requests.get(url)
data = response.json()
for train in data['realtimeArrivalList']:
print(f"[{train['subwayId']}호선] {train['trainLineNm']}")
print(f"도착: {train['arvlMsg2']}")
print(f"방향: {train['bstatnNm']} → {train['statnNm']} → {train['endStnNm']}\n")
# 따릉이 대여소 정보
url = f'http://openapi.seoul.go.kr:8088/{API_KEY}/json/bikeList/1/10/'
response = requests.get(url)
data = response.json()
for station in data['rentBikeStatus']['row']:
print(f"{station['stationName']}: {station['parkingBikeTotCnt']}대 대여 가능")
회원가입: https://data.seoul.go.kr/
9. 기타 유용한 API
REST Countries
특징: 국가 정보 (250개국)
제한: 무제한
인증: 불필요
// 모든 국가
fetch('https://restcountries.com/v3.1/all')
.then(res => res.json())
.then(countries => {
countries.forEach(country => {
console.log(`${country.name.common}: ${country.capital?.[0]}`);
});
});
// 특정 국가
fetch('https://restcountries.com/v3.1/name/korea')
.then(res => res.json())
.then(data => {
const country = data[0];
console.log(`Name: ${country.name.common}`);
console.log(`Capital: ${country.capital[0]}`);
console.log(`Population: ${country.population.toLocaleString()}`);
console.log(`Languages: ${Object.values(country.languages).join(', ')}`);
console.log(`Flag: ${country.flag}`);
});
문서: https://restcountries.com/
JSONPlaceholder (테스트용 가짜 API)
특징: REST API 테스트용 더미 데이터
제한: 무제한
인증: 불필요
// GET
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(posts => {
posts.slice(0, 5).forEach(post => {
console.log(`${post.id}. ${post.title}`);
});
});
// POST
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'New Post',
body: 'This is a new post',
userId: 1
})
})
.then(res => res.json())
.then(data => console.log('Created:', data));
// PUT
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: 1,
title: 'Updated Post',
body: 'Updated content',
userId: 1
})
})
.then(res => res.json())
.then(data => console.log('Updated:', data));
// DELETE
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'DELETE'
})
.then(() => console.log('Deleted'));
문서: https://jsonplaceholder.typicode.com/
IP Geolocation
특징: IP 주소 위치 정보
제한: 1,000 requests/day (무료)
인증: 불필요
import requests
# 현재 IP 위치
response = requests.get('https://ipapi.co/json/')
data = response.json()
print(f"IP: {data['ip']}")
print(f"City: {data['city']}")
print(f"Region: {data['region']}")
print(f"Country: {data['country_name']}")
print(f"Latitude: {data['latitude']}")
print(f"Longitude: {data['longitude']}")
# 특정 IP 조회
ip = '8.8.8.8'
response = requests.get(f'https://ipapi.co/{ip}/json/')
data = response.json()
print(data)
QR Code Generator
특징: QR 코드 생성
제한: 무제한
인증: 불필요
<!-- URL로 QR 코드 생성 -->
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://example.com" alt="QR Code">
<!-- 텍스트 -->
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=Hello%20World" alt="QR">
<!-- 색상 지정 -->
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://example.com&color=0000ff&bgcolor=ffffff" alt="Blue QR">
카테고리별 API 목록
날씨 및 환경
| API | 특징 | 제한 | 인증 |
|---|---|---|---|
| OpenWeatherMap | 날씨, 예보 | 60/min | API Key |
| WeatherAPI | 날씨, 공기질 | 1M/month | API Key |
| 기상청 | 한국 날씨 | 1,000/day | API Key |
| AirVisual | 공기질 | 10,000/month | API Key |
지도 및 위치
| API | 특징 | 제한 | 인증 |
|---|---|---|---|
| Google Maps | 지도, 경로 | $200 credit | API Key |
| Kakao Map | 한국 지도 | 300,000/day | API Key |
| Naver Map | 한국 지도 | 제한 있음 | Client ID |
| OpenStreetMap | 오픈소스 지도 | 1/sec | 불필요 |
금융 및 경제
| API | 특징 | 제한 | 인증 |
|---|---|---|---|
| Alpha Vantage | 주식, 환율 | 25/day | API Key |
| CoinGecko | 암호화폐 | 10-50/min | 불필요 |
| ExchangeRate-API | 환율 | 1,500/month | API Key |
| 한국은행 | 경제 통계 | 제한 있음 | API Key |
AI 및 머신러닝
| API | 특징 | 제한 | 인증 |
|---|---|---|---|
| OpenAI | GPT, DALL-E | $5 credit | API Key |
| Hugging Face | AI 모델 | 30,000/month | Token |
| Google Gemini | Gemini Pro | 60/min | API Key |
| Stability AI | Stable Diffusion | 제한 있음 | API Key |
이미지 및 미디어
| API | 특징 | 제한 | 인증 |
|---|---|---|---|
| Unsplash | 고품질 이미지 | 50/hour | Access Key |
| Pexels | 이미지, 비디오 | 200/hour | API Key |
| Pixabay | 무료 이미지 | 100/min | API Key |
| Lorem Picsum | 플레이스홀더 | 무제한 | 불필요 |
뉴스 및 소셜
| API | 특징 | 제한 | 인증 |
|---|---|---|---|
| NewsAPI | 전 세계 뉴스 | 100/day | API Key |
| Reddit 데이터 | 60/min | OAuth2 | |
| 트윗, 타임라인 | 제한 있음 | OAuth | |
| YouTube | 비디오 검색 | 10,000 units/day | API Key |
게임 및 엔터테인먼트
| API | 특징 | 제한 | 인증 |
|---|---|---|---|
| PokeAPI | 포켓몬 | 무제한 | 불필요 |
| RAWG | 게임 DB | 20,000/month | API Key |
| TMDB | 영화, TV | 무제한 | API Key |
| Spotify | 음악 메타데이터 | 제한 있음 | OAuth |
개발 도구
| API | 특징 | 제한 | 인증 |
|---|---|---|---|
| GitHub | 저장소, 이슈 | 5,000/hour | Token |
| JSONPlaceholder | 테스트 API | 무제한 | 불필요 |
| Random User | 가짜 사용자 | 무제한 | 불필요 |
| Lorem Ipsum | 더미 텍스트 | 무제한 | 불필요 |
10. API 사용 베스트 프랙티스
1. API 키 보안
# .env 파일
OPENWEATHER_API_KEY=abc123def456
GOOGLE_MAPS_API_KEY=xyz789uvw012
NEWS_API_KEY=qwe456rty789
# .gitignore에 추가
.env
.env.local
.env.production
// Node.js (dotenv)
require('dotenv').config();
const API_KEY = process.env.OPENWEATHER_API_KEY;
fetch(`https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid=${API_KEY}`)
.then(res => res.json())
.then(data => console.log(data));
# Python (python-dotenv)
from dotenv import load_dotenv
import os
load_dotenv()
API_KEY = os.getenv('OPENWEATHER_API_KEY')
2. Rate Limiting 처리
import time
import requests
from functools import wraps
def rate_limit(max_per_second):
"""Rate limiting 데코레이터"""
min_interval = 1.0 / max_per_second
last_called = [0.0]
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
left_to_wait = min_interval - elapsed
if left_to_wait > 0:
time.sleep(left_to_wait)
result = func(*args, **kwargs)
last_called[0] = time.time()
return result
return wrapper
return decorator
@rate_limit(1) # 초당 1회
def fetch_weather(city):
url = f'https://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}'
return requests.get(url).json()
# 사용
cities = ['Seoul', 'Busan', 'Incheon', 'Daegu', 'Daejeon']
for city in cities:
data = fetch_weather(city)
print(f"{city}: {data['main']['temp']}°C")
# 자동으로 1초 대기
3. 에러 처리
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
if (response.status === 429) {
// Rate limit 초과
const retryAfter = response.headers.get('Retry-After') || 60;
console.log(`⏳ Rate limited. Waiting ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (response.status >= 500) {
// 서버 오류 (재시도)
console.log(`❌ Server error (${response.status}). Retry ${i+1}/${maxRetries}...`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
continue;
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log(`❌ Error: ${error.message}. Retry ${i+1}/${maxRetries}...`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
// 사용
try {
const data = await fetchWithRetry('https://api.example.com/data');
console.log(data);
} catch (error) {
console.error('Failed after retries:', error);
}
4. 캐싱
import requests
import time
from functools import lru_cache
import hashlib
import json
import os
class APICache:
def __init__(self, cache_dir='.cache', ttl=3600):
self.cache_dir = cache_dir
self.ttl = ttl
os.makedirs(cache_dir, exist_ok=True)
def get_cache_path(self, url):
"""URL을 캐시 파일 경로로 변환"""
url_hash = hashlib.md5(url.encode()).hexdigest()
return os.path.join(self.cache_dir, f"{url_hash}.json")
def get(self, url):
"""캐시에서 데이터 조회"""
cache_path = self.get_cache_path(url)
if os.path.exists(cache_path):
mtime = os.path.getmtime(cache_path)
if time.time() - mtime < self.ttl:
with open(cache_path, 'r') as f:
return json.load(f)
return None
def set(self, url, data):
"""캐시에 데이터 저장"""
cache_path = self.get_cache_path(url)
with open(cache_path, 'w') as f:
json.dump(data, f)
def fetch_with_cache(url, cache_ttl=3600):
"""캐싱된 API 요청"""
cache = APICache(ttl=cache_ttl)
# 캐시 확인
cached = cache.get(url)
if cached:
print(f"✅ Cache hit: {url}")
return cached
# API 요청
print(f"📡 Fetching: {url}")
response = requests.get(url)
data = response.json()
# 캐시 저장
cache.set(url, data)
return data
# 사용
url = f'https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid={API_KEY}'
data = fetch_with_cache(url, cache_ttl=1800) # 30분 캐싱
print(data)
5. 백엔드 프록시 (API 키 숨기기)
// server.js (Express)
const express = require('express');
const axios = require('axios');
require('dotenv').config();
const app = express();
// CORS 설정
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
// 날씨 API 프록시
app.get('/api/weather/:city', async (req, res) => {
try {
const { city } = req.params;
const API_KEY = process.env.OPENWEATHER_API_KEY;
const response = await axios.get(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`
);
res.json(response.data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 뉴스 API 프록시
app.get('/api/news', async (req, res) => {
try {
const API_KEY = process.env.NEWS_API_KEY;
const response = await axios.get(
`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${API_KEY}`
);
res.json(response.data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('✅ Proxy server running on port 3000');
});
// 프론트엔드에서 프록시 사용
fetch('/api/weather/Seoul')
.then(res => res.json())
.then(data => console.log(data));
// API 키가 노출되지 않음!
실전 프로젝트 아이디어
프로젝트 1: 날씨 대시보드
// weather-dashboard.js
const API_KEY = process.env.OPENWEATHER_API_KEY;
async function createWeatherDashboard() {
const cities = ['Seoul', 'Busan', 'Incheon', 'Daegu', 'Daejeon'];
const weatherData = await Promise.all(
cities.map(city =>
fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=kr`)
.then(res => res.json())
)
);
weatherData.forEach(data => {
console.log(`\n${data.name}`);
console.log(` 온도: ${data.main.temp}°C (체감: ${data.main.feels_like}°C)`);
console.log(` 날씨: ${data.weather[0].description}`);
console.log(` 습도: ${data.main.humidity}%`);
console.log(` 풍속: ${data.wind.speed}m/s`);
});
}
createWeatherDashboard();
프로젝트 2: 암호화폐 트래커
import requests
import time
from datetime import datetime
def crypto_tracker():
"""실시간 암호화폐 가격 추적"""
coins = ['bitcoin', 'ethereum', 'ripple', 'cardano', 'solana']
while True:
url = 'https://api.coingecko.com/api/v3/simple/price'
params = {
'ids': ','.join(coins),
'vs_currencies': 'usd,krw',
'include_24hr_change': 'true',
'include_market_cap': 'true'
}
response = requests.get(url, params=params)
data = response.json()
print(f"\n{'='*60}")
print(f"Crypto Tracker - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'='*60}\n")
for coin, prices in data.items():
change = prices['usd_24h_change']
emoji = '📈' if change > 0 else '📉'
print(f"{emoji} {coin.upper()}")
print(f" USD: ${prices['usd']:,.2f} ({change:+.2f}%)")
print(f" KRW: ₩{prices['krw']:,.0f}")
print(f" Cap: ${prices['usd_market_cap']:,.0f}\n")
time.sleep(60) # 1분마다 업데이트
crypto_tracker()
프로젝트 3: 뉴스 애그리게이터
// news-aggregator.js
const NEWS_API_KEY = process.env.NEWS_API_KEY;
async function fetchNews(category = 'technology') {
const url = `https://newsapi.org/v2/top-headlines?country=kr&category=${category}&apiKey=${NEWS_API_KEY}`;
const response = await fetch(url);
const data = await response.json();
return data.articles.map(article => ({
title: article.title,
description: article.description,
url: article.url,
source: article.source.name,
publishedAt: new Date(article.publishedAt),
image: article.urlToImage
}));
}
async function createNewsFeed() {
const categories = ['technology', 'business', 'science'];
for (const category of categories) {
console.log(`\n📰 ${category.toUpperCase()}\n`);
const articles = await fetchNews(category);
articles.slice(0, 5).forEach((article, i) => {
console.log(`${i+1}. ${article.title}`);
console.log(` Source: ${article.source}`);
console.log(` URL: ${article.url}\n`);
});
}
}
createNewsFeed();
프로젝트 4: 영화 추천 시스템
import requests
TMDB_API_KEY = 'your_api_key'
def get_popular_movies(page=1):
"""인기 영화 조회"""
url = f'https://api.themoviedb.org/3/movie/popular'
params = {
'api_key': TMDB_API_KEY,
'language': 'ko-KR',
'page': page
}
response = requests.get(url, params=params)
return response.json()['results']
def get_movie_recommendations(movie_id):
"""영화 추천"""
url = f'https://api.themoviedb.org/3/movie/{movie_id}/recommendations'
params = {
'api_key': TMDB_API_KEY,
'language': 'ko-KR'
}
response = requests.get(url, params=params)
return response.json()['results']
def search_movies(query):
"""영화 검색"""
url = f'https://api.themoviedb.org/3/search/movie'
params = {
'api_key': TMDB_API_KEY,
'language': 'ko-KR',
'query': query
}
response = requests.get(url, params=params)
return response.json()['results']
# 사용
print("🎬 인기 영화 Top 10")
movies = get_popular_movies()
for i, movie in enumerate(movies[:10], 1):
print(f"{i}. {movie['title']} ({movie['release_date'][:4]})")
print(f" 평점: {movie['vote_average']}/10")
print("\n🔍 '인셉션' 검색")
results = search_movies('Inception')
if results:
movie = results[0]
print(f"제목: {movie['title']}")
print(f"개봉: {movie['release_date']}")
print(f"줄거리: {movie['overview']}")
print("\n💡 추천 영화")
recommendations = get_movie_recommendations(movie['id'])
for rec in recommendations[:5]:
print(f" - {rec['title']} ({rec['vote_average']}/10)")
API 통합 예제
멀티 API 대시보드
#!/usr/bin/env python3
"""
멀티 API 통합 대시보드
"""
import requests
import os
from datetime import datetime
class DashboardAPI:
def __init__(self):
self.weather_key = os.getenv('OPENWEATHER_API_KEY')
self.news_key = os.getenv('NEWS_API_KEY')
self.crypto_enabled = True
def get_weather(self, city='Seoul'):
"""날씨 정보"""
url = f'https://api.openweathermap.org/data/2.5/weather'
params = {
'q': city,
'appid': self.weather_key,
'units': 'metric',
'lang': 'kr'
}
response = requests.get(url, params=params)
data = response.json()
return {
'city': data['name'],
'temp': data['main']['temp'],
'description': data['weather'][0]['description'],
'humidity': data['main']['humidity']
}
def get_crypto_prices(self):
"""암호화폐 시세"""
url = 'https://api.coingecko.com/api/v3/simple/price'
params = {
'ids': 'bitcoin,ethereum',
'vs_currencies': 'usd,krw',
'include_24hr_change': 'true'
}
response = requests.get(url, params=params)
return response.json()
def get_news(self, category='technology'):
"""뉴스 헤드라인"""
url = 'https://newsapi.org/v2/top-headlines'
params = {
'country': 'kr',
'category': category,
'apiKey': self.news_key,
'pageSize': 5
}
response = requests.get(url, params=params)
data = response.json()
return [
{
'title': article['title'],
'source': article['source']['name'],
'url': article['url']
}
for article in data['articles']
]
def display_dashboard(self):
"""대시보드 출력"""
print(f"\n{'='*70}")
print(f"📊 Dashboard - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'='*70}\n")
# 날씨
print("🌤️ 날씨")
weather = self.get_weather()
print(f" {weather['city']}: {weather['temp']}°C, {weather['description']}")
print(f" 습도: {weather['humidity']}%\n")
# 암호화폐
print("💰 암호화폐")
crypto = self.get_crypto_prices()
for coin, prices in crypto.items():
change = prices['usd_24h_change']
emoji = '📈' if change > 0 else '📉'
print(f" {emoji} {coin.upper()}: ${prices['usd']:,.2f} ({change:+.2f}%)")
print()
# 뉴스
print("📰 기술 뉴스")
news = self.get_news('technology')
for i, article in enumerate(news, 1):
print(f" {i}. {article['title']}")
print(f" {article['source']}\n")
# 실행
dashboard = DashboardAPI()
dashboard.display_dashboard()
API 키 관리 도구
환경 변수 관리
# .env.example (템플릿)
OPENWEATHER_API_KEY=your_key_here
GOOGLE_MAPS_API_KEY=your_key_here
NEWS_API_KEY=your_key_here
TMDB_API_KEY=your_key_here
# .env (실제 키, .gitignore에 추가)
OPENWEATHER_API_KEY=abc123def456
GOOGLE_MAPS_API_KEY=xyz789uvw012
NEWS_API_KEY=qwe456rty789
TMDB_API_KEY=asd789fgh012
Vercel/Netlify 환경 변수
# Vercel
vercel env add OPENWEATHER_API_KEY
# 값 입력: abc123def456
# Netlify
netlify env:set OPENWEATHER_API_KEY abc123def456
# GitHub Actions
# Settings → Secrets → New repository secret
# Name: OPENWEATHER_API_KEY
# Value: abc123def456
추가 무료 API 목록
유틸리티
📧 이메일 검증: https://hunter.io/ (50/month)
📱 SMS 전송: https://www.twilio.com/ ($15 credit)
🔗 URL 단축: https://tinyurl.com/app/dev (무제한)
📊 QR 코드: https://goqr.me/api/ (무제한)
🌐 번역: https://libretranslate.com/ (무제한)
데이터베이스
🗄️ Firebase: https://firebase.google.com/ (Spark 플랜)
🐘 Supabase: https://supabase.com/ (500MB)
🍃 MongoDB Atlas: https://www.mongodb.com/cloud/atlas (512MB)
🔥 PlanetScale: https://planetscale.com/ (5GB)
인증
🔐 Auth0: https://auth0.com/ (7,000 users)
🎫 Clerk: https://clerk.com/ (10,000 users)
👤 Firebase Auth: https://firebase.google.com/ (무제한)
🔑 Supabase Auth: https://supabase.com/ (50,000 users)
결제
💳 Stripe: https://stripe.com/ (테스트 모드 무제한)
💰 PayPal: https://developer.paypal.com/ (샌드박스)
🏦 Toss Payments: https://docs.tosspayments.com/ (테스트)
이메일
📬 SendGrid: https://sendgrid.com/ (100/day)
📮 Mailgun: https://www.mailgun.com/ (5,000/month)
✉️ Resend: https://resend.com/ (100/day)
분석
📈 Google Analytics: https://analytics.google.com/ (무제한)
📊 Plausible: https://plausible.io/ (10,000 pageviews)
🔍 PostHog: https://posthog.com/ (1M events)
API 테스트 도구
Postman
// Postman Collection 예시
{
"info": {
"name": "Weather API Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Get Current Weather",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid={{API_KEY}}&units=metric",
"host": ["api", "openweathermap", "org"],
"path": ["data", "2.5", "weather"],
"query": [
{"key": "q", "value": "Seoul"},
{"key": "appid", "value": "{{API_KEY}}"},
{"key": "units", "value": "metric"}
]
}
}
}
]
}
curl 테스트
# GET 요청
curl "https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid=YOUR_API_KEY"
# 헤더 포함
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://api.example.com/data"
# POST 요청
curl -X POST \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"[email protected]"}' \
"https://api.example.com/users"
# 응답 저장
curl -o response.json "https://api.example.com/data"
# 진행률 표시
curl --progress-bar -o large_file.zip "https://example.com/file.zip"
제한사항 및 주의사항
Rate Limiting 전략
flowchart TB
Request[API 요청] --> Check{Rate Limit<br/>확인}
Check -->|OK| Call[API 호출]
Check -->|초과| Wait[대기]
Call --> Success{성공?}
Success -->|200| Cache[캐시 저장]
Success -->|429| Retry[재시도<br/>Exponential Backoff]
Success -->|5xx| Retry
Success -->|4xx| Error[에러 처리]
Wait --> Sleep[Sleep]
Sleep --> Check
Retry --> Sleep2[대기 시간 증가]
Sleep2 --> Call
Cache --> Return[결과 반환]
Error --> Return
무료 플랜 제한 비교
일일 요청 제한:
Alpha Vantage: 25 requests/day
NewsAPI: 100 requests/day
기상청: 1,000 requests/day
Kakao Map: 300,000 requests/day
월별 요청 제한:
OpenWeatherMap: 1,000,000 requests/month
WeatherAPI: 1,000,000 requests/month
ExchangeRate: 1,500 requests/month
RAWG: 20,000 requests/month
시간당 요청 제한:
Unsplash: 50 requests/hour
Pexels: 200 requests/hour
GitHub: 5,000 requests/hour (인증)
분당 요청 제한:
OpenWeatherMap: 60 requests/min
CoinGecko: 10-50 requests/min
Reddit: 60 requests/min
정리
API 선택 체크리스트
프로젝트 요구사항:
- [ ] 필요한 데이터 타입 확인
- [ ] 예상 요청 횟수 계산
- [ ] 상업적 사용 여부 확인
- [ ] 응답 속도 요구사항
- [ ] 데이터 신선도 (실시간 vs 캐싱)
API 평가:
- [ ] 무료 제한 확인
- [ ] 인증 방식 확인
- [ ] 문서 품질 확인
- [ ] 커뮤니티 활성도
- [ ] 안정성 (SLA)
보안:
- [ ] API 키 환경 변수 관리
- [ ] .gitignore에 .env 추가
- [ ] 프론트엔드는 백엔드 프록시 사용
- [ ] HTTPS 사용
- [ ] Rate limiting 구현
최적화:
- [ ] 캐싱 구현
- [ ] 에러 처리 및 재시도
- [ ] 요청 배칭
- [ ] 응답 압축
프로젝트 아이디어
초급:
✅ 날씨 앱 (OpenWeatherMap)
✅ 환율 계산기 (ExchangeRate-API)
✅ 영화 검색 (TMDB)
✅ 뉴스 리더 (NewsAPI)
중급:
✅ 암호화폐 트래커 (CoinGecko)
✅ 지도 기반 장소 검색 (Google Maps)
✅ 주식 차트 (Alpha Vantage)
✅ 이미지 갤러리 (Unsplash)
고급:
✅ AI 챗봇 (OpenAI)
✅ 영화 추천 시스템 (TMDB)
✅ 실시간 대시보드 (멀티 API)
✅ 데이터 시각화 (여러 API 통합)
핵심 팁
1. 무료 플랜 제한 확인
→ 일일/월별 요청 횟수 계산
2. API 키 보안
→ 환경 변수 사용, GitHub에 노출 금지
3. 캐싱 활용
→ 불필요한 요청 줄이기
4. 에러 처리
→ 429 (Rate Limit), 5xx (서버 오류) 재시도
5. 백엔드 프록시
→ 프론트엔드에서 API 키 숨기기
6. 약관 확인
→ 상업적 사용 제한 여부
7. 대체 API 준비
→ 메인 API 장애 시 폴백
참고 자료
한 줄 요약: 개인 프로젝트는 OpenWeatherMap(날씨), CoinGecko(암호화폐), NewsAPI(뉴스), TMDB(영화), Unsplash(이미지) 등 무료 API를 활용하되, Rate Limiting과 API 키 보안을 반드시 고려하세요.