Post

[Day63] ๋‚ ์”จ API ์—ฐ๋™

[Day63] ๋‚ ์”จ API ์—ฐ๋™

๐Ÿ”— ์‚ฌ์šฉ API: OpenWeatherMap

OpenWeatherMap์€ ์ „ ์„ธ๊ณ„ ๋‚ ์”จ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฌด๋ฃŒ API ์ด๋‹ค !


โœ… API Key ๋ณด์•ˆ ๊ด€๋ฆฌ

.env ํŒŒ์ผ ์ƒ์„ฑ

API Key๋Š” ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜๋ฉด ์•ˆ ๋˜๋Š” ๋ฏผ๊ฐ ์ •๋ณด โ†’ .env ํŒŒ์ผ์— ์ €์žฅํ•ด์„œ ์ฝ”๋“œ์—์„œ ๋ถ„๋ฆฌ ๊ด€๋ฆฌ

1
VITE_WEATHER_API_KEY = 8b7f7d685453d033636d45
  • .env - ๊ธฐ๋ณธ ํŒŒ์ผ, ๋ชจ๋“  ํ™˜๊ฒฝ์—์„œ ๋กœ๋“œ
  • .env.local - ๋กœ์ปฌ ํ™˜๊ฒฝ ๋ณ€์ˆ˜, git์—์„œ ๋ฌด์‹œํ•ด์•ผ ํ•จ
  • .env.development - ๊ฐœ๋ฐœ ํ™˜๊ฒฝ
  • .env.production - ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ
  • .env.test - ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ

๐Ÿ“Œ Vite ํ™˜๊ฒฝ์—์„œ์˜ ์ฃผ์˜์‚ฌํ•ญ

  • VITE_ ์ ‘๋‘์‚ฌ๋Š” Vite ํ™˜๊ฒฝ์—์„œ๋งŒ ํ•„์ˆ˜
    • VITE_API_KEY=abcdef123456 (O)
    • API_KEY=abcdef123456 (X - ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ ์ ‘๊ทผ ๋ถˆ๊ฐ€)
  • .env.local์€ ๋ฐ˜๋“œ์‹œ .gitignore์— ์ถ”๊ฐ€ํ•ด์„œ Git์— ์˜ฌ๋ผ๊ฐ€์ง€ ์•Š๋„๋ก ๊ด€๋ฆฌ

๐Ÿ“Œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ ์˜ˆ์‹œ

1
2
3
4
5
6
7
8
9
10
11
12
13
const API_KEY = import.meta.env.VITE_WEATHER_API_KEY;
const BASE_URL = "https://api.openweathermap.org/data/2.5/weather";

export const getWeatherByCurrentLocation = async (lat, lon) => {
  try {
    const res = await axios.get(
      `${BASE_URL}?lat=${lat}&lon=${lon}&appid=${API_KEY}&lang=kr&units=metric`
    );
    return res.data;
  } catch (error) {
    console.log("๋‚ ์”จ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ", error);
  }
};
  • import.meta.env.VITE_๋ณ€์ˆ˜๋ช… ์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

โœ… ๊ณต๊ณต API ์‚ฌ์šฉ ๊ฐœ์š”

API ์—”๋“œํฌ์ธํŠธ

  • ํ˜„์žฌ ์œ„์น˜ ๊ธฐ๋ฐ˜:
    https://api.openweathermap.org/data/2.5/weather?lat={์œ„๋„}&lon={๊ฒฝ๋„}&appid={API_KEY}
  • ๋„์‹œ๋ช… ๊ธฐ๋ฐ˜:
    https://api.openweathermap.org/data/2.5/weather?q={๋„์‹œ๋ช…}&appid={API_KEY}

API ํ˜ธ์ถœ ์ฝ”๋“œ ๊ตฌ์กฐ

๐Ÿ“‚ useWeatherApi.js

๊ณตํ†ต API ํ˜ธ์ถœ ํ•จ์ˆ˜๋“ค์„ ๋ชจ์•„๋†“์€ ์œ ํ‹ธ ํŒŒ์ผ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import axios from "axios";

const API_KEY = import.meta.env.VITE_WEATHER_API_KEY;
const BASE_URL = "https://api.openweathermap.org/data/2.5/weather";

// 1. ์ขŒํ‘œ ๊ธฐ๋ฐ˜ ๋‚ ์”จ ์ •๋ณด
export const getWeatherByCurrentLocation = async (lat, lon) => {
  const res = await axios.get(
    `${BASE_URL}?lat=${lat}&lon=${lon}&appid=${API_KEY}&lang=kr&units=metric`
  );
  return res.data;
};
// 2. ํ˜„์žฌ ์œ„์น˜ ๊ธฐ๋ฐ˜ ๋‚ ์”จ ์ •๋ณด
export const getCurrentData = async () => {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(
      async (pos) => {
        const { latitude, longitude } = pos.coords;
        const res = await getWeatherByCurrentLocation(latitude, longitude);
        resolve(res);
      },
      (err) => reject(err)
    );
  });
};
// 3. ๋„์‹œ๋ช… ๊ธฐ๋ฐ˜ ๋‚ ์”จ ์ •๋ณด
export const getCountryData = async (city) => {
  const res = await axios.get(
    `${BASE_URL}?q=${city}&appid=${API_KEY}&lang=kr&units=metric`
  );
  return res.data;
};

๐Ÿ” ์™œ getCurrentData์—์„œ Promise๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ• ๊นŒ?

getCurrentData ํ•จ์ˆ˜์—์„œ Promise๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” navigator.geolocation.getCurrentPosition์ด Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š” ์ฝœ๋ฐฑ ๊ธฐ๋ฐ˜ API์ด๊ธฐ ๋•Œ๋ฌธ !

โœ… ๋ฌธ์ œ ์ƒํ™ฉ

1
navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
  • ์ด API๋Š” ์„ฑ๊ณต ์‹œ์—๋Š” successCallback, ์‹คํŒจ ์‹œ์—๋Š” errorCallback์„ ์‹คํ–‰
  • ํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ ์ž‘์—…์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— await์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค

โœ… ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•: Promise๋กœ ๊ฐ์‹ธ๊ธฐ

์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฑด ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ getCurrentData๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ฒƒ

1
const data = await getCurrentData();

์ด๋ ‡๊ฒŒ ์“ฐ๋ ค๋ฉด getCurrentData๋Š” Promise๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๊ฐ์‹ผ๋‹ค !

1
2
3
4
5
6
7
8
9
10
11
12
export const getCurrentData = async () => {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(
      async (pos) => {
        const { latitude, longitude } = pos.coords;
        const res = await getWeatherByCurrentLocation(latitude, longitude);
        resolve(res); // ์„ฑ๊ณตํ•˜๋ฉด ๋‚ ์”จ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜
      },
      (err) => reject(err) // ์‹คํŒจํ•˜๋ฉด ์—๋Ÿฌ ๋ฐ˜ํ™˜
    );
  });
};

๐Ÿ” ํ๋ฆ„ ์š”์•ฝ

  1. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ˜„์žฌ ์œ„์น˜ ์ขŒํ‘œ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด getCurrentPosition ํ˜ธ์ถœ
  2. ์„ฑ๊ณต ์‹œ โ†’ ์ขŒํ‘œ ๋ฐ›์•„์„œ getWeatherByCurrentLocation์œผ๋กœ ๋‚ ์”จ ์ •๋ณด ์š”์ฒญ
  3. ์ด ๋ชจ๋“  ํ๋ฆ„์„ ํ•˜๋‚˜์˜ Promise๋กœ ๊ฐ์‹ธ์„œ await์œผ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๊ฒŒ ๋ณ€ํ™˜


๐Ÿ’ก

์š”์•ฝํ•˜์ž๋ฉด getCurrentPosition์€ ์˜ค๋ž˜๋œ API๋ผ์„œ ์ฝœ๋ฐฑ ํŒจํ„ด์„ ์‚ฌ์šฉ
๊ทธ๋ž˜์„œ async/await ํŒจํ„ด์„ ์“ฐ๋ ค๋ฉด ์ด๊ฑธ Promise๋กœ ๊ฐ์‹ธ๋ฉด ๋น„๋™๊ธฐ ํ๋ฆ„์— ํ†ตํ•ฉํ•˜๊ฒŒ ํ•ด์คŒ
getCurrentData๋Š” ์ด๊ฑธ ๊ฐ์‹ธ์„œ, ์šฐ๋ฆฌ๊ฐ€ ํŽธํ•˜๊ฒŒ awaitํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ํ•จ์ˆ˜


๐Ÿ“‚ WeatherPage ์ปดํฌ๋„ŒํŠธ

React์—์„œ useSearchParams๋ฅผ ํ™œ์šฉํ•ด ๋„์‹œ๋ช…์„ URL๋กœ ๊ด€๋ฆฌํ•˜๊ณ ,
์„ ํƒ๋œ ๋„์‹œ ๋˜๋Š” ํ˜„์žฌ ์œ„์น˜์— ๋”ฐ๋ผ ๋‚ ์”จ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ตฌ์กฐ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import React, { useEffect, useState } from "react";
import css from "./WeatherPage.module.css";
import { getCurrentData } from "./useWeatherApi";
import { useSearchParams } from "react-router-dom";
import Button from "../weather/Button";
import { getCountryData } from "./useWeatherApi";

const WeatherPage = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  const city = searchParams.get("city");
  const [weatherData, setWeatherData] = useState(null);

  const cityButtons = [
    { id: "current", label: "ํ˜„์žฌ์œ„์น˜" },
    { id: "seoul", label: "์„œ์šธ" },
    { id: "tokyo", label: "๋„์ฟ„" },
    { id: "new york", label: "๋‰ด์š•" },
    { id: "paris", label: "ํŒŒ๋ฆฌ" },
  ];
  useEffect(() => {
    const fetchWeatherData = async () => {
      try {
        let data;
        if (city) {
          data = await getCountryData(city);
        } else {
          data = await getCurrentData();
        }
        setWeatherData(data);
      } catch (err) {
        console.err(err);
      }
    };
    fetchWeatherData();
  }, [city]);

  const handleChangeCity = (city) => {
    if (city === "current") {
      setSearchParams({});
    } else {
      setSearchParams({ city });
    }
  };

  return (
    <main className={css.main}>
      <h2>WeatherPage</h2>
      <div className={css.weatherInfo}>
        <p className={css.location}>
          {weatherData?.sys.country} / {weatherData?.name}
        </p>
        <div className={css.temperature}>
          <p>{weatherData?.main.temp} &#8451;</p>
          <p>
            <img
              src={`http://openweathermap.org/img/wn/${weatherData?.weather[0].icon}.png`}
              alt="weather"
            />
          </p>
        </div>
      </div>
      <div className={css.btnList}>
        {cityButtons.map((button) => (
          <Button
            key={button.id}
            city={button.id}
            label={button.label}
            onClick={handleChangeCity}
          />
        ))}
      </div>
    </main>
  );
};

export default WeatherPage;

์‹คํ–‰ ๊ฒฐ๊ณผ ์˜ˆ์‹œ

๋‚ ์”จ UI ๋‚ ์”จ UI

  • ๋„์‹œ ๋ฒ„ํŠผ ํด๋ฆญ โ†’ URL์— ?city=tokyo ๋“ฑ ์ž๋™ ๋ฐ˜์˜
  • ํ˜„์žฌ ์œ„์น˜ ํด๋ฆญ ์‹œ geolocation API๋กœ ํ˜„์žฌ ์ขŒํ‘œ ์š”์ฒญ

END

This post is licensed under CC BY 4.0 by the author.