[미니 프로젝트] 25JS Ideas (3 of 27)- Random quote generator

Seungrok Yoon (Lethe)·2023년 7월 6일
0
post-thumbnail

Project 3 : Random quote generator

(최초 작성일 - 2023.07.07)


학습할 내용

  • fetch API
  • Node.appenChild()

기본 마크업 및 스타일 레이아웃

먼저 기본 마크업과 레이아웃을 잡아주었습니다. 결과는 아래와 같습니다.
<p>태그와 <q>태그를 사용해서 카드의 content를 만들어주었습니다. 명언의 저자의 이름은 다른 폰트 스타일을 주기 위해 별도의 author클래스를 주었습니다.

기본 마크업이 끝난 직후 모습

기능 요구사항

추가적인 스타일링은 핵심 기능이 완료되고 나서 조정할 예정입니다. 그러면 어떤 기능이 필요한지 나열해볼까요?

  • 유저가 Generate Quote 버튼을 클릭하면, 명언이 카드에 출력되는 기능이 필요합니다.

이 기능을 구현하기 위해서는

  • (1) 프로젝트 내부에 명언을 저장하는 파일을 생성하고, 이를 불러와서 사용
  • (2) 외부 API 서버로부터 명언 데이터를 받아오기
    이렇게 두 가지 방법이 있을 것 같아요.

저는 외부 API 콜을 하여 명언 데이터를 버튼 클릭시마다 요청해 보겠습니다. 외부 API는 API-NinjasQuotes API를 사용해볼게요.

여러분들도 원하신다면 가입하시고, API키를 받아서 사용해보시기 바랍니다. API키는 잘 숨겨서 사용해야겠죠?

기능1: 명언 API 호출 함수 작성하기

fetch API를 활용하여 비동기 함수 작성하기

MDN - fetch API 문서를 참고하여 API에서 명언을 요청하는 비동기 함수를 작성했습니다. 헤더 부분에 API 키도 첨부하는 것 잊지 말아주세요!

async function getQuotes(url = '') {
  try {
    const response = await fetch(url, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': config.api_ninjas_key,
      },
    });
    if (!response.ok) {
      throw new Error('네트워크 오류');
    }
    return response.json();
  } catch (error) {
    console.error(error);
  }
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./style.css" />
    <title>Random Quotes</title>
  </head>
  <body>
    <div class="container">
      <div class="card">
        <div class="content">
          <p class="quote"></p>
          <span class="author"></span>
        </div>
        <button class="generate-btn">Generate Quote</button>
      </div>
    </div>
    <script type="text/javascript" src="/apiKey.js"></script>
    <script type="module" src="./main.js"></script>
  </body>
</html>

번외: API 키 숨기기

주의: 이 방법은 배포 시에는 적용되기 어려운 방법입니다. 이에 관해서는 추가로 포스팅 하도록 하겠습니다.

깃허브에 내 API키가 노출이 되는 것은 보안적으로 굉장히 안 좋은 일입니다. 그렇기에, 별도의 apiKey.js파일에 내 API 키를 객체로 저장해두고, script 태그에 추가하여 가져오는 식으로 우회했습니다.

apiKey.js.gitIgnore에 추가를 하였습니다.

//apiKey.js
const config = {
  API_NINJAS_KEY: '[내가 발급받은 API Key]',
};

이로써 API 키는 깃허브에 노출되지 않을 것입니다. 하지만,이 방법은 레포지토리를 배포할 때는 적용될 수 없는 미봉책에 해당합니다. 왜냐하면 배포는 깃허브 레포에 있는 파일들을 기반하여 진행되기 때문에, 내 로컬에 있는 apiKey.js파일을 찾을 수 없을 것이 뻔하기 때문입니다.

또한, 이렇게 코드로 우회한다고 해도, 브라우저의 Nework 헤더를 확인해보면 버젓이 내 API키를 확인할 수 있어서 찜찜하기도 합니다.
우측 하단에 API 키가 버젓이 노출되고 있는 모습입니다

프론트단에서는 근본적인 해결책이 존재하지 않는 것 같다는 생각이 들었습니다.

조사를 해보니,

  • API 프록시 서버를 별도로 개발하여 활용 (프록시 서버가 클라이언트로부터 받은 요청에 API키를 주입하여 API NINJAS 서버로 요청 전달)
  • 배포 시 Netlify 와 같은 클라우드 서비스로 배포 시, 해당 서비스에서 API Key를 환경 변수로 설정하여 빌드 시점에 환경 변수를 전달

정도가 있을 것으로 생각됩니다. 둘 다 프론트단이 아닌 서버단에서 환경변수로 API Key를 관리하는 방법이군요. 하지만 지금 당장은 배포하지 않을 예정이니 이정도만 하고 일단 쿨하게 ~ 넘어갑시다!
(node.js로 API 프록시 서버를 만들어보게 될 수도...? )

=> API 키를 프론트단에서 완전히 숨기기 위해, Netlify 환경변수와 Serverless function을 활용하여 진짜로 실행에 옮겨봤습니다. 정말 멍멍이 고생하면서 성공했어요 엉엉...Netlify 배포 후기글에서 확인하실 수 있습니다!

기능 2: Generate Quote 버튼에 기능 연결

기능 구현 코드

기능구현을 마친 JS 파일은 아래와 같습니다.

const quoteParagraph = document.querySelector('.quote');
const authorSpan = document.querySelector('.author');
const generateBtn = document.querySelector('.generate-btn');

async function getQuotes(url = '') {
  try {
    const response = await fetch(url, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': config.api_ninjas_key,
      },
    });
    if (!response.ok) {
      throw new Error('네트워크 오류');
    }
    return response.json();
  } catch (error) {
    console.error(error);
  }
}

async function handleClick() {
  const [{ author, quote }] = await getQuotes(
    'https://api.api-ninjas.com/v1/quotes'
  );
  const quoteElement = document.createElement('q');
  quoteElement.innerText = quote;
  quoteParagraph.innerHTML = '';
  quoteParagraph.appendChild(quoteElement);
  authorSpan.innerText = '- ' + author;
}

generateBtn.addEventListener('click', handleClick);

오...잘 동작하는군요

스타일링

배경은 radial-gradient를 사용하였습니다. 처음 접하는 속성값이라

background: radial-gradient(ellipse at center, #06bdc1, #68469f);

완성된 모습


프로젝트 회고


[Network] CORS Preflight Request

https://developer.mozilla.org/en-US/docs/Glossary/

CORS

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
https://developer.mozilla.org/en-US/docs/Glossary/CORS

[CSS] body의 border 없애기

이상한 공백이 화면에 생겼습니다. 이 하얀 공백을 없애야, container div 가 화면을 꽉 채울텐데 말이죠.

* {
  box-sizing: border-box;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  background: radial-gradient(ellipse at top, #e66465, transparent),
    radial-gradient(ellipse at bottom, #4d9f0c, transparent);
}

바로 <body>의 것이었습니다. 8px의 margin이 적용되어 있었습니다.

찾아보니, <body>는 8px의 기본 margin이 적용되어 있다고 합니다.

참고링크

등잔 밑이 어두웠어요...ㅠㅠ 그래서 <body>의 margin과 padding을 0으로 설정해주어 container div가 화면 전체를 채우도록 스타일링할 수 있었습니다.

* {
  box-sizing: border-box;
}

body {
  padding: 0;
  margin: 0;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background: radial-gradient(ellipse at top, #e66465, transparent),
    radial-gradient(ellipse at bottom, #4d9f0c, transparent);
}

[CSS] radial-gradient

https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/radial-gradient

[CSS] @media query

https://developer.mozilla.org/en-US/docs/Web/CSS/@media

profile
안녕하세요 개발자 윤승록입니다. 내 성장을 가시적으로 기록하기 위해 블로그를 운영중입니다.

0개의 댓글