바닐라 자바 스크립트 API - banilla jaba seukeulibteu API

Ajax를 쓰지않고 바닐라 자바스크립트로 백엔드에 API 요청을 보낼 수 있다
여기에 사용되는 메소드가 async, await, fetch이다

✔️ API 요청 보내기

api 통신을 하는 function이라면 앞에 async를 붙이면 이 function은 비동기 통신이라고 선언이 된다
그 후 function 안에 await fetch를 사용하면 API 요청을 보내고 리턴을 받은 후에 다음 라인이 실행된다

// async function을 사용하여 비동기 통신임을 선언
async function getMyPageData(nickname){
    // await fetch(API요청을 받는 백엔드 주소), {API요청에 담을 정보}
    const response = await fetch(`${backendBaseUrl}/mypage/${nickname}`, {
        method: 'GET',
        mode: 'cors',
        headers: {
            'X-CSRFToken': csrftoken,
            'Authorization': 'Bearer ' + token,
        }
    })
    // await로 백엔드에서 리턴을 받은 후 다음 라인이 실행
    if (response.status == 200){
        data = await response.json()
        return data
    }
    else {
        console.log(response.status, "유저 활동 데이터가 없습니다")
        return response.status
    }
}

참고 - https://github.com/bradtraversy/vanillawebprojects

사용한 API 사이트

https://www.themealdb.com/

TheMealDB.com

www.themealdb.com

API 호출 개념 참고

https://www.daleseo.com/js-window-fetch/

[자바스크립트] fetch() 함수로 API 호출하기

Engineering Blog by Dale Seo

www.daleseo.com


결과 화면

바닐라 자바 스크립트 API - banilla jaba seukeulibteu API
카테고리 버튼을 눌렀을 때
바닐라 자바 스크립트 API - banilla jaba seukeulibteu API
카테고리 별 음식
바닐라 자바 스크립트 API - banilla jaba seukeulibteu API
입력한 검색 단어에 해당되는 음식들
바닐라 자바 스크립트 API - banilla jaba seukeulibteu API
바닐라 자바 스크립트 API - banilla jaba seukeulibteu API
랜덤 버튼 or 음식을 선택했을 때 나오는 음식 정보들

코드

API 사이트에서 

Lookup full meal details by id
www.themealdb.com/api/json/v1/1/lookup.php?i=52772
Lookup a single random meal
www.themealdb.com/api/json/v1/1/random.php

List all meal categories
www.themealdb.com/api/json/v1/1/categories.php

Filter by Category
www.themealdb.com/api/json/v1/1/filter.php?c=Seafood

이러한 API들을 사용했다. 

차례대로 아이디에 따라 음식 보여주고

랜덤한 음식 하나를 보여주고

모든 음식 카테고리를 보여주고

카테고리에 따른 음식을 보여주는 API이다.

특정 음식을 검색하고

fetch를 통해 받아온 특정 음식의 데이터를 콘솔창에 출력해보면 이렇게 오브젝트가 나온다.

오브젝트의 value는 음식으로 이루어진 배열이고 각각의 요소는 음식의 특징들이 담긴 오브젝트이다. 

바닐라 자바 스크립트 API - banilla jaba seukeulibteu API

여기서 음식의 이름, 이미지, 아이디 등의 값들을 가져와 innerHTML로 html에 추가해서 화면에 띄운다.

음식을 클릭했을 때 event.path를 사용해 클릭한 노드의 이벤트 흐름을 따라 ID를 찾아 

그 ID에 맞는 음식 정보 화면을 띄운다.

음식 종류에 따라 띄우는 것, 랜덤한 음식 하나를 띄우는 것 모두 같은 로직으로 작성한다.

const search = document.getElementById("search"),
  form = document.getElementById("form"),
  random = document.getElementById("random"),
  meals = document.getElementById("meals"),
  result = document.getElementById("result"),
  single_meal = document.getElementById("single-meal"),
  categoryBtn = document.getElementById("categories"),
  meals_by_category = document.getElementById("meals-by-category"),
  search_result = document.getElementById("result"),
  area = document.getElementById("area");

function searchMeal(e) {
  e.preventDefault(); // submit 되어서 화면이 reload 되는 것을 방지
  meals_by_category.innerHTML = "";
  single_meal.innerHTML = "";

  const searchItem = search.value;

  if (searchItem.trim()) {
    // 입력 내용이 있으면
    fetch(`https://www.themealdb.com/api/json/v1/1/search.php?s=${searchItem}`)
      //아니 대문자 S였는데 소문자로 바꾸니까 됨 뭐임
      .then((response) => response.json()) // json으로 변환
      // 성공 했을 경우에는 응답(response)객체를 resolve
      // 대부분의 REST API들은 JSON 형태의 데이터를 응답하기 때문에 응답 객체는 JSON()메서드를 제공
      // 응답 객체로부터 JSON 포멧의 응답 전문을 자바스크립트 객체로 변환하여 얻을 수 있음
      .then((data) => {
        console.log(data);
        // data는 {meals: Array(음식의개수)} 오브젝트.
        // 그 Array의 요소들은 오브젝트다.
        if (data.meals === null) {
          meals.innerHTML = "";
          result.innerHTML = `<p class="search-result">찾는 결과가 없습니다.</p>`;
        } else {
          result.innerHTML = `<p class="search-result">'${searchItem}' 검색결과 ${data.meals.length}건</p>`;
          const mealArray = data.meals.map(
            // 음식 이미지, 이름을 넣은 새로운 array 만듬
            (meal) =>
              `
              <div class="meal">
                <img src="${meal.strMealThumb}" alt="${meal.strMeal}" />
                <div class="meal-info" data-mealID="${meal.idMeal}">
                  <h3>${meal.strMeal}</h3>
                </div>
              </div>
              `
          );

          meals.innerHTML = mealArray.join("");
          // join 안하면 각각 배열의 인덱스가 0, 1, 2..로 저장이 되는데
          // join을 해줘 하나의 문자열로 반환하여 html에 넣어 줌
        }
      });
    search.value = "";
  } else {
    alert("찾는 음식을 입력하세요.");
  }
}

function getRandomMeal() {
  meals.innerHTML = "";
  result.innerHTML = "";
  meals_by_category.innerHTML = "";

  fetch(`https://www.themealdb.com/api/json/v1/1/random.php`)
    .then((response) => response.json())
    .then((data) => {
      //console.log(data); // data는 랜덤으로 선택된 음식의 정보가 담긴 길이가 1인 array 이다.
      const meal = data.meals[0];

      addMealToDOM(meal);
    });
}

function addMealToDOM(meal) {
  const ingredients = [];

  for (let i = 1; i <= 20; i++) {
    if (meal[`strIngredient${i}`]) {
      // 있으면 넣어줌 (조건문 없으면 빈칸 출력됨)
      ingredients.push(
        `${meal[`strIngredient${i}`]} - ${meal[`strMeasure${i}`]}`
      );
    } else {
      break;
    }
  }

  single_meal.innerHTML = `
    <div class="single-meal">
      <h2>${meal.strMeal}</h2>
      <img src="${meal.strMealThumb}" alt="${meal.strMeal}"/>
      <div class="single-meal-info">
        <p> ${meal.strCategory ? `${meal.strCategory}` : ""} /
         ${meal.strArea ? `${meal.strArea}` : ""}</p> 
      </div>

      <div class="main">
        <ul>
          ${ingredients.map((item) => `<li>${item}</li>`).join("")}
        </ul>
       
        <p>${meal.strInstructions}</p>
        <p><i class="fab fa-youtube"></i> <a href=${
          meal.strYoutube ? `${meal.strYoutube}` : ""
        } target='_blank'>만드는 법 YOUTUBE로 보기</a></p>
      </div>

    </div>
        `;
}

function getMealById(mealID) {
  // ID에 따라 정보들을 DOM에 추가한다.
  fetch(`https://www.themealdb.com/api/json/v1/1/lookup.php?i=${mealID}`)
    .then((response) => response.json())
    .then((data) => {
      const meal = data.meals[0];

      addMealToDOM(meal);
      single_meal.scrollIntoView({
        behavior: "smooth",
        block: "start",
        inline: "nearest",
      });
    });
}

function showCategories() {
  single_meal.innerHTML = "";
  meals.innerHTML = "";

  search_result.innerHTML = "";
  fetch(`https://www.themealdb.com/api/json/v1/1/categories.php`)
    .then((res) => res.json())
    .then((data) => {
      meals_by_category.innerHTML = data.categories
        .map(
          (categories) =>
            `
        <div class = "mealByCategory" data-categoryName="${categories.strCategory}">
          <h2>${categories.strCategory}</h2>
          <img src="${categories.strCategoryThumb}" alt="${categories.strCategory}"/>
          
        </div>
        `
        )
        .join("");
    });
}

function filterByCategory(name) {
  search_result.innerHTML = `<p class="search-result">카테고리: '${name}'</p>`;
  fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?c=${name}`)
    .then((res) => res.json())
    .then((data) => {
      meals_by_category.innerHTML = data.meals
        .map(
          (meal) =>
            `
            <div class="meal">
            <img src="${meal.strMealThumb}" alt="${meal.strMeal}" />
            <div class="meal-info" data-mealID="${meal.idMeal}">
              <h3>${meal.strMeal}</h3>
            </div>
          </div>
        `
        )
        .join("");
    });
}

function getMealInfo(e) {
  //Event.composedPath는 이벤트의 Path를 반환해준다. 이벤트가 어떤 흐름으로 도달했는지 알 수 있다.
  //console.log(e.path) 했을 때
  //이미지를 클릭했을 시에 [img, div.meal, div#meals.meals, body, html, document, window] 가 출력
  const mealInfo = e.path.find((item) => {
    //console.log(item.classList);

    if (item.classList) {
      return item.classList.contains("meal-info"); //contains함수라는게 없다는데??
      // meal-info class가 있으면 true반환
    } else {
      return false;
    }
  });

  if (mealInfo) {
    const mealID = mealInfo.getAttribute("data-mealid");
    getMealById(mealID);
  }
}

form.addEventListener("submit", searchMeal); //form 안의 input type이 submit
random.addEventListener("click", getRandomMeal);

meals.addEventListener("click", getMealInfo);
meals_by_category.addEventListener("click", getMealInfo);

meals_by_category.addEventListener("click", (e) => {
  const mealCategory = e.path.find((item) => {
    if (item.classList) {
      return item.classList.contains("mealByCategory");
    } else {
      return false;
    }
  });
  if (mealCategory) {
    const categoryName = mealCategory.getAttribute("data-categoryName");
    filterByCategory(categoryName);
  }
});

categoryBtn.addEventListener("click", showCategories);

노마드코더, 드림코딩에서도 잠깐 fetch를 사용해봤지만 이번에야말로 제대로 활용하여 연습할 수 있었다.

가져온 데이터가 어떤 타입이고 어떤 요소들이 있는지 잘 분석해서 적절하게 사용하는게 중요한듯 하다.