React로 영화 정보를 검색해보자 - 두번째 네이버 API 사용 / CORS 설정 하기
반응형

 

안녕하세요 Lotts 입니다 😉

 

저번 포스팅에서는

ReactJS 설치와 네이버 검색 API를 어떻게 사용하는지를 확인해봤어요

자세한 내용은 아래 포스팅을 참조하시면 됩니다

 

2020/03/27 - [프로그래밍/ReactJS] - React로 영화 정보를 검색해보자 - 첫 번째 React 설치 / 네이버 API 사용기

 

React로 영화 정보를 검색해보자 - 첫번째 React 설치 / 네이버 API 사용기

안녕하세요 Lotts 입니다 😉 nomadCoders에서 ReactJS로 웹 서비스만들기 강의를 듣고 있습니다. 강의에서는 영화 정보를 yts사이트에서 가져와서 사용하고 있습니다만 이번 포스팅에서 소개할 내용은 우리에게..

msyu1207.tistory.com

 

이번 포스팅에서는

네이버 검색 API를 이용하여 영화 정보 가져오기

CORS를 처리하는 방법 One

CORS를 처리하는 방법 Two

에 대해서 알아보겠습니다

👏👏👏👏

 

 

React로 영화정보를 검색해보자 두 번째 시간 🎉

 


📢네이버 검색 API를 이용하여 영화 정보 가져오기

 

저번 포스팅에서 영화 정보를 가져오는 방법에 대해서는

POSTMAN를 통해 확인했어요

 

본격적으로 소스를 보면서 확인해 보겠습니다 🔎

 

directory 구조

movie_app
|
|
--src
   |
   --App.js
   --index.js
|
--routes
    |
    --Search.js
|
--components
    |
    --SearchMovie.js
    --Navigation.js

 

index.js

 

💡<App />는 component 이며 data를 보여줍니다

또한 HTML을 반환하는 함수입니다

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

 

App.js

 

nomadcorders 인강 마지막 부분에 HashRouter를 사용하여 분기 처리를 해주는데 여기서 저는

Search를 별도로 만들어서 영화 검색에 사용하였습니다 😊

 

📍 react-router-dom 설치

npm install react-router-dom

react-router-dom을 설치하고 HashRouter를 사용합니다

import React from 'react';
import {HashRouter, Route} from 'react-router-dom';
import Home from "./routes/Home";
import Search from "./routes/Search"
import Navigation from "./components/Navigation";


function App(){
  return (
  <HashRouter>
    <Navigation />
    <Route path="/" exact={true} component={Home}></Route>
    <Route path="/search" exact={true} component={Search}></Route>
  </HashRouter>
  );
}

export default App;

💡 참고

Route를 보면 path가 '/', '/search'가 있습니다.

exact={true} 옵션이 없으면 react는 현재 주어진 path를 통해 모든 route를 조회합니다

예를 들어 현재 경로가 'localhost:3000/#/search' 인경우 react는 /#/search에 대해서

모든 route를 검색하는데 해당되는 route는

<Route path="/" component={Home}></Route>

<Route path="/search" component={Search}></Route>

모두 해당이 됩니다.

따라서 두개의 route에 있는 데이터가 렌더링 됩니다

 

이러한 이슈를 해결하기 위해서는 현재 코딩처럼 exact={true}를 같이 사용합니다

 

💡 exact={true} 옵션

설정하면 해당 path에 정확히 일해야 렌더링을 진행합니다

따라서 'localhost:3000/#/search' 경우 '/search'만 랜더링을 진행합니다

 

 

Search.js

 

네이버 검색 API를 이용하여 영화의 이름을 검색하여 데이터를 받아오는 부분입니다

 

저번 포스팅에서 보신 것처럼 api를 이용해 데이터를 가져오면 아래 JSON  형식처럼 가져옵니다

따라서 해당 data안에 items리스트를 가져와 합니다 🔎

"items": [ { "title": "<b>아이언맨</b>", 
			 "link": "https://movie.naver.com/movie/bi/mi/basic.nhn?code=123684", 
             "image": "https://ssl.pstatic.net/imgmovie/mdi/mit110/1236/123684_P00_161340.JPG", 
             "subtitle": "", 
             "pubDate": "2014", 
             "director": "김용수|", 
             "actor": "이동욱|신세경|김갑수|", 
             "userRating": "4.94" 
             }, 
			{ "title": "<b>아이언맨</b> &amp; 캡틴 아메리카", 
              "link": "https://movie.naver.com/movie/bi/mi/basic.nhn?code=133519", 
              "image": "https://ssl.pstatic.net/imgmovie/mdi/mit110/1335/133519_P01_183929.jpg", 
              "subtitle": "Iron Man &amp; Captain America: Heroes United", 
              "pubDate": "2014", 
              "director": "", 
              "actor": "아드리안 패스더|로저 크레이그 스미스|", 
              "userRating": "9.00" 
              }
          ]

 

전체 소스를 확인하고 한 줄씩 간략하게 설명하도록 하겠습니다

import React from 'react';
import axios from 'axios';
import SearchMovie from '../components/SearchMovie';
import "./Home.css";
import "./Search.css";

class Search extends React.Component {
  state = {
    isLoading: true,
    movies: [],
    value: ""
  };


  getSearchMovie = async () => {
    const ID_KEY = 'id_key';
    const SECRET_KEY = 'secret_key';
    const search = this.state.value;
    try {
      if (search === "") {
        this.setState({movies: [], isLoading: false})
      } else {
        const {data: {
            items
          }} = await axios.get('https://openapi.naver.com/v1/search/movie.json',{
            params:{
              query: search,
              display: 20
            },
            headers: {
              'X-Naver-Client-Id': ID_KEY,
              'X-Naver-Client-Secret': SECRET_KEY
            }
          });

        this.setState({movies: items, isLoading: false});
      }
    } catch (error) {
      console.log(error);
    }
  };


  componentDidMount() {
    this.getSearchMovie();
  };


  handleChange = (e : any) => {
    this.setState({value: e.target.value});
  };


  handleSubmit = (e : any) => {
    e.preventDefault();
    this.getSearchMovie();
  };


  render() {
    const {movies, isLoading} = this.state;


    return (<section className="container">
      {
        isLoading
          ? (<div className="loader">
            <span className="loader__text">Loading..</span>
          </div>)
          : (<form onSubmit={this.handleSubmit}>
            <div>
              <div className="input_div">
                <h1>영화 검색</h1>
                <input className="input_search" type="text" value={this.state.value} onChange={this.handleChange} placeholder="영화를 검색해 보세요."/>
              </div>
              <div className="movies">
                {movies.map(movie => (
                	<SearchMovie key={movie.link} id={movie.link} year={movie.pubDate} title={movie.title} poster={movie.image} rating={movie.userRating} director={movie.director} actor={movie.actor}
					/>))
                 }
              </div>
            </div>
          </form>)
      }
    </section>);
  }
}


export default Search;

 

💡 전반적인 흐름

react생명주기를 보시면 render() 후에 componentDidMount()가 실행되기 때문에 nomadcorders 식 방법으로 isLoading를 만들어서 처음 render() 시에는 loading 중을 표시하고 이후 componentDidMount()에 있는 getSearchMovie() 함수를 실행하여 영화 정보를 가져와 State를 변경하면 React는 그 내용을 render() 메서드를 다시 호출하여 보여주도록 합니다 (강의를 듣다 보니 니꼴라스쌤이 이런 방식으로 많이 하십니다)

 

API를 사용하기 위해서는 axios를 설치해야 합니다 😊

 

📍 axios 설치

npm install axios

 

네이버 검색 API 가이드를 참조하여 get 방식으로 json데이터를 호출하는 부분입니다 🔎

axios는 가져오는데 시간이 오래 걸리기 때문에 async를 설정하고 누구를 기다려야 하는지 await를 설정합니다

params에 우리가 검색해야 할 영화 정보(search)를 필수로 넣어주며 검색 결과의 건수(display)는 선택 사항입니다

그다음 가장 중요한 절차인 headers에 발급받은 id, secret 값을 넣어줍니다👌👌

const {data: {
            items
          }} = await axios.get('https://openapi.naver.com/v1/search/movie.json',{
            params:{
              query: search,
              display: 20
            },
            headers: {
              'X-Naver-Client-Id': ID_KEY,
              'X-Naver-Client-Secret': SECRET_KEY
            }
          });

 

영화 정보를 State에 저장합니다 👍

this.setState({movies: items, isLoading: false});

State에 직접 변경하는 것은 좋지 않습니다 🙅‍♀️

this.state.movies = items;

 

input 이벤트인 onchange를 사용하여 검색어를 입력하면 실시간으로 검색 정보(value)를 State에 저장합니다

또한 handleSubmit은 input box에서 엔터를 입력 시 정보가 전송되는 기본 이벤트이며

해당 이벤트가 발생할 때 getSearchMovie() 함수를 실행하도록 하였습니다.

다만 해당 이벤트의 고유 기능은 사용하지 않기에 e.preventDefault()를 사용하여 막아두었습니다 😁

handleChange = (e : any) => {
    this.setState({value: e.target.value});
  };


  handleSubmit = (e : any) => {
    e.preventDefault();
    this.getSearchMovie();
  };

 

render() 메서드 안에 있는 변경된 State의 값에서 영화 정보(movies) 리스트📜를 map을 이용하여 하나씩 SearchMovie로 렌더링을 진행합니다

  render() {
    const {movies, isLoading} = this.state;


    return (<section className="container">
      {
        isLoading
          ? (<div className="loader">
            <span className="loader__text">Loading..</span>
          </div>)
          : (<form onSubmit={this.handleSubmit}>
            <div>
              <div className="input_div">
                <h1>영화 검색</h1>
                <input className="input_search" type="text" value={this.state.value} onChange={this.handleChange} placeholder="영화를 검색해 보세요."/>
              </div>
              <div className="movies">
                {movies.map(movie => (
                	<SearchMovie key={movie.link} id={movie.link} year={movie.pubDate} title={movie.title} poster={movie.image} rating={movie.userRating} director={movie.director} actor={movie.actor}
					/>))
                 }
              </div>
            </div>
          </form>)
      }
    </section>);
  }

💡 주의할 점

렌더링 시 주의해야 할 점은 key가 필요하다는 것입니다.

공식문서에 Key에 대해 자세한 설명이 있습니다

 

 

SearchMovie.js

 

Search.js에서 영화 정보를 렌더링 하여 보여주는 화면 부분입니다

 

전달받은 데이터의 유효성을 검증하기 위해 propType를 사용합니다

 

역시나 사용하기 위해서 설치를 진행해 줍니다

 

📍 prop-types 설치

npm i prop-types

 

전체 소스입니다

import React from "react";
import {Link} from 'react-router-dom';
import PropTypes from "prop-types";
import "./Movie.css";

function SearchMovie({id, year, title, poster, rating, director, actor}) {
  return (
    <div className="movie">
    <a href={id} target="_blank">
      <img src={poster} alt={title} titlt={title}></img>
    <div className="movie__data">
      <h3 className="movie__title">{
          title.replace(/<b>/gi,"").replace(/<\/b>/gi,"")
        }</h3>
      <p className="movie__rating">
        <span>평점</span> {rating}/10
      </p>
      <p className="movie__year">
        <span>개봉일</span> {year}
      </p>
    <p className="movie__director">
      <span>감독</span> {director}
    </p>
    <p className="movie__actor">
      <span>배우</span> {actor}
    </p>
    </div>
  </a>
  </div>
  )
};

SearchMovie.propTypes = {
  id: PropTypes.string.isRequired,
  year: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  rating: PropTypes.string.isRequired,
  director: PropTypes.string.isRequired,
  actor: PropTypes.string.isRequired

};

export default SearchMovie;

전달받은 데이터를 기본 문법대로 꾸며주시면 됩니다.

 

💡 주의할 점

propTypes는 반드시 propTypes로 해야 합니다

PropTypes❌

propsTypes

안됩니다

만약 다른 이름을 사용하면 아래와 같은 에러가 발생됩니다 🙅‍♀️

Warning: Component FoodLike declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?

 

 

드디어 😁😁

네이버 검색 API를 이용하여 영화 정보를 가져왔습니다

 

실행화면입니다

👏👏

 

영화 검색 메인

 

제가좋아하는 어벤져스를 검색해 보았습니다 😎

영화 검색

 

전체 소스는 Lotts github에 올렸기 때문에 참조하시면 됩니다

 

하지만 여기서 끝이 아닙니다 ❗❗

 

반전이 있지요... 😅😅

바로 위에 처럼 호출을 하면

한 번쯤 겪어 봤을 법한 교차 출처 리소스 공유( Cross-Origin Resource Sharing, CORS )를

보게 되실 겁니다

바로 아래 처럼 말이지요 💦

이거 뭐야? 안되는 걸 알려준 거야? 😤

하시겠지만

 

이것 때문에 정말 엄청 찾아보고 고생 많이 했습니다 🤣

 

자 그럼 CORS를 해결하는 방안에 대해

알아보겠습니다 🔎


📢CORS를 처리하는 방법 ONE

 

아주 간단한 방법부터 보여드리겠습니다

바로 Proxy를 설정하는 방법입니다

 

package.json

{
  "name": "movie_app_2019",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.4.0",
    "@testing-library/user-event": "^7.2.1",
    "axios": "^0.19.2",
    "cors": "^2.8.5",
    "prop-types": "^15.7.2",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-scripts": "3.3.0"
  },
  "proxy":"https://openapi.naver.com"

💡 해당 파일에서 proxy:"https://openapi.naver.com" 을 추가 줍니다

 

 

✅ Search.js

const {data: {
            items
          }} = await axios.get('/v1/search/movie.json',{
            params:{
              query: search,
              display: 20
            },
            headers: {
              'X-Naver-Client-Id': ID_KEY,
              'X-Naver-Client-Secret': SECRET_KEY
            }
          });

💡 axios 변경

https://openapi.naver.com/v1/search/movie.json  👉👉  /v1/search/movie.json 변경합니다

그러면 설정해둔 proxy로 api가 정상 호출되어 CORS의 문제없이 정보를 가져올 수 있습니다

 

 

위의 방식으로

아주 간단히 CORS 문제를 해결할 수 있습니다

👏👏👏

다만 몇 가지 이슈가 있습니다 😎

 

해당 방식은 서버로 배포나 github page 배포 시에는

CORS 문제를 해결할 수 없으며 개발환경에서만 적용됩니다 💦

또한

여러 개를 호출할 수 없고 1개만 가능합니다 😅

 

그렇다면 여려 개를 호출할 수 있는 방법은 무엇일까요?

 

바로 다음 CORS 문제를 해결하는 방법을 알려드리겠습니다 🔎


📢CORS를 처리하는 방법 TWO

 

두 번째 방법은 Proxy server를 같이 생성하여 사용하는 방법입니다 😁

 

 

📍 http-proxy-middleware 설치

npm install http-proxy-middleware --save

 

src 폴더 아래에 setupProxy.js 파일을 생성합니다

 

✅ setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app){

  app.use(
      createProxyMiddleware( '/api', {
          target: 'https://openapi.naver.com',
          changeOrigin: true,
          // 하단 처리는 필수로 해주어야 한다. 아래의 내용이 없으면 url 경로에
          // api가 추가되어 경로를 찾을 수 없어진다.
          pathRewrite:{
            '^/api/':'/'
          }
      })
  )
};

보시면 아시겠지만 https://openapi.naver.com를 /api로 설정하여

/api를 proxy로 설정하여 호출하는 방식입니다

 

💡 axios 변경

 https://openapi.naver.com/v1/search/movie.json  👉👉  /api/v1/search/movie.json 변경하여 작성해주시면 됩니다.

const {data: {
            items
          }} = await axios.get('/api/v1/search/movie.json',{
            params:{
              query: search,
              display: 20
            },
            headers: {
              'X-Naver-Client-Id': ID_KEY,
              'X-Naver-Client-Secret': SECRET_KEY
            }
          });

 

두 번째 방식의 장점은

여러 개의 proxy를 설정할 수 있다는 점입니다

👏👏👏

다만, 이 방법도 역시 서버에서는 불가능한 것으로 보입니다😅

제가 git pages나 별도의 서버에서 nginx에서 올려봤으나 되지 않았습니다.

 

😉 만약 되시는 분이 계시면 알려주세요  🙏

 


🎈 자 드디어 마무리입니다 

 

이번 포스팅에서는 

✅ 네이버 검색 API를 이용하여 영화 정보 가져오기

✅ CORS를 처리하는 방법 One

✅ CORS를 처리하는 방법 Two

를 알아보았습니다

 

전반적인 소스는 LottsGithub에서 참조하시면 됩니다 😁

 

다음 포스팅은 서버에서 올려도 CORS를 요리조리 피해 

해결할 수 있는 방법에 대해 설명하도록 하겠습니다

(생각보다 쉬울 수 있어요)

 

👏👏👏👏👏👏

 

 

 

 

반응형