Ch.18 API 연동 — fetch와 데이터 흐름

실전 — 날씨 앱 만들기

공개 API를 활용하여 실제 데이터를 가져올 수 있다사용자 입력 → API 호출 → 결과 표시 흐름을 구현할 수 있다로딩, 에러, 빈 상태의 UX를 적절히 처리할 수 있다

공개 API로 진짜 앱을 만들어보자

지금까지 배운 fetch, async/await, useEffect를 모두 결합해서 실제 날씨 API를 호출하는 앱을 만듭니다.

배운 것들을 하나의 앱으로 조립할 수 있을까?

실전 조립 — 검색 → fetch → 상태 관리 → 화면 표시까지 전체 흐름을 완성합니다.


article

핵심 내용

연습용 공개 API 무료로 바로 사용 가능합니다

// JSONPlaceholder — 바로 테스트 가능!

// 게시물 목록
const res = await fetch(
  "https://jsonplaceholder.typicode.com/posts"
);
const posts = await res.json();
// [{id:1, title:"...", body:"..."}, ...]

// 특정 게시물
const res2 = await fetch(
  "https://jsonplaceholder.typicode.com/posts/1"
);
const post = await res2.json();
// {id:1, title:"...", body:"..."}

API 키란? 일부 API는 사용자 인증을 위해 API 키(비밀번호)를 요구합니다. 회원가입 → 무료 키 발급 → URL에 ?appid=YOUR_KEY 형태로 전달합니다.

사용자 입력 → fetch → 결과 표시 완전한 흐름을 구현합니다

import { useState } from "react";

function WeatherApp() {
  const [city, setCity] = useState("");
  const [weather, setWeather] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  async function handleSearch() {
    if (!city.trim()) return;
    setLoading(true);
    setError(null);

    try {
      const API_KEY = "YOUR_API_KEY";
      const url = `https://api.openweathermap.org
        /data/2.5/weather?q=${city}
        &appid=${API_KEY}&units=metric&lang=kr`;

      const res = await fetch(url);
      if (!res.ok) {
        throw new Error("도시를 찾을 수 없습니다");
      }
      const data = await res.json();
      setWeather(data);
    } catch (err) {
      setError(err.message);
      setWeather(null);
    } finally {
      setLoading(false);
    }
  }

입력: 사용자가 도시 이름 입력 (city state)

검증: 빈 입력 체크, 로딩 시작

API 호출: fetch()로 날씨 데이터 요청

결과 처리: 성공 → setWeather, 실패 → setError

화면 갱신: state 변경으로 자동 리렌더링

검색 전 검증을 잊지 마세요 city.trim()이 비어 있으면 API 호출을 하지 않습니다. 불필요한 네트워크 요청을 막는 기본적인 UX입니다.

로딩 스피너, 에러 메시지, 빈 상태 3가지 UX를 챙깁니다

  // WeatherApp의 return 부분
  return (
    <div className="max-w-md mx-auto p-6">
      <div className="flex gap-2">
        <input
          value={city}
          onChange={e => setCity(e.target.value)}
          onKeyDown={e =>
            e.key === "Enter" && handleSearch()}
          placeholder="도시 이름 입력"
          className="flex-1 border rounded-lg
            px-4 py-2 focus:ring-2
            focus:ring-blue-500" />
        <button
          onClick={handleSearch}
          disabled={loading}
          className="bg-blue-500 text-white
            px-4 py-2 rounded-lg
            hover:bg-blue-600
            disabled:opacity-50">
          {loading ? "검색 중..." : "검색"}
        </button>
      </div>

      {/* 에러 메시지 */}
      {error && (
        <p className="text-red-500 mt-4">
          {error}
        </p>
      )}

      {/* 날씨 결과 */}
      {weather && (
        <div className="mt-4 bg-white rounded-xl
             shadow p-6 text-center">
          <h2 className="text-2xl font-bold">
            {weather.name}
          </h2>
          <p className="text-4xl mt-2">
            {Math.round(weather.main.temp)}°C
          </p>
          <p className="text-gray-600 mt-1">
            {weather.weather[0].description}
          </p>
        </div>
      )}
    </div>
  );
}

바이브 코더 필수 패턴 AI에게 앱을 만들어 달라고 하면 이 패턴이 반드시 포함됩니다. input + fetch + loading/error/data 상태 = 모든 API 앱의 뼈대!

API 호출 시 로딩 상태를 확실히 끝내기 위해 setLoading(false)는 어디에?

fetch()는 404 응답도 catch 블록에서 잡아준다.

다음 중 useEffect + fetch 패턴에서 빠지면 안 되는 것은?

바이브 코더가 API 앱에서 반드시 관리해야 할 3가지 상태는?

key

핵심 용어

📝

JSONPlaceholder

가짜 REST API — 연습용 사용자/게시물/댓글 데이터

🌤️

OpenWeatherMap

실제 날씨 데이터 — 무료 API 키 발급 필요

PokeAPI

포켓몬 데이터 — 키 불필요, 바로 사용 가능

🐱

The Cat API

고양이 사진 API — 랜덤 이미지 제공

로딩 상태

버튼 텍스트를 "검색 중..."으로 변경 + disabled

⚠️

에러 표시

빨간색 텍스트로 사용자에게 원인 안내

📭

빈 상태

weather가 null이면 결과 영역 미표시

⌨️

키보드 지원

Enter 키로도 검색 가능 (onKeyDown)

edit_note

정리 노트

실전 — 날씨 앱 만들기 총정리

핵심 포인트

공개 API
OpenWeatherMap 등 무료 API로 실제 데이터 활용
검색 → 호출 흐름
사용자 입력 → API 호출 → 결과 표시의 전체 과정
에러 핸들링
네트워크 에러, 404, 빈 결과 등 예외 상황 처리
UX 패턴
로딩 스피너, 에러 메시지, 빈 상태 안내 등 사용자 배려

실전 앱의 핵심은 '정상 작동'이 아니라 '에러 상황 처리'입니다.

퀴즈와 인터랙션으로 더 깊이 학습하세요

play_circle인터랙티브 레슨 시작