<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>effortDev</title>
    <link>https://shlee0882.tistory.com/</link>
    <description>step by step</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 15:06:51 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>effortDev</managingEditor>
    <image>
      <title>effortDev</title>
      <url>https://t1.daumcdn.net/cfile/tistory/2240D23F5919C5431F</url>
      <link>https://shlee0882.tistory.com</link>
    </image>
    <item>
      <title>Reactjs, Tailwindcss 토이 퀴즈 앱 만들기</title>
      <link>https://shlee0882.tistory.com/298</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당&amp;nbsp;토이&amp;nbsp;프로젝트는&amp;nbsp;아래와&amp;nbsp;같은&amp;nbsp;기술을&amp;nbsp;사용할&amp;nbsp;것이다. &lt;br /&gt;&lt;br /&gt;-&amp;nbsp;Reactjs,&amp;nbsp;Typescript,&amp;nbsp;Tailwindcss &lt;br /&gt;-&amp;nbsp;Opentdb&amp;nbsp;API(퀴즈&amp;nbsp;API) &lt;br /&gt;-&amp;nbsp;Google&amp;nbsp;Cloud&amp;nbsp;Translation&amp;nbsp;API &lt;br /&gt;&lt;br /&gt;먼저&amp;nbsp;프로젝트를&amp;nbsp;구성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 아래 명령어를 입력한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#fafafa;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #e5e5e5&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#666;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;4&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;리액트&amp;nbsp;앱&amp;nbsp;설치&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npx&amp;nbsp;create&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;react&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;app&amp;nbsp;react&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;ts&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;quiz&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;app&amp;nbsp;&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;template&amp;nbsp;typescript&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;cd&amp;nbsp;react&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;ts&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;quiz&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;app&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;start&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align:right;margin-top:-13px;margin-right:5px;font-size:9px;font-style:italic&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;color:#e5e5e5text-decoration:none&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#e5e5e5;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 서버가 정상적으로 뜨는지 확인한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;679&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/szomJ/btspCmL9PdL/qV1UJBHigXgi9KFWpsUXUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/szomJ/btspCmL9PdL/qV1UJBHigXgi9KFWpsUXUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/szomJ/btspCmL9PdL/qV1UJBHigXgi9KFWpsUXUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FszomJ%2FbtspCmL9PdL%2FqV1UJBHigXgi9KFWpsUXUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;385&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;679&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. tailwind css를 적용해보자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://tailwindcss.com/docs/guides/create-react-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tailwindcss.com/docs/guides/create-react-app&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690950489291&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Install Tailwind CSS with Create React App - Tailwind CSS&quot; data-og-description=&quot;Setting up Tailwind CSS in a Create React App project.&quot; data-og-host=&quot;tailwindcss.com&quot; data-og-source-url=&quot;https://tailwindcss.com/docs/guides/create-react-app&quot; data-og-url=&quot;https://tailwindcss.com/docs/guides/create-react-app&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uXymV/hyTwcydF05/sd25yhpfIdPTj7LryBVac1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bhZgpy/hyTvcs32fz/rO7MVuZA033pY177jpvEqK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/Ym2yl/hyTwmHAlhf/LQdhD5FHMJLmK5KkYncDf1/img.png?width=2880&amp;amp;height=1232&amp;amp;face=0_0_2880_1232&quot;&gt;&lt;a href=&quot;https://tailwindcss.com/docs/guides/create-react-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tailwindcss.com/docs/guides/create-react-app&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uXymV/hyTwcydF05/sd25yhpfIdPTj7LryBVac1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bhZgpy/hyTvcs32fz/rO7MVuZA033pY177jpvEqK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/Ym2yl/hyTwmHAlhf/LQdhD5FHMJLmK5KkYncDf1/img.png?width=2880&amp;amp;height=1232&amp;amp;face=0_0_2880_1232');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Install Tailwind CSS with Create React App - Tailwind CSS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Setting up Tailwind CSS in a Create React App project.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tailwindcss.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설명대로 따라하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#fafafa;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #e5e5e5&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#666;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;tailwindcss&amp;nbsp;설치&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;install&amp;nbsp;&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;D&amp;nbsp;tailwindcss&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npx&amp;nbsp;tailwindcss&amp;nbsp;init&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#e5e5e5;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제대로 설치 되었다면 아래와 같이 css가 적용된 화면이 나타날것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7MugC/btspOCs6FzI/UGDZcHdVOS7j53YqvfGP0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7MugC/btspOCs6FzI/UGDZcHdVOS7j53YqvfGP0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7MugC/btspOCs6FzI/UGDZcHdVOS7j53YqvfGP0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7MugC%2FbtspOCs6FzI%2FUGDZcHdVOS7j53YqvfGP0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;230&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 퀴즈 API 데이터를 가져오자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 데이터는 오픈 API인 Open Trivia DB 에서 가져올 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://opentdb.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://opentdb.com/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690950875435&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Open Trivia DB&quot; data-og-description=&quot;Free to use, user-contributed trivia questions!&quot; data-og-host=&quot;opentdb.com&quot; data-og-source-url=&quot;https://opentdb.com/&quot; data-og-url=&quot;https://opentdb.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bwpY7B/hyTwr9ZcJt/HHCO6jPz2OG3TDbN4Z6lSk/img.png?width=816&amp;amp;height=376&amp;amp;face=0_0_816_376&quot;&gt;&lt;a href=&quot;https://opentdb.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://opentdb.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bwpY7B/hyTwr9ZcJt/HHCO6jPz2OG3TDbN4Z6lSk/img.png?width=816&amp;amp;height=376&amp;amp;face=0_0_816_376');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Open Trivia DB&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Free to use, user-contributed trivia questions!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;opentdb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 카테고리, 타입, 난이도, 정답, 오답 등의 json 데이터를 확인가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://opentdb.com/api.php?amount=10&quot;&gt;https://opentdb.com/api.php?amount=10&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SpKal/btspOAvmA0Z/skOcY6LVka3gdKVIapfDJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SpKal/btspOAvmA0Z/skOcY6LVka3gdKVIapfDJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SpKal/btspOAvmA0Z/skOcY6LVka3gdKVIapfDJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSpKal%2FbtspOAvmA0Z%2FskOcY6LVka3gdKVIapfDJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;607&quot; height=&quot;824&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문제가 생겼다. json 데이터가 모두 영어 데이터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어 json 데이터 를 한글로 번역하면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 번역 API를 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 구글 클라우드 콘솔에 접속한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;804&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CX8Hd/btspRhPPUFz/HGyddIjYG8Rmly4ATHorS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CX8Hd/btspRhPPUFz/HGyddIjYG8Rmly4ATHorS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CX8Hd/btspRhPPUFz/HGyddIjYG8Rmly4ATHorS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCX8Hd%2FbtspRhPPUFz%2FHGyddIjYG8Rmly4ATHorS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;773&quot; height=&quot;804&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;804&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 하나 생성 후 Cloud Translation API 를 사용설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인증 정보 &amp;gt; 사용자 인증 정보 만들기 &amp;gt; API 키 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqxQcY/btspNQ6jT3G/pJmSRTTwlEjITdmODMxqJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqxQcY/btspNQ6jT3G/pJmSRTTwlEjITdmODMxqJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqxQcY/btspNQ6jT3G/pJmSRTTwlEjITdmODMxqJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqxQcY%2FbtspNQ6jT3G%2FpJmSRTTwlEjITdmODMxqJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1456&quot; height=&quot;438&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API Key 값을 복사해서 갖고 있는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 키 수정을 통해 호출 제한 사항을 적절하게 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj82Wh/btspCmyLu3I/kPUBcX4zTPvL0dwVtzKLEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj82Wh/btspCmyLu3I/kPUBcX4zTPvL0dwVtzKLEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj82Wh/btspCmyLu3I/kPUBcX4zTPvL0dwVtzKLEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj82Wh%2FbtspCmyLu3I%2FkPUBcX4zTPvL0dwVtzKLEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;696&quot; height=&quot;858&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 구글 번역 API를 사용해 데이터를 번역해서 가져오자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#fafafa;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #e5e5e5&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#666;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;tailwindcss&amp;nbsp;설치&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;i&amp;nbsp;@google&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;cloud&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;/&lt;/span&gt;translate&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#e5e5e5;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6.1 먼저 퀴즈 객체 생성 후 데이터를 가져오자.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690956465386&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface Quiz {
    category: string;
    type: string;
    difficulty: string;
    question: string;
    correct_answer: string;
    incorrect_answers: string[];
    all_answers: string[];
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1690952016724&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const getQuizData = async() =&amp;gt; {
    let problemCnt = '1'
    let url = `https://opentdb.com/api.php?amount=${problemCnt}&amp;amp;difficulty=easy&amp;amp;type=multiple`
    let response = await fetch(url, {
      method: 'GET',
    }); 
    let data = await response.json()
    return data;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6.2 가져온 퀴즈 데이터를 인자로 받아 구글 번역 API를 불러 번역시킨다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690952088144&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const translate = async (quizzes: Quiz[]) =&amp;gt; {
    let url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`

    multiplePreTranslate = '';
    let preTranslate = '';

    for(let quizObj of quizzes){
      let icaListStr = '';
      for (let ica of quizObj.incorrect_answers) {
        if(ica === quizObj.incorrect_answers[quizObj.incorrect_answers.length - 1]) {
          icaListStr += `${ica}`;
        }else{
          icaListStr += `${ica}\n`;
        }
      }
      if(quizObj === quizzes[quizzes.length - 1]) {
        preTranslate += `${quizObj.category}\n${quizObj.difficulty}\n${quizObj.question}\n${quizObj.correct_answer}\n${icaListStr}`;      
      }else{
        preTranslate += `${quizObj.category}\n${quizObj.difficulty}\n${quizObj.question}\n${quizObj.correct_answer}\n${icaListStr}\n\n`;      
      }
    }

    multiplePreTranslate = dataStrReplace(preTranslate);

    let response = await fetch(url, {
      method: 'POST',
      headers: { 
        'Content-Type': 'application/json',
       },
       body: JSON.stringify({
         target: 'ko',
         format: &quot;text&quot;,
         q: multiplePreTranslate
       }),
    }); 
    let data = await response.json()
    return data;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원활한 번역을 위해 문자열과 구분은 한줄내림(\n) 으로 구분해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈데이터를 받아와 구글 번역 API를 통해 아래와 같이 데이터가 적절하게 번역된것을 확인할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690953248461&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 번역 전 퀴즈 데이터
`
Entertainment: Musicals &amp;amp; Theatres
medium
When was the play &quot;Macbeth&quot; written?
1606
1605
1723
1628

Entertainment: Musicals &amp;amp; Theatres
easy
Which Shakespeare play inspired the musical 'West Side Story'?
Romeo  Juliet
Hamlet
Macbeth
Othello
`;

// 번역 후 퀴즈 데이터
`
엔터테인먼트: 뮤지컬 및 극장
중간
연극 &quot;맥베스&quot;는 언제 쓰여졌습니까?
1606년
1605년
1723년
1628년

엔터테인먼트: 뮤지컬 및 극장
쉬움
어떤 셰익스피어 연극이 뮤지컬 '웨스트 사이드 스토리'에 영감을 주었습니까?
로미오  줄리엣
작은 촌락
맥베스
오셀로
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. 화면을 꾸며보자.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1690954911366&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useLayoutEffect, useState } from 'react';
import './App.css'; 
import { Quiz } from './quiz.interface';

function App() {
  const [translateQuizzArr, setTranslateQuizzArr] = useState&amp;lt;Quiz[]&amp;gt;([]);
  const [currentQuizIndex, setCurrentQuizIndex] = useState&amp;lt;number&amp;gt;(0);
  const [score, setScore] = useState&amp;lt;number&amp;gt;(0);
  const [selectedAnswer, setSelectedAnswer] = useState&amp;lt;string | null&amp;gt;(null);
  const [showAnswer, setShowAnswer] = useState&amp;lt;boolean&amp;gt;(false);
  const [hasIncorrectAttempt, setHasIncorrectAttempt] = useState(false);
  const [isCorrect, setIsCorrect] = useState(false);
  const [quizTrigger, setQuizTrigger] = useState(false);
  const [isLoading, setIsLoading] = useState(true);

  const { REACT_APP_GOOGLE_TRANSLATE_API_KEY } = process.env;
  const apiKey: string | undefined = REACT_APP_GOOGLE_TRANSLATE_API_KEY;
  if (!apiKey) {
    throw new Error('Missing Google API Key');
  }
  let multiplePreTranslate = '';

  useLayoutEffect(() =&amp;gt; {
    getQuizData().then(response =&amp;gt; {

      console.log(response.data);
      let quizzes: Quiz[] = response.results;
      let quizArr: Quiz[] = [];
  
      const translatePromises = translate(quizzes)
        .then(response =&amp;gt; {
          let translatedData = response?.data?.translations[0]?.translatedText;
          let multipleArrPreTranslate = translatedData.split('\n\n');
          let multipleEngPreTranslate = multiplePreTranslate.split('\n\n');
          for(let i=0; i &amp;lt; multipleArrPreTranslate.length; i++){
            let data = multipleArrPreTranslate[i];
            let engData = multipleEngPreTranslate[i];

            let arrPreTranslate = data.split('\n');
            let engArrPreTranslate = engData.split('\n');
            let engRightAnswerStr = dataStrReplace(engArrPreTranslate[3]);
            let engWrongAnswerStr1 = dataStrReplace(engArrPreTranslate[4]);
            let engWrongAnswerStr2 = dataStrReplace(engArrPreTranslate[5]);
            let engWrongAnswerStr3 = dataStrReplace(engArrPreTranslate[6]);

            let incorrectStrArr: string[] = [];
            let questionStr = `${dataStrReplace(arrPreTranslate[2])}`;
            let rightAnswerStr = `${dataStrReplace(arrPreTranslate[3])} ( ${engRightAnswerStr} )`; 
            let wrongAnswerStr1 = `${dataStrReplace(arrPreTranslate[4])} ( ${engWrongAnswerStr1} )`;
            let wrongAnswerStr2 = `${dataStrReplace(arrPreTranslate[5])} ( ${engWrongAnswerStr2} )`;
            let wrongAnswerStr3 = `${dataStrReplace(arrPreTranslate[6])} ( ${engWrongAnswerStr3} )`;

            incorrectStrArr.push(wrongAnswerStr1, wrongAnswerStr2, wrongAnswerStr3);
            let allAnswersStrArr: string[] = [];
            allAnswersStrArr.push(...incorrectStrArr, rightAnswerStr);
            shuffleArray(allAnswersStrArr);
  
            let translateQuizObj: Quiz = {
              category: arrPreTranslate[0],
              difficulty: arrPreTranslate[1], 
              question: questionStr,
              correct_answer: rightAnswerStr,
              incorrect_answers: incorrectStrArr,
              all_answers: allAnswersStrArr,
              type: ''
            };
            quizArr.push(translateQuizObj);
          }
        })
        .catch(reason =&amp;gt; {
          console.log(reason.message);
          return {
            category: '',
            difficulty: '',
            question: '',
            correct_answer: '',
            incorrect_answers: [''],
            type: ''
          } as Quiz;
        });
  
      Promise.all([translatePromises]).then(() =&amp;gt; {
        setTranslateQuizzArr(quizArr);
        setIsLoading(false);
      });
  
    });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [quizTrigger]);
  
  const handleSolveMore = () =&amp;gt; {
    let quizArr: Quiz[] = [];
    setTranslateQuizzArr(quizArr);
    setIsLoading(true);
    setQuizTrigger(!quizTrigger); 
    setScore(0);
    setCurrentQuizIndex(0);
  };
  
  const handleAnswer = (answer: string) =&amp;gt; {
    setSelectedAnswer(answer);
    const isAnswerCorrect = answer === translateQuizzArr[currentQuizIndex].correct_answer;
  
    if (isAnswerCorrect) {
      setIsCorrect(true);
      if (!hasIncorrectAttempt) {
        setScore(score + 1);
      }
      setShowAnswer(true);
  
      setTimeout(() =&amp;gt; {
        setIsCorrect(false);
        setShowAnswer(false);
        setHasIncorrectAttempt(false);
        setCurrentQuizIndex(currentQuizIndex + 1);
      }, 800);  
    } else {
      setShowAnswer(true);
      setHasIncorrectAttempt(true);
    }
  };
  


  function dataStrReplace(arrPreTranslate: any) {
    return arrPreTranslate.replace(/&amp;amp;quot;/g, '&quot;')
                     .replace(/&amp;amp;#039;/g, &quot;'&quot;)
                     .replace(/&amp;amp;ldquo;/g, '&quot;')
                     .replace(/&amp;amp;rdquo;/g, '&quot;');
  }

  function shuffleArray(array: any[]) {
    for (let i = array.length - 1; i &amp;gt; 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [array[i], array[j]] = [array[j], array[i]];
    }
  }
  
  async function getQuizData() {
    let problemCnt = '3'
    let url = `https://opentdb.com/api.php?amount=${problemCnt}&amp;amp;difficulty=easy&amp;amp;type=multiple`
    let response = await fetch(url, {
      method: 'GET',
    }); 
    let data = await response.json()
    return data;
  }

  async function translate(quizzes: Quiz[]) {
    let url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`

    multiplePreTranslate = '';
    let preTranslate = '';

    for(let quizObj of quizzes){
      let icaListStr = '';
      for (let ica of quizObj.incorrect_answers) {
        if(ica === quizObj.incorrect_answers[quizObj.incorrect_answers.length - 1]) {
          icaListStr += `${ica}`;
        }else{
          icaListStr += `${ica}\n`;
        }
      }
      if(quizObj === quizzes[quizzes.length - 1]) {
        preTranslate += `${quizObj.category}\n${quizObj.difficulty}\n${quizObj.question}\n${quizObj.correct_answer}\n${icaListStr}`;      
      }else{
        preTranslate += `${quizObj.category}\n${quizObj.difficulty}\n${quizObj.question}\n${quizObj.correct_answer}\n${icaListStr}\n\n`;      
      }
    }

    multiplePreTranslate = dataStrReplace(preTranslate);

    let response = await fetch(url, {
      method: 'POST',
      headers: { 
        'Content-Type': 'application/json',
       },
       body: JSON.stringify({
         target: 'ko',
         format: &quot;text&quot;,
         q: multiplePreTranslate
       }),
    }); 
    let data = await response.json()
    return data;
  }

  return (
    &amp;lt;div className=&quot;App bg-blue-50 min-h-screen flex flex-col items-center justify-center font-nanum-gothic font-bold&quot;&amp;gt;
      &amp;lt;h1 className=&quot;text-4xl mb-4 font-bold&quot;&amp;gt;랜덤퀴즈 앱&amp;lt;/h1&amp;gt;
      {
        isLoading ? 
        &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt; : 
        (
          &amp;lt;h2 className=&quot;text-2xl mb-8&quot;&amp;gt;점수 : {score}&amp;lt;/h2&amp;gt;
        )
      }
      {translateQuizzArr.length &amp;gt; 0 &amp;amp;&amp;amp; currentQuizIndex &amp;lt;= translateQuizzArr.length - 1 ? (
        &amp;lt;div className=&quot;w-full bg-white p-8 rounded shadow flex flex-col&quot;&amp;gt;
          &amp;lt;h2 className=&quot;text-xl mb-4&quot;&amp;gt;{translateQuizzArr[currentQuizIndex].question}&amp;lt;/h2&amp;gt;

          {translateQuizzArr[currentQuizIndex].all_answers.map((answer, index) =&amp;gt; (

          &amp;lt;button
            key={index}
            onClick={() =&amp;gt; handleAnswer(answer)}
            className={`my-2 p-4 text-white 
              ${showAnswer &amp;amp;&amp;amp; answer === translateQuizzArr[currentQuizIndex].correct_answer 
                ? 'bg-green-500 text-white animate-pulse'  
                : showAnswer &amp;amp;&amp;amp; answer === selectedAnswer
                ? 'bg-red-500 text-white'
                : 'bg-sky-500/100 text-white'}`}
          &amp;gt;
            {answer}
          &amp;lt;/button&amp;gt;

          ))}

          {isCorrect &amp;amp;&amp;amp; (
            &amp;lt;div className=&quot;bg-green-500 text-white p-4 mt-4 rounded&quot;&amp;gt;
              정답입니다. 다음 문제로 이동 중...
            &amp;lt;/div&amp;gt;
          )}
            
          {showAnswer &amp;amp;&amp;amp; !isCorrect &amp;amp;&amp;amp; (
            &amp;lt;div className=&quot;text-red-500 p-4 mt-4 rounded&quot;&amp;gt;
              정답은 : {translateQuizzArr[currentQuizIndex].correct_answer} 입니다.
            &amp;lt;/div&amp;gt;
          )}
        &amp;lt;/div&amp;gt;
      ) : isLoading ? &amp;lt;div&amp;gt;      
        &amp;lt;h2 className=&quot;text-2xl&quot;&amp;gt;랜덤 퀴즈 로딩 중입니다.&amp;lt;/h2&amp;gt;
      &amp;lt;/div&amp;gt; : 
      (
        &amp;lt;&amp;gt;
        &amp;lt;h2 className=&quot;text-2xl&quot;&amp;gt;퀴즈가 끝났습니다. &amp;lt;br/&amp;gt; 최종 점수는 {score} 점 입니다.&amp;lt;/h2&amp;gt;
        &amp;lt;button className=&quot;bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-4&quot; onClick={handleSolveMore}&amp;gt;
          퀴즈 더 풀기
        &amp;lt;/button&amp;gt;
      &amp;lt;/&amp;gt;
      )
    }
    &amp;lt;/div&amp;gt;
  );

}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.css&lt;/p&gt;
&lt;pre id=&quot;code_1690955023583&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* App.css */
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import url('https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800;900&amp;amp;display=swap');

body {
  @apply font-sans m-0 p-0 bg-gray-200;
}

.App {
  @apply max-w-md mx-auto p-5 bg-white rounded-lg shadow-md;
}

h1 {
  @apply text-blue-600 text-center;
}

.input-container {
  @apply bg-blue-100 p-2 rounded mb-4;
  @apply w-full mx-2 box-border;
}

input {
  @apply w-full py-2 px-3 border-none outline-none text-lg rounded bg-blue-200 box-border;
}

.translated-text {
  @apply text-lg leading-loose;
}

@media screen and (max-width: 600px) {
  .App {
    @apply p-2;
  }
}

@media screen and (max-width: 400px) {
  .App {
    @apply p-1;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7.1 결과 화면&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;945&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bciw8H/btspUgJMb8K/umkmq4eNUeQGkU5jL2yirk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bciw8H/btspUgJMb8K/umkmq4eNUeQGkU5jL2yirk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bciw8H/btspUgJMb8K/umkmq4eNUeQGkU5jL2yirk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbciw8H%2FbtspUgJMb8K%2Fumkmq4eNUeQGkU5jL2yirk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;947&quot; height=&quot;945&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;945&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 퀴즈는 한 사이클당 30문제로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 퀴즈를 푸는 사용자는 사지선다로 문제를 풀수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최초 정답 선택 시 점수가 올라간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최초 틀린 문제를 선택 시 점수가 올라가지 않고 정답을 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정답을 선택 시 다음 문제로 진입할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 퀴즈 주제 바꾸기로 퀴즈 카테고리를 바꿀수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8. 문제가 생겼다. 구글 클라우드 번역 API는 공짜가 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루에 요청가능한 글자수와 건수가 정해져있다.&amp;nbsp; ( 프로젝트가 결제설정 되어있다면 사용한 만큼 지불된다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://cloud.google.com/translate/pricing?hl=ko&quot;&gt;https://cloud.google.com/translate/pricing?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690953455759&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;가격 책정 &amp;nbsp;|&amp;nbsp; Cloud Translation &amp;nbsp;|&amp;nbsp; Google Cloud&quot; data-og-description=&quot;Cloud Translation 가격 책정 검토&quot; data-og-host=&quot;cloud.google.com&quot; data-og-source-url=&quot;https://cloud.google.com/translate/pricing?hl=ko&quot; data-og-url=&quot;https://cloud.google.com/translate/pricing?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdkZ8E/hyTwirHxFX/yokAaUQJLazat8ZvMliiLK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://cloud.google.com/translate/pricing?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cloud.google.com/translate/pricing?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdkZ8E/hyTwirHxFX/yokAaUQJLazat8ZvMliiLK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;가격 책정 &amp;nbsp;|&amp;nbsp; Cloud Translation &amp;nbsp;|&amp;nbsp; Google Cloud&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Cloud Translation 가격 책정 검토&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cloud.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://cloud.google.com/billing/docs/how-to/modify-project?hl=ko#how-to-disable-billing&quot;&gt;https://cloud.google.com/billing/docs/how-to/modify-project?hl=ko#how-to-disable-billing&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690953963288&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로젝트의 결제 사용 설정, 사용 중지, 변경 &amp;nbsp;|&amp;nbsp; Cloud Billing &amp;nbsp;|&amp;nbsp; Google Cloud&quot; data-og-description=&quot;의견 보내기 프로젝트의 결제 사용 설정, 사용 중지, 변경 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 페이지에서는 각 Google Cloud 프로젝트와 Google Maps&quot; data-og-host=&quot;cloud.google.com&quot; data-og-source-url=&quot;https://cloud.google.com/billing/docs/how-to/modify-project?hl=ko#how-to-disable-billing&quot; data-og-url=&quot;https://cloud.google.com/billing/docs/how-to/modify-project?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LpgA4/hyTwjxnxLY/owQxsEtokjjpo0k3kiJrQK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://cloud.google.com/billing/docs/how-to/modify-project?hl=ko#how-to-disable-billing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cloud.google.com/billing/docs/how-to/modify-project?hl=ko#how-to-disable-billing&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LpgA4/hyTwjxnxLY/owQxsEtokjjpo0k3kiJrQK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트의 결제 사용 설정, 사용 중지, 변경 &amp;nbsp;|&amp;nbsp; Cloud Billing &amp;nbsp;|&amp;nbsp; Google Cloud&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;의견 보내기 프로젝트의 결제 사용 설정, 사용 중지, 변경 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 페이지에서는 각 Google Cloud 프로젝트와 Google Maps&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cloud.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RB6lQ/btspCl7K5fK/rGoSo2DAMgHhkvOtEdtm2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RB6lQ/btspCl7K5fK/rGoSo2DAMgHhkvOtEdtm2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RB6lQ/btspCl7K5fK/rGoSo2DAMgHhkvOtEdtm2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRB6lQ%2FbtspCl7K5fK%2FrGoSo2DAMgHhkvOtEdtm2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1308&quot; height=&quot;275&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용량을 초과하면 아래와 같이 에러가 발생될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m0CLH/btspFj2R9tq/HuMX48CKmWo0BofKZsEw30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m0CLH/btspFj2R9tq/HuMX48CKmWo0BofKZsEw30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m0CLH/btspFj2R9tq/HuMX48CKmWo0BofKZsEw30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm0CLH%2FbtspFj2R9tq%2FHuMX48CKmWo0BofKZsEw30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1554&quot; height=&quot;454&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User Rate Limit Exeeded !!!! (허허 ... 흙발자 무시하는겁니가... ㅠㅠ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 데이터는 카테고리가 약&amp;nbsp; 24개이고 40문제씩 호출할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 총 문제수만 8~900 개 일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 오픈 API도 분명 데이터가 추가되거나 업데이트 될텐데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 매번 앱 실행 시 구글 번역 API를 실행해야한다? &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 번역 API 트래픽 다량 발생가능성 높음. 얼마나 번역할것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;새로운 퀴즈 데이터에 대한&lt;span&gt; 업데이트나 추가 갱신이 빠름 &lt;/span&gt;&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 처음 불러올때 데이터를 조금만 불러온다 ? &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 퀴즈데이터 번역하는 시간 또한 고려해야하니 유저입장에서 다음화면으로 가기전까지 피곤할것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역 API 트래픽은 줄었지만 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 대량의 데이터를 한번에 번역해서 파일형태의 static으로 갖고있는다?&amp;nbsp; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 퀴즈 데이터 최신화 업데이트에 대한 문제&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 비용이 발생하면 부담이 발생될것 같아 3번으로 고치겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8.1 퀴즈 데이터 static 파일 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;quizData.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1690956399905&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const ENG_QUIZ_CATE9 = 
`
General Knowledge
easy
What is the official language of Brazil?
Portugese
Brazilian
Spanish
English
`;

export const KOR_QUIZ_CATE9 = 
`
일반 지식
쉬움
브라질의 공식 언어는 무엇입니까?
포르투갈어
브라질
스페인의
영어
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어 원문 데이터, 한글 번역된 데이터 2개를 갖고 static 데이터를 불러와 활용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1690956574737&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const getStaticQuizData = () =&amp;gt; {
    let quizArr: Quiz[] = [];
    let quizCategory = [
        {&quot;eng&quot;: ENG_QUIZ_CATE9, &quot;kor&quot; : KOR_QUIZ_CATE9}
      , {&quot;eng&quot;: ENG_QUIZ_CATE10, &quot;kor&quot;: KOR_QUIZ_CATE10}  
    ]

    shuffleArray(quizCategory);

    for(let data=0; data &amp;lt; quizCategory.length; data++){
      let quizCate = quizCategory[data];
      let multipleArrPreTranslate = quizCate.kor.split('\n\n');
      let multipleEngPreTranslate = quizCate.eng.split('\n\n');

      for(let i=0; i &amp;lt; multipleArrPreTranslate.length; i++){
        let data = multipleArrPreTranslate[i];
        let engData = multipleEngPreTranslate[i];
  
        let arrPreTranslate = data.split('\n');
        let engArrPreTranslate = engData.split('\n');
        let engRightAnswerStr = dataStrReplace(engArrPreTranslate[3]);
        let engWrongAnswerStr1 = dataStrReplace(engArrPreTranslate[4]);
        let engWrongAnswerStr2 = dataStrReplace(engArrPreTranslate[5]);
        let engWrongAnswerStr3 = dataStrReplace(engArrPreTranslate[6]);
  
        let incorrectStrArr: string[] = [];
        let questionStr = `${dataStrReplace(arrPreTranslate[2])}`;
        let rightAnswerStr = `${dataStrReplace(arrPreTranslate[3])} ( ${engRightAnswerStr} )`; 
        let wrongAnswerStr1 = `${dataStrReplace(arrPreTranslate[4])} ( ${engWrongAnswerStr1} )`;
        let wrongAnswerStr2 = `${dataStrReplace(arrPreTranslate[5])} ( ${engWrongAnswerStr2} )`;
        let wrongAnswerStr3 = `${dataStrReplace(arrPreTranslate[6])} ( ${engWrongAnswerStr3} )`;
  
        incorrectStrArr.push(wrongAnswerStr1, wrongAnswerStr2, wrongAnswerStr3);
        let allAnswersStrArr: string[] = [];
        allAnswersStrArr.push(...incorrectStrArr, rightAnswerStr);
        shuffleArray(allAnswersStrArr);
  
        let translateQuizObj: Quiz = {
          category: arrPreTranslate[0],
          difficulty: arrPreTranslate[1], 
          question: questionStr,
          correct_answer: rightAnswerStr,
          incorrect_answers: incorrectStrArr,
          all_answers: allAnswersStrArr,
          type: ''
        };
        quizArr.push(translateQuizObj);
      }
    }
    setTranslateQuizzArr(quizArr);
    setIsLoading(false);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 API를 활용하지 않았으니 속도는 빨라졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 데이터에 대한 업데이트는 주기적으로 API를 호출하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 데이터와 비교하여 변경사항에 대한 정보를 받아올지 생각해봐야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 배포된 url과 github 주소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://shlee0882.github.io/react-ts-quiz-app/&quot;&gt;https://shlee0882.github.io/react-ts-quiz-app/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690957054625&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;랜덤퀴즈 앱&quot; data-og-description=&quot;&quot; data-og-host=&quot;shlee0882.github.io&quot; data-og-source-url=&quot;https://shlee0882.github.io/react-ts-quiz-app/&quot; data-og-url=&quot;https://shlee0882.github.io/react-ts-quiz-app/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://shlee0882.github.io/react-ts-quiz-app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://shlee0882.github.io/react-ts-quiz-app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;랜덤퀴즈 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;shlee0882.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://github.com/shlee0882/react-ts-quiz-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/shlee0882/react-ts-quiz-app&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690957083325&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - shlee0882/react-ts-quiz-app: :question: ReactJs, Ts, Tailwindcss 랜덤 퀴즈 앱 토이 프로젝트&quot; data-og-description=&quot;:question: ReactJs, Ts, Tailwindcss 랜덤 퀴즈 앱 토이 프로젝트 - GitHub - shlee0882/react-ts-quiz-app: :question: ReactJs, Ts, Tailwindcss 랜덤 퀴즈 앱 토이 프로젝트&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/shlee0882/react-ts-quiz-app&quot; data-og-url=&quot;https://github.com/shlee0882/react-ts-quiz-app&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dn502z/hyTvfDopPn/jTkufC37MtbkPl2qQZ3IG0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/shlee0882/react-ts-quiz-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/shlee0882/react-ts-quiz-app&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dn502z/hyTvfDopPn/jTkufC37MtbkPl2qQZ3IG0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - shlee0882/react-ts-quiz-app: :question: ReactJs, Ts, Tailwindcss 랜덤 퀴즈 앱 토이 프로젝트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;:question: ReactJs, Ts, Tailwindcss 랜덤 퀴즈 앱 토이 프로젝트 - GitHub - shlee0882/react-ts-quiz-app: :question: ReactJs, Ts, Tailwindcss 랜덤 퀴즈 앱 토이 프로젝트&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-ts-quiz-app의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dev 브랜치에 퀴즈 데이터가 static으로 변경된 소스가 올라가있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;master 브랜치에는 구글 번역 api가 적용된 소스가 올라가있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://github1s.com/shlee0882/react-ts-quiz-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github1s.com/shlee0882/react-ts-quiz-app&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690957140502&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;GitHub1s&quot; data-og-description=&quot;&quot; data-og-host=&quot;github1s.com&quot; data-og-source-url=&quot;https://github1s.com/shlee0882/react-ts-quiz-app&quot; data-og-url=&quot;https://github1s.com/shlee0882/react-ts-quiz-app&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github1s.com/shlee0882/react-ts-quiz-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github1s.com/shlee0882/react-ts-quiz-app&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub1s&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github1s.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상으로 포스팅을 마치겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>전체/개인 프로젝트</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/298</guid>
      <comments>https://shlee0882.tistory.com/298#entry298comment</comments>
      <pubDate>Wed, 2 Aug 2023 15:22:20 +0900</pubDate>
    </item>
    <item>
      <title>NestJs 토이 프로젝트 구성해보기 - 2</title>
      <link>https://shlee0882.tistory.com/297</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Swagger 설정, Google Cloud MySQL 설정을 완료했으니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 구글 로그인 사용을 위해 아래 노드 패키지를 설치해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#fafafa;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #e5e5e5&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#666;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;passport&amp;nbsp;설치&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;install&amp;nbsp;@nestjs&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;/&lt;/span&gt;passport&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;install&amp;nbsp;passport&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;google&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;oauth20&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#e5e5e5;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. src 하위에 auth 폴더를 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1803&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NpNfU/btsnFfuuCai/VYYH0FJkkclmuOS6sZfxN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NpNfU/btsnFfuuCai/VYYH0FJkkclmuOS6sZfxN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NpNfU/btsnFfuuCai/VYYH0FJkkclmuOS6sZfxN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNpNfU%2FbtsnFfuuCai%2FVYYH0FJkkclmuOS6sZfxN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1803&quot; height=&quot;698&quot; data-origin-width=&quot;1803&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;auth 폴더 하위에 cotroller, service, module 을 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.1. auth.contoller.ts 생성&lt;/p&gt;
&lt;pre id=&quot;code_1689499887918&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// auth.controller.ts
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';

@ApiTags('auth API')
@Controller('auth')
export class AuthController {

  @ApiOperation({ summary: 'google login auth' })
  @ApiResponse({ status: 200, description: 'google login auth' })
  @Get('google')
  @UseGuards(AuthGuard('google'))
  googleLogin() {
  }

  @ApiOperation({ summary: 'google login auth callback' })
  @ApiResponse({ status: 200, description: 'google login auth callback' })
  @Get('google/callback')
  @UseGuards(AuthGuard('google'))
  googleLoginCallback(@Req() req) {
    return {
      user: req.user,
    };
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.2. auth.service.ts 생성&lt;/p&gt;
&lt;pre id=&quot;code_1689499915458&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// auth.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AuthService {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.3. auth.module.ts 생성&lt;/p&gt;
&lt;pre id=&quot;code_1689499953583&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'src/user/user.entity';
import { GoogleStrategy } from 'src/strategies/google.strategy';
import { AuthController } from './auth.contoller';
@Module({
  // eslint-disable-next-line prettier/prettier
  imports: [
    PassportModule.register({ defaultStrategy: 'google' }),
    TypeOrmModule.forFeature([User]),
  ],
  providers: [AuthService, GoogleStrategy],
  controllers: [AuthController],
})
export class AuthModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4. Passport의 Google 전략 사용을 위해 src/strategies/google.strategy.ts 파일을 생성 후 작성해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1689500247405&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// google.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { Strategy } from 'passport-google-oauth20';
import { User } from 'src/user/user.entity';
import { Repository } from 'typeorm';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository&amp;lt;User&amp;gt;,
  ) {
    super({
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: 'http://localhost:3000/auth/google/callback',
      scope: ['email', 'profile'],
    });
  }

  // eslint-disable-next-line @typescript-eslint/ban-types
  async validate(accessToken: string, refreshToken: string, profile: any, done: Function): Promise&amp;lt;any&amp;gt; {
    const { name, emails, photos } = profile;

    const user = await this.userRepository.findOne({ where: { email: emails[0].value } });

    if (user) {
      return done(null, user);
    }

    const newUser = new User();
    newUser.email = emails[0].value;
    newUser.firstName = name.givenName;
    newUser.lastName = name.familyName;
    newUser.picture = photos[0].value;

    const result = await this.userRepository.save(newUser);

    return done(null, result);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.5.&amp;nbsp; User 정보 데이터 바인딩 및 저장을 위해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;src/&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;user&lt;/span&gt;/user.entity.ts 파일을 생성해준다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1689500392252&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column({ nullable: true })
  picture: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.6 서버를 실행 후 아래 주소로 접속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://localhost:3000/auth/google&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 계정 목록이 나타나고 로그인 시 계정에 대한 정보를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mhNsJ/btsnFbeuHqE/GwLkEYttUMljQfuHfA0YqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mhNsJ/btsnFbeuHqE/GwLkEYttUMljQfuHfA0YqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mhNsJ/btsnFbeuHqE/GwLkEYttUMljQfuHfA0YqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmhNsJ%2FbtsnFbeuHqE%2FGwLkEYttUMljQfuHfA0YqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;509&quot; height=&quot;577&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l5sbw/btsnK81RYRc/1kSrJNk9szCxrpBG9G2gCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l5sbw/btsnK81RYRc/1kSrJNk9szCxrpBG9G2gCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l5sbw/btsnK81RYRc/1kSrJNk9szCxrpBG9G2gCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl5sbw%2FbtsnK81RYRc%2F1kSrJNk9szCxrpBG9G2gCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;755&quot; height=&quot;238&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 데이터를 가지고 service 단에서 User 정보를 DB에 저장할수 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 로직을 추가할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 셋팅이 완료되면 localhost로 갖고있는것보단&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google App Engine을 활용해 NestJS 프로젝트를 바로 올려 DEV(개발용)으로 사용할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 과정은 이지하므로 포스팅 작성하는것을 생략하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V0bYN/btsnE9ViDct/bi7OTfyTqAMPCrwEe2D5R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V0bYN/btsnE9ViDct/bi7OTfyTqAMPCrwEe2D5R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V0bYN/btsnE9ViDct/bi7OTfyTqAMPCrwEe2D5R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV0bYN%2FbtsnE9ViDct%2Fbi7OTfyTqAMPCrwEe2D5R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1496&quot; height=&quot;904&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdycsB/btsnLbqJWDn/GWpuFAqlGCSkJk38ok5qK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdycsB/btsnLbqJWDn/GWpuFAqlGCSkJk38ok5qK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdycsB/btsnLbqJWDn/GWpuFAqlGCSkJk38ok5qK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdycsB%2FbtsnLbqJWDn%2FGWpuFAqlGCSkJk38ok5qK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1693&quot; height=&quot;886&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지를 통해 DEV로 올라간 NestJS 프로젝트 를 확인할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 github 코드를 첨부한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/shlee0882/nestjs-study&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/shlee0882/nestjs-study&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689502100673&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - shlee0882/nestjs-study: :cat: Nest JS 스터디&quot; data-og-description=&quot;:cat: Nest JS 스터디. Contribute to shlee0882/nestjs-study development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/shlee0882/nestjs-study&quot; data-og-url=&quot;https://github.com/shlee0882/nestjs-study&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c9Kp4h/hyTk741ZHM/16aAvOrbjzMekDG1kp06Hk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/shlee0882/nestjs-study&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/shlee0882/nestjs-study&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c9Kp4h/hyTk741ZHM/16aAvOrbjzMekDG1kp06Hk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - shlee0882/nestjs-study: :cat: Nest JS 스터디&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;:cat: Nest JS 스터디. Contribute to shlee0882/nestjs-study development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 토이 프로젝트는 NetJS에 Google Cloud MySQL , Google App Engine을 붙여사용했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발에 대한 편의성은 높아졌지만 서비스 이용에 대한 비용부담이 커졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beEmrC/btsnK8t0H6Q/IQkGhF5RlMmvFvdTfk5ykK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beEmrC/btsnK8t0H6Q/IQkGhF5RlMmvFvdTfk5ykK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beEmrC/btsnK8t0H6Q/IQkGhF5RlMmvFvdTfk5ykK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeEmrC%2FbtsnK8t0H6Q%2FIQkGhF5RlMmvFvdTfk5ykK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1529&quot; height=&quot;798&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15일 정도 혼자 사용했는데 7만원이라니...&amp;nbsp; 굉장한 과금이다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발비용을 고려하지 못한 내 잘못이긴한데&amp;nbsp; 흙수저인 나에게 부담이 되긴하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;p3mcp.jpg&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BUPYS/btsnTDGXHg8/6FkO7LNjMUqpIvUHBFSUj1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BUPYS/btsnTDGXHg8/6FkO7LNjMUqpIvUHBFSUj1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BUPYS/btsnTDGXHg8/6FkO7LNjMUqpIvUHBFSUj1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBUPYS%2FbtsnTDGXHg8%2F6FkO7LNjMUqpIvUHBFSUj1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;412&quot; height=&quot;351&quot; data-filename=&quot;p3mcp.jpg&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>전체/NestJS</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/297</guid>
      <comments>https://shlee0882.tistory.com/297#entry297comment</comments>
      <pubDate>Sun, 16 Jul 2023 19:06:50 +0900</pubDate>
    </item>
    <item>
      <title>NestJs 토이 프로젝트 구성해보기 - 1</title>
      <link>https://shlee0882.tistory.com/296</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는 백엔드 서버 사이드 애플리케이션을 구축하기 위한 프레임워크이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고양이 그림이 귀여워서 공부해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;nestjs-logo-09342F76C0-seeklogo.com.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4k102/btsnOfl2HXO/cX7h1jzLqUUXNuhD4NY7n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4k102/btsnOfl2HXO/cX7h1jzLqUUXNuhD4NY7n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4k102/btsnOfl2HXO/cX7h1jzLqUUXNuhD4NY7n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4k102%2FbtsnOfl2HXO%2FcX7h1jzLqUUXNuhD4NY7n0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;299&quot; data-filename=&quot;nestjs-logo-09342F76C0-seeklogo.com.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. nestjs/cli 를 전역으로 설치해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#fafafa;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #e5e5e5&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#666;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;nest&amp;nbsp;프로젝트&amp;nbsp;설치&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;i&amp;nbsp;&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;g&amp;nbsp;@nestjs&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;/&lt;/span&gt;cli&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;nest&amp;nbsp;new&amp;nbsp;nestjs&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;study&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#e5e5e5;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 적절한 위치에서 nestjs 프로젝트를 생성해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpmDGa/btsnEMTy6go/6aOilpmjsMdWbEWE3UrT9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpmDGa/btsnEMTy6go/6aOilpmjsMdWbEWE3UrT9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpmDGa/btsnEMTy6go/6aOilpmjsMdWbEWE3UrT9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpmDGa%2FbtsnEMTy6go%2F6aOilpmjsMdWbEWE3UrT9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;508&quot; height=&quot;568&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. vsc 를 실행하면 다음과 같은 구조가 보일것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1829&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oNArk/btsnIKUtOcW/INLdSnH6Ds0ih32v6kNJQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oNArk/btsnIKUtOcW/INLdSnH6Ds0ih32v6kNJQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oNArk/btsnIKUtOcW/INLdSnH6Ds0ih32v6kNJQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoNArk%2FbtsnIKUtOcW%2FINLdSnH6Ds0ih32v6kNJQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1829&quot; height=&quot;670&quot; data-origin-width=&quot;1829&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는 모듈, 서비스, 컨트롤러라는 개념을 사용해 코드를 구조화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 서버를 실행하면 Hello World 가 나오게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Vr86/btsnFeWx4YU/slhKgf3OxQDRaEnhXPdcg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Vr86/btsnFeWx4YU/slhKgf3OxQDRaEnhXPdcg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Vr86/btsnFeWx4YU/slhKgf3OxQDRaEnhXPdcg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Vr86%2FbtsnFeWx4YU%2FslhKgf3OxQDRaEnhXPdcg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1077&quot; height=&quot;278&quot; data-origin-width=&quot;1077&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qOcaA/btsnIKUtSgC/NCj029ecjBt6U9UDD8CPt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qOcaA/btsnIKUtSgC/NCj029ecjBt6U9UDD8CPt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qOcaA/btsnIKUtSgC/NCj029ecjBt6U9UDD8CPt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqOcaA%2FbtsnIKUtSgC%2FNCj029ecjBt6U9UDD8CPt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;148&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 모듈, 서비스, 컨트롤러라는 개념을 적용해 확장 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 cmd를 이용해 task subject에 대한 service, controller, module을 생성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#fafafa;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #e5e5e5&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#666;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;4&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;controller,&amp;nbsp;service,&amp;nbsp;module&amp;nbsp;생성&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;nest&amp;nbsp;generate&amp;nbsp;controller&amp;nbsp;task&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;nest&amp;nbsp;generate&amp;nbsp;service&amp;nbsp;task&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;nest&amp;nbsp;generate&amp;nbsp;module&amp;nbsp;task&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#e5e5e5;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y34mU/btsnFeCh7SF/G6rXQLN3PM957vU6ppGVWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y34mU/btsnFeCh7SF/G6rXQLN3PM957vU6ppGVWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y34mU/btsnFeCh7SF/G6rXQLN3PM957vU6ppGVWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy34mU%2FbtsnFeCh7SF%2FG6rXQLN3PM957vU6ppGVWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;935&quot; height=&quot;419&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src 하위로 디렉토리가 생성된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. TypeORM을 사용하여 Google Cloud SQL의 MySQL 데이터베이스에 연결하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#fafafa;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #e5e5e5&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#666;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;4&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;5&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;6&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;7&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;8&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;9&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;10&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;11&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#010101;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;type&amp;nbsp;orm&amp;nbsp;mysql&amp;nbsp;설치&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;install&amp;nbsp;&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;save&amp;nbsp;@nestjs&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;/&lt;/span&gt;typeorm&amp;nbsp;typeorm&amp;nbsp;mysql&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;환경변수&amp;nbsp;설정파일&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;install&amp;nbsp;dotenv&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;config&amp;nbsp;env파일&amp;nbsp;불러오기&amp;nbsp;위한&amp;nbsp;설치&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;install&amp;nbsp;@nestjs&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;/&lt;/span&gt;config&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#999999&quot;&gt;#&amp;nbsp;swagger&amp;nbsp;설치&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;$&amp;nbsp;npm&amp;nbsp;install&amp;nbsp;&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;save&amp;nbsp;@nestjs&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;/&lt;/span&gt;swagger&amp;nbsp;swagger&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;ui&lt;span style=&quot;color:#0086b3&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#a71d5d&quot;&gt;-&lt;/span&gt;express&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#e5e5e5;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 노드 패키지를 설치해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env 파일에 민감한 정보를 작성해주고 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb5ZKN/btsnSZJ1LTj/mcPN964bWSZn99Va8IsDe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb5ZKN/btsnSZJ1LTj/mcPN964bWSZn99Va8IsDe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb5ZKN/btsnSZJ1LTj/mcPN964bWSZn99Va8IsDe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb5ZKN%2FbtsnSZJ1LTj%2FmcPN964bWSZn99Va8IsDe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1076&quot; height=&quot;592&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-1. app.module.ts 파일을 수정해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1689496116857&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TaskModule } from './task/task.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, // makes the config global
      envFilePath: '.env', // point to .env in root directory
    }),
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: process.env.DB_HOST,
      port: +process.env.DB_PORT,
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_DATABASE,
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
      extra: process.env.INSTANCE_CONNECTION_NAME &amp;amp;&amp;amp; process.env.NODE_ENV === 'dev' 
      ? { socketPath: `/cloudsql/${process.env.INSTANCE_CONNECTION_NAME}` } 
      : undefined,
    }),
    TaskModule,
  ],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-2. task.entity.ts 파일을 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1689496716657&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity('tasks') // Specifies the name of the table in the database
export class Task {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-3. task.controller.ts 파일을 수정해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1689496223010&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Controller, Get, Post, Body } from '@nestjs/common';
import { TaskService } from './task.service';
import { Task } from './task.entity';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';

@ApiTags('task API')
@Controller('tasks')
export class TaskController {
  constructor(private taskService: TaskService) {}

  @ApiOperation({ summary: 'Retrieve a list of task.' })
  @ApiResponse({ status: 200, description: 'List of task.' })
  @Get()
  findAll(): Promise&amp;lt;Task[]&amp;gt; {
    return this.taskService.findAll();
  }

  @ApiOperation({ summary: 'Create an task.' })
  @ApiResponse({ status: 201, description: 'The task has been successfully created.' })
  @Post()
  create(@Body() task: Task): Promise&amp;lt;Task&amp;gt; {
    return this.taskService.create(task);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-4. task.service.ts 파일을 수정해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1689496303715&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Task } from './task.entity';

@Injectable()
export class TaskService {
  constructor(
    @InjectRepository(Task)
    private taskRepository: Repository&amp;lt;Task&amp;gt;,
  ) {}

  findAll(): Promise&amp;lt;Task[]&amp;gt; {
    return this.taskRepository.find();
  }

  create(task: Task): Promise&amp;lt;Task&amp;gt; {
    return this.taskRepository.save(task);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-5. task.module.ts 파일을 수정해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1689496798511&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TaskController } from './task.controller';
import { TaskService } from './task.service';
import { Task } from './task.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Task])],
  controllers: [TaskController],
  providers: [TaskService],
})
export class TaskModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-6 main.ts 파일을 수정해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1689497575726&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { INestApplication } from '@nestjs/common';

export function setupSwagger(app: INestApplication) {
  const options = new DocumentBuilder()
    .setTitle('NestJS Swagger')
    .setDescription('API documentation')
    .setVersion('1.0')
    .addTag('NestJS API LIST')
    .build();
  
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api-docs', app, document);
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  setupSwagger(app);
  await app.listen(3000);
}
bootstrap();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 아래와 같을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 켜고 DB접속이 정상적이라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 결과를 확인할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGfeNT/btsnK8OiLW3/lr1LFxrDISKrBJ2k7gCWj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGfeNT/btsnK8OiLW3/lr1LFxrDISKrBJ2k7gCWj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGfeNT/btsnK8OiLW3/lr1LFxrDISKrBJ2k7gCWj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGfeNT%2FbtsnK8OiLW3%2Flr1LFxrDISKrBJ2k7gCWj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1375&quot; height=&quot;577&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bktVHL/btsnIKNIeP2/VfIK7ZOjZN52AZzvQ1qKQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bktVHL/btsnIKNIeP2/VfIK7ZOjZN52AZzvQ1qKQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bktVHL/btsnIKNIeP2/VfIK7ZOjZN52AZzvQ1qKQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbktVHL%2FbtsnIKNIeP2%2FVfIK7ZOjZN52AZzvQ1qKQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1121&quot; height=&quot;872&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://localhost:3000/api-docs#/ 로 접속시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swagger도 정상적으로 접근되는것을 확인할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 Talend API Tester 크롬 확장프로그램을 사용하겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn0khA/btsnF4Tta9v/M83XszcRB0BwrB3Xws0rJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn0khA/btsnF4Tta9v/M83XszcRB0BwrB3Xws0rJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn0khA/btsnF4Tta9v/M83XszcRB0BwrB3Xws0rJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn0khA%2FbtsnF4Tta9v%2FM83XszcRB0BwrB3Xws0rJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;894&quot; height=&quot;371&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET 데이터 조회&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tJzmE/btsnF7CEXVO/LM48Tq69LQvA30fgbQSn91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tJzmE/btsnF7CEXVO/LM48Tq69LQvA30fgbQSn91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tJzmE/btsnF7CEXVO/LM48Tq69LQvA30fgbQSn91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtJzmE%2FbtsnF7CEXVO%2FLM48Tq69LQvA30fgbQSn91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1574&quot; height=&quot;606&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST 데이터 조회&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGptsh/btsnSZpJvTN/QweAVdPnzebHNWtKhdNOc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGptsh/btsnSZpJvTN/QweAVdPnzebHNWtKhdNOc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGptsh/btsnSZpJvTN/QweAVdPnzebHNWtKhdNOc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGptsh%2FbtsnSZpJvTN%2FQweAVdPnzebHNWtKhdNOc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1581&quot; height=&quot;683&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 영속성이 보장되니 db tool (dbeaver) 을 이용하여 데이터를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음편은 Google Auth Login을 활용해 보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>전체/NestJS</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/296</guid>
      <comments>https://shlee0882.tistory.com/296#entry296comment</comments>
      <pubDate>Sun, 16 Jul 2023 18:23:11 +0900</pubDate>
    </item>
    <item>
      <title>ReactJs, TypeScript, Vite 휴가 추천 토이 프로젝트</title>
      <link>https://shlee0882.tistory.com/294</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ReactJs,&amp;nbsp;TypeScript,&amp;nbsp;Vite를&amp;nbsp;사용해휴가(연차)를&amp;nbsp;효율적으로&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp; &lt;br /&gt;추천해주는&amp;nbsp;[휴가&amp;nbsp;추천&amp;nbsp;토이&amp;nbsp;프로젝트]를&amp;nbsp;만들어보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트는 TypeScript와 React를 학습하는 것에 포커스를 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript는 (변수 : 타입 ) 형태로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수 Object의 타입을 알고있다면 Object안에 속한 속성을 빠르게 찾아 접근가능하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입을 제한하여 메소드의 진입을 막을수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 API를 만드는 백엔드가 주도권을 갖고 있으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트는 받아서 사용하는 주체이므로 API가 구조적으로 변경되거나&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입을 모를때 지정하고 싶지 않을때가 발생된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 any로 받아버리면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 DateType이라는 Object를 정의한것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서는 String year; String Month; ... 로 정의가 될것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-calendar &lt;span style=&quot;color: #006dd7;&quot;&gt;(&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://www.npmjs.com/package/react-calendar&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/react-calendar&lt;/a&gt;)&lt;/span&gt; 모듈을 사용했으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈소스 이므로 가이드를 보며 필요한 props를 주입시켜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스터마이징 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686386865134&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export type DateType = {
  year: string,
  month: string,
  holidayArray: Array&amp;lt;RtnArrType&amp;gt;
  holidayRecArray: any,
  selectHoliday: string
}

const CalendarComponents = (dateType: DateType) =&amp;gt; {
  const [value, onChange] = useState(new Date());
  return (
    &amp;lt;Calendar onChange={onChange} 
    className={[&quot;box&quot;]}
    view=&quot;month&quot;
    onClickMonth={(value, event) =&amp;gt; (
      event.preventDefault(),
      event.stopPropagation()
    )}
    onClickDay={(value, event) =&amp;gt; (
      event.preventDefault(),
      event.stopPropagation()
    )}
    defaultValue={[new Date(Number(dateType.year), Number(dateType.month))]}
    tileContent={({ activeStartDate, date, view }) =&amp;gt; view === 'month'? &amp;lt;TitleContent activeStartDate={activeStartDate} date={date} holidayArray={dateType.holidayArray}/&amp;gt; : null}
    tileClassName={({activeStartDate, date, view }) =&amp;gt; changeColorDate(activeStartDate, date, view, dateType)}
    calendarType=&quot;US&quot; 
    formatDay={(locale, date) =&amp;gt; moment(date).format(&quot;D&quot;)} /&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState를 사용하여 상태관리가 가능하게 할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{{ }} expression을 사용해 데이터 바인딩을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetchData를 통해 공공 휴일데이터 정보를 가져와 화면을 렌더링한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686387434213&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const App = () =&amp;gt; {
  const [selectHoliday, setSelectHoliday] = useState(&quot;2&quot;);
  const [selectedOption, setSelectedOption] = useState(year);
  const [holidayArray, setHolidayArray] = useState&amp;lt;Array&amp;lt;RtnArrType&amp;gt;&amp;gt;([]);
  const [holidayRecArray, setHolidayRecArray] = useState([]);
  const [data, setData] = useState(null);
  const [error, setError] = useState(false);
  const [loading, setLoading] = useState(false);

  const fetchData = async () =&amp;gt; {
    setError(false);
    setLoading(true);
    try {
      let reqUri = rtnReqUriAppend(selectedOption)
      const response = await axios.get(reqUri);
      let rtnArray = response?.data?.response?.body?.items?.item
      let holidayArray = rtnArray.filter((arr:RtnArrType) =&amp;gt; arr.isHoliday == &quot;Y&quot;);
      setHolidayArray(holidayArray)
      console.log(holidayArray)
      const arrDayArr:any = recHolidayCalc(holidayArray, selectHoliday)
      setHolidayRecArray(arrDayArr)
    } catch (err) {
      setError(true);
    }
    setLoading(false);
  };

  useEffect(() =&amp;gt; {

  }, []);

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
        &amp;lt;p aria-hidden=&quot;true&quot; style={{textAlign: &quot;center&quot;}}&amp;gt;
          &amp;lt;Placeholder.Button xs={6} aria-hidden=&quot;true&quot;&amp;gt;
            직장인 연차 추천 애플리케이션 - 연차 효율적으로 쓰고 여행 가기!&amp;lt;br/&amp;gt;
            연차 사용 추천일이 파란색으로 선택됩니다.
          &amp;lt;/Placeholder.Button&amp;gt;
        &amp;lt;/p&amp;gt;
        &amp;amp;nbsp;&amp;amp;nbsp;연도: &amp;lt;Form.Select aria-label=&quot;Default select example2&quot; onChange={e =&amp;gt; {
        setSelectedOption(e.target.value);}}
        value={selectedOption} className='selectBox1'&amp;gt;
        &amp;lt;option&amp;gt;휴가 갈 연도를 선택&amp;lt;/option&amp;gt;
        {selectOptionData.map((item:number, index:number) =&amp;gt;(
          &amp;lt;option key={item} value={item}&amp;gt;{item}&amp;lt;/option&amp;gt;
        ))}
      &amp;lt;/Form.Select&amp;gt;
      사용가능 휴가일수: &amp;lt;Form.Select aria-label=&quot;Default select example1&quot; onChange={e =&amp;gt; {
        setSelectHoliday(e.target.value);}}
        value={selectHoliday} className='selectBox1'&amp;gt;
        &amp;lt;option&amp;gt;사용가능한 휴가 일수를 선택&amp;lt;/option&amp;gt;
        {selectHolidayCnt.map((item:number, index:number) =&amp;gt;(
          &amp;lt;option key={item} value={item}&amp;gt;{item}&amp;lt;/option&amp;gt;
        ))}
      &amp;lt;/Form.Select&amp;gt;
      &amp;lt;Button variant=&quot;success&quot; className='holidaySearch1' onClick={() =&amp;gt; {
        fetchData();
      }
      }&amp;gt;휴가 검색!!&amp;lt;/Button&amp;gt;
      &amp;lt;div&amp;gt;
        {loading ? (
          &amp;lt;div style={{ height: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}&amp;gt;
            &amp;lt;BeatLoader size={70} color=&quot;green&quot;/&amp;gt;
          &amp;lt;/div&amp;gt;
        ) : 
          error? (
          &amp;lt;Alert variant=&quot;danger&quot; onClose={() =&amp;gt; setError(false)} dismissible&amp;gt;
            &amp;lt;Alert.Heading&amp;gt;Error&amp;lt;/Alert.Heading&amp;gt;
            &amp;lt;p&amp;gt;휴일데이터를 갖고 오지 못했습니다.&amp;lt;/p&amp;gt;
          &amp;lt;/Alert&amp;gt;
          ) : (
          &amp;lt;&amp;gt;
            &amp;lt;div style={{ display: 'flex', flexWrap: 'nowrap' }}&amp;gt;
              {items1.map((item1: string, index: number) =&amp;gt; (
                &amp;lt;CalendarComponents key={index} year={selectedOption} month={item1} holidayArray={holidayArray} holidayRecArray={holidayRecArray} selectHoliday={selectHoliday}&amp;gt;&amp;lt;/CalendarComponents&amp;gt;
              ))}
            &amp;lt;/div&amp;gt;
            &amp;lt;div style={{ display: 'flex', flexWrap: 'nowrap' }}&amp;gt;
              {items2.map((item2: string, index: number) =&amp;gt; (
                &amp;lt;CalendarComponents key={index} year={selectedOption} month={item2} holidayArray={holidayArray} holidayRecArray={holidayRecArray} selectHoliday={selectHoliday}&amp;gt;&amp;lt;/CalendarComponents&amp;gt;
              ))}
            &amp;lt;/div&amp;gt;
            &amp;lt;div style={{ display: 'flex', flexWrap: 'nowrap' }}&amp;gt;
              {items3.map((item3: string, index: number) =&amp;gt; (
                &amp;lt;CalendarComponents key={index} year={selectedOption} month={item3} holidayArray={holidayArray} holidayRecArray={holidayRecArray} selectHoliday={selectHoliday}&amp;gt;&amp;lt;/CalendarComponents&amp;gt;
              ))}
            &amp;lt;/div&amp;gt;
          &amp;lt;/&amp;gt;
        )}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스를 수정하며 발생되는 불필요한 기본 설정이나 스타일 등을 node_modules 안에 접근해서 적절하게 수정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈소스를 사용하며 발생되는 불가피한 영역인듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite 공식 가이드에 나온대로 배포 준비를 하고 &lt;span style=&quot;color: #006dd7;&quot;&gt;(&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://vitejs.dev/guide/static-deploy.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://vitejs.dev/guide/static-deploy.html)&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github 정적 페이지 배포를 통해 배포해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포된&amp;nbsp;Deploy&amp;nbsp;URL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://shlee0882.github.io/react-ts-calendar-app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://shlee0882.github.io/react-ts-calendar-app/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686387832554&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;직장인 연차 추천 애플리케이션&quot; data-og-description=&quot;&quot; data-og-host=&quot;shlee0882.github.io&quot; data-og-source-url=&quot;https://shlee0882.github.io/react-ts-calendar-app/&quot; data-og-url=&quot;https://shlee0882.github.io/react-ts-calendar-app/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://shlee0882.github.io/react-ts-calendar-app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://shlee0882.github.io/react-ts-calendar-app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;직장인 연차 추천 애플리케이션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;shlee0882.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;repo&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://github.com/shlee0882/react-ts-calendar-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/shlee0882/react-ts-calendar-app&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686387763978&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - shlee0882/react-ts-calendar-app: TypeScript, ReactJs, Vite 직장인 휴가 추천 토이 프로젝트&quot; data-og-description=&quot;TypeScript, ReactJs, Vite 직장인 휴가 추천 토이 프로젝트. Contribute to shlee0882/react-ts-calendar-app development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/shlee0882/react-ts-calendar-app&quot; data-og-url=&quot;https://github.com/shlee0882/react-ts-calendar-app&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czhiJJ/hySXdj3SIJ/UXEbX1uOcush5lexIsf7v1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/shlee0882/react-ts-calendar-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/shlee0882/react-ts-calendar-app&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czhiJJ/hySXdj3SIJ/UXEbX1uOcush5lexIsf7v1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - shlee0882/react-ts-calendar-app: TypeScript, ReactJs, Vite 직장인 휴가 추천 토이 프로젝트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;TypeScript, ReactJs, Vite 직장인 휴가 추천 토이 프로젝트. Contribute to shlee0882/react-ts-calendar-app development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1686386603784&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;react-ts-calendar-app&quot;,
  &quot;private&quot;: true,
  &quot;version&quot;: &quot;0.0.0&quot;,
  &quot;type&quot;: &quot;module&quot;,
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;vite&quot;,
    &quot;build&quot;: &quot;tsc &amp;amp;&amp;amp; vite build&quot;,
    &quot;preview&quot;: &quot;vite preview&quot;
  },
  &quot;dependencies&quot;: {
    &quot;@reduxjs/toolkit&quot;: &quot;^1.9.2&quot;,
    &quot;axios&quot;: &quot;^1.3.2&quot;,
    &quot;bootstrap&quot;: &quot;^5.2.3&quot;,
    &quot;react&quot;: &quot;^18.2.0&quot;,
    &quot;react-big-calendar&quot;: &quot;^1.6.3&quot;,
    &quot;react-bootstrap&quot;: &quot;^2.7.0&quot;,
    &quot;react-calendar&quot;: &quot;^4.0.0&quot;,
    &quot;react-dom&quot;: &quot;^18.2.0&quot;,
    &quot;react-redux&quot;: &quot;^8.0.5&quot;,
    &quot;react-spinners&quot;: &quot;^0.13.8&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@types/moment&quot;: &quot;^2.13.0&quot;,
    &quot;@types/react&quot;: &quot;^18.0.26&quot;,
    &quot;@types/react-big-calendar&quot;: &quot;^0.38.4&quot;,
    &quot;@types/react-calendar&quot;: &quot;^3.9.0&quot;,
    &quot;@types/react-dom&quot;: &quot;^18.0.9&quot;,
    &quot;@vitejs/plugin-react&quot;: &quot;^3.0.0&quot;,
    &quot;typescript&quot;: &quot;^4.9.3&quot;,
    &quot;vite&quot;: &quot;^4.0.0&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>전체/개인 프로젝트</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/294</guid>
      <comments>https://shlee0882.tistory.com/294#entry294comment</comments>
      <pubDate>Sat, 10 Jun 2023 18:10:14 +0900</pubDate>
    </item>
    <item>
      <title>mybatis에서 enum type handler 사용하기</title>
      <link>https://shlee0882.tistory.com/292</link>
      <description>&lt;p&gt;model에 enum type을 넣고&amp;nbsp;&lt;/p&gt;
&lt;p&gt;mybatis에서 아래 테이블의 data를 받아오려고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TifW8/btqZpfkOjam/bXm2pckAxs8R4Y4pktygc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TifW8/btqZpfkOjam/bXm2pckAxs8R4Y4pktygc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TifW8/btqZpfkOjam/bXm2pckAxs8R4Y4pktygc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTifW8%2FbtqZpfkOjam%2FbXm2pckAxs8R4Y4pktygc1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;enum은 여러 형태를 가질 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래와 같이 그냥 단건의 데이터만 지정하여 만드는 경우가 있을 수 있고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/a2852aa376bbf6e005b4ed22ac8eb87b.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;db에 쌓이는 데이터유형이 숫자( &lt;span style=&quot;color: #333333;&quot;&gt;1_VERSION, 2_VERSION )&lt;/span&gt;나&lt;/p&gt;
&lt;p&gt;특수문자로 시작할 경우 enum을 아래와 같이 인자를 2개씩 갖게 지정될 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/91eceabdc5f4f9c010f613430217676a.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럴경우 여러 형태의 enum 데이터를 바인딩 하지 못해&lt;/p&gt;
&lt;p&gt;아래와 같은 오류가 발생 할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV9aAX/btqZrgwvuX7/qvmdVoTPJld3NEn9yKHd7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV9aAX/btqZrgwvuX7/qvmdVoTPJld3NEn9yKHd7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV9aAX/btqZrgwvuX7/qvmdVoTPJld3NEn9yKHd7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV9aAX%2FbtqZrgwvuX7%2FqvmdVoTPJld3NEn9yKHd7k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column '' from result set.&lt;/p&gt;
&lt;p&gt;Caused by: java.lang.IllegalArgumentException: No enum constant&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/2a96bf0a3e2d0799076b83abfb1b8e4a.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위소스를 보면 상품정보 rest controller에서 service를 거쳐 mapper에 해당하는 쿼리결과를 lists 객체에&lt;/p&gt;
&lt;p&gt;담겨야 하는데 데이터를 못받아와 catch절 NOT_FOUND로 빠져&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;swagger에서해당 data를 알지 못해 못받아오는 경우도 생긴다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bgmx2/btqZpfylDzg/SINwoidJXCKH61vd8ns0a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bgmx2/btqZpfylDzg/SINwoidJXCKH61vd8ns0a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bgmx2/btqZpfylDzg/SINwoidJXCKH61vd8ns0a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBgmx2%2FbtqZpfylDzg%2FSINwoidJXCKH61vd8ns0a1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이럴경우 TypeHandler를 생성하여 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/c405ef8dfceffbaae981ec1e8bb3a598.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 생성한 type handler 위치의 패키지명을 아래와 같이 지정해준다.&lt;/p&gt;
&lt;p&gt;type-handlers-package: com.shlee.toy1.common.handler.type&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/f0837c481f3edf514203391cea3fa698.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 api를 조회해보면 아래와 같이 enum으로 정의한 키값으로 나온다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAguT/btqZoyycOaH/NR6RkwV1TQeUnegdkbR870/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAguT/btqZoyycOaH/NR6RkwV1TQeUnegdkbR870/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAguT/btqZoyycOaH/NR6RkwV1TQeUnegdkbR870/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAguT%2FbtqZoyycOaH%2FNR6RkwV1TQeUnegdkbR870%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이제 이 반환된 키 값으로 서비스 로직에서 데이터를 가공해서 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;반환해주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bynLVx/btqZvOsKtPd/e96sh7WbPiliHTMBEij28K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bynLVx/btqZvOsKtPd/e96sh7WbPiliHTMBEij28K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bynLVx/btqZvOsKtPd/e96sh7WbPiliHTMBEij28K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbynLVx%2FbtqZvOsKtPd%2Fe96sh7WbPiliHTMBEij28K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 여러개의 enum type에 대응가능한 mybatis type handler에 대해 작성할 수 있게 되었다.&amp;nbsp;&lt;/p&gt;</description>
      <category>전체/Spring Boot</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/292</guid>
      <comments>https://shlee0882.tistory.com/292#entry292comment</comments>
      <pubDate>Sun, 7 Mar 2021 21:23:10 +0900</pubDate>
    </item>
    <item>
      <title>여러개의 이미지를(N) to 한개 이미지(1) gif 효과 만들기</title>
      <link>https://shlee0882.tistory.com/291</link>
      <description>&lt;p&gt;프로젝트에서 이미지 api에서 받아온 이미지 url로&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이미지 파일 N개 혹은&lt;/p&gt;
&lt;p&gt;이미지 파일 1개를 같은 width, height 값으로 N개 분할하여&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;100&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lhEov/btqZpe7edFH/qhtwydViJl4so1SMRXMfA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lhEov/btqZpe7edFH/qhtwydViJl4so1SMRXMfA1/img.png&quot; data-alt=&quot;여러개의 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lhEov/btqZpe7edFH/qhtwydViJl4so1SMRXMfA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlhEov%2FbtqZpe7edFH%2FqhtwydViJl4so1SMRXMfA1%2Fimg.png&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;100&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;여러개의 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이미지 N개를 1개의 연결된 gif 이미지로 보여주려는 작업요건이 필요했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 일단 해당 요건을 테스트 해보기 위해 테스트 소스를 작성해보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;a style=&quot;color: #1a5490;&quot; href=&quot;https://codepen.io/shlee0882/pen/QWGmJbm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;codepen.io/shlee0882/pen/QWGmJbm&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1615113190091&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;이미지 gif 효과 만들기&quot; data-og-description=&quot;...&quot; data-og-host=&quot;codepen.io&quot; data-og-source-url=&quot;https://codepen.io/shlee0882/pen/QWGmJbm&quot; data-og-url=&quot;https://codepen.io/shlee0882/details/QWGmJbm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tU0p9/hyJtMw0SGd/hobh7zRhXosbKr0LRN9M80/img.png?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/ktxmb/hyJvPFDok0/FwKts3jkavXOKOx46YSQdK/img.png?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540&quot;&gt;&lt;a href=&quot;https://codepen.io/shlee0882/pen/QWGmJbm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codepen.io/shlee0882/pen/QWGmJbm&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tU0p9/hyJtMw0SGd/hobh7zRhXosbKr0LRN9M80/img.png?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/ktxmb/hyJvPFDok0/FwKts3jkavXOKOx46YSQdK/img.png?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;이미지 gif 효과 만들기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;...&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;codepen.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;위 소스는 캔버스를 이용해 특정영역을 잡고&lt;/p&gt;
&lt;p&gt;이미지 데이터 주소를 획득하여 배열에 넣은 다음&lt;/p&gt;
&lt;p&gt;마우스 hover시 이미지 정보가 담긴 배열을 0.5초 간격으로 번갈아 가며 보여주며&lt;/p&gt;
&lt;p&gt;마우스 out시는 반복을 멈추는 &lt;span style=&quot;color: #333333;&quot;&gt;소스코드이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 이 코드로 기본검증이 끝났으므로&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;응용하여 gif를 생성하는 라이브러리를 사용하지 않고 (not use gif library)&lt;/p&gt;
&lt;p&gt;아래와 같은&amp;nbsp;움직이는 gif이미지를 생성한 것 같은 착각을 일으키는 코드를 작성할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;gif_effect.gif&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;254&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uR8XP/btqZn02JmDK/Z7jxC6yrnM1A6NWdnlnXR1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uR8XP/btqZn02JmDK/Z7jxC6yrnM1A6NWdnlnXR1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uR8XP/btqZn02JmDK/Z7jxC6yrnM1A6NWdnlnXR1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/uR8XP/btqZn02JmDK/Z7jxC6yrnM1A6NWdnlnXR1/img.gif&quot; data-filename=&quot;gif_effect.gif&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;254&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>전체/JS &amp;amp; Jquery</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/291</guid>
      <comments>https://shlee0882.tistory.com/291#entry291comment</comments>
      <pubDate>Sun, 7 Mar 2021 19:43:06 +0900</pubDate>
    </item>
    <item>
      <title>image url canvas 조작하기, CORS policy origin exception 회피하기</title>
      <link>https://shlee0882.tistory.com/289</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnqy60/btqXgpISxBJ/NXKvO14pTvipXuoFinl87k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnqy60/btqXgpISxBJ/NXKvO14pTvipXuoFinl87k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnqy60/btqXgpISxBJ/NXKvO14pTvipXuoFinl87k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcnqy60%2FbtqXgpISxBJ%2FNXKvO14pTvipXuoFinl87k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 이미지는 구글 웹페이지 메인에 로고이다.&lt;/p&gt;
&lt;p&gt;위 이미지의 url이 노출된 상태에서 &lt;span style=&quot;color: #333333;&quot;&gt;js에서 &lt;/span&gt;해당 이미지url로 image 생성자에 담아&amp;nbsp;&lt;/p&gt;
&lt;p&gt;localhost로 해당 이미지를 조작 및 변경 처리를 시도 하면 아래와 같이&lt;/p&gt;
&lt;p&gt;CORS policy: No 'Access-Control-Allow-Origin 오류가 발생한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3kIqB/btqXgoQJXia/RaaJ9KEiuiQaKAjeCKKGx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3kIqB/btqXgoQJXia/RaaJ9KEiuiQaKAjeCKKGx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3kIqB/btqXgoQJXia/RaaJ9KEiuiQaKAjeCKKGx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3kIqB%2FbtqXgoQJXia%2FRaaJ9KEiuiQaKAjeCKKGx1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CORS를 통하지 않고, 다른 origin으로 부터 가져온 이미지를&lt;/p&gt;
&lt;p&gt;canvas를&amp;nbsp;통해&amp;nbsp;이미지를&amp;nbsp;자르고&amp;nbsp;조작&amp;nbsp;시&amp;nbsp;&lt;br /&gt;원본데이터가&amp;nbsp;오염&amp;nbsp;및&amp;nbsp;안전하지&amp;nbsp;않다고&amp;nbsp;판단하여&lt;/p&gt;
&lt;p&gt;js에서 origin exception이 발생한 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/HTML/CORS_enabled_image&quot;&gt;developer.mozilla.org/ko/docs/Web/HTML/CORS_enabled_image&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1613302747047&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;교차 출처 이미지와 캔버스 허용하기 - HTML: Hypertext Markup Language | MDN&quot; data-og-description=&quot;교차 출처 이미지와 캔버스 허용하기 Table of contentsTable of contentsHTML은 이미지 처리를 위해 CORS header를 포함하고 있는 crossorigin 속성을 제공합니다. 이는 요소에서 정의된, 외부 origin으로 부터 가&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTML/CORS_enabled_image&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTML/CORS_enabled_image&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTML/CORS_enabled_image&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTML/CORS_enabled_image&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;교차 출처 이미지와 캔버스 허용하기 - HTML: Hypertext Markup Language | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;교차 출처 이미지와 캔버스 허용하기 Table of contentsTable of contentsHTML은 이미지 처리를 위해 CORS header를 포함하고 있는 crossorigin 속성을 제공합니다. 이는 요소에서 정의된, 외부 origin으로 부터 가&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 문제를 회피하기 위해 image url이 완전히 노출된 데이터를 인코딩 해서 처리해야한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- js에서 base64 encoding으로 변환 후 처리를 시도할 수 있지만 잘 처리가 되지 않을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/9e09c459889dca158fad1c3e128c760f.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 이럴경우 java에서 base64 encoding으로 변환 후 처리하면 쉽게 해결 할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/3bf78ee073bab410b37dc6eb98116412.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;인코딩 된 base64 data 값을 화면에서 가져와 img 태그안에 src attribute로 넣어주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/0ff53722f7aa00efc7e4f50538bcacb9.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;img 태그는 해당 이미지값을 읽어 다음과 같이 출력해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnqy60/btqXgpISxBJ/NXKvO14pTvipXuoFinl87k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnqy60/btqXgpISxBJ/NXKvO14pTvipXuoFinl87k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnqy60/btqXgpISxBJ/NXKvO14pTvipXuoFinl87k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcnqy60%2FbtqXgpISxBJ%2FNXKvO14pTvipXuoFinl87k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;인코딩된 값으로 canvas 등을 활용해 &lt;span style=&quot;color: #333333;&quot;&gt;CORS policy: No 'Access-Control-Allow-Origin을 회피하여&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;DOM을 조작할 수 있게 되었다.&amp;nbsp;&lt;/p&gt;</description>
      <category>전체/JS &amp;amp; Jquery</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/289</guid>
      <comments>https://shlee0882.tistory.com/289#entry289comment</comments>
      <pubDate>Sun, 14 Feb 2021 19:15:28 +0900</pubDate>
    </item>
    <item>
      <title>Could not autowire. No beans of type found. error 해결</title>
      <link>https://shlee0882.tistory.com/288</link>
      <description>&lt;p&gt;component로 등록된 것은 @SpringBootApplication 어노테이션을 통해 componentscan을 한다고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-using-springbootapplication-annotation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-using-springbootapplication-annotation&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1613293964000&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Using Spring Boot&quot; data-og-description=&quot;This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-using-springbootapplication-annotation&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-using-springbootapplication-annotation&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-using-springbootapplication-annotation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-using-springbootapplication-annotation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Using Spring Boot&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;intellij에서는 component로 등록된 것을 @&lt;span&gt;Autowired하여 사용하려면 &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Could not autowire. No beans of '' type found. 라는 error가 발생한다.&lt;/p&gt;
&lt;p&gt;컴파일 및 서버 실행에 문제는 없지만 해당 오류를 수정 할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;컴포넌트를 사용하려는 클래스 위에 컴포넌트를 스캔할 패키지명을 적어준다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;@ComponentScan(basePackages={&quot;com.shlee.toy1.common.components&quot;})&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;참조&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://stackoverflow.com/questions/26889970/intellij-incorrectly-saying-no-beans-of-type-found-for-autowired-repository&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;stackoverflow.com/questions/26889970/intellij-incorrectly-saying-no-beans-of-type-found-for-autowired-repository&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>전체/Spring Boot</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/288</guid>
      <comments>https://shlee0882.tistory.com/288#entry288comment</comments>
      <pubDate>Sun, 14 Feb 2021 18:20:22 +0900</pubDate>
    </item>
    <item>
      <title>spring boot swagger 적용하기</title>
      <link>https://shlee0882.tistory.com/287</link>
      <description>&lt;p&gt;spring boot 테스트 프로젝트로 swagger를 적용해보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. gradle에 해당 springfox-swagger를 명시해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/228af3b95dd5ff3e20b9893a91eb50bc.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;swagger2, swagger-ui, swagger-annotations, swagger-models를 받아주는데&lt;/p&gt;
&lt;p&gt;해당 정보는 mvn repository에서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://mvnrepository.com/artifact/io.springfox/springfox-swagger2/2.9.2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mvnrepository.com/artifact/io.springfox/springfox-swagger2/2.9.2&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui/2.9.2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mvnrepository.com/artifact/io.springfox/springfox-swagger-ui/2.9.2&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://mvnrepository.com/artifact/io.swagger/swagger-annotations/1.5.21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mvnrepository.com/artifact/io.swagger/swagger-annotations/1.5.21&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://mvnrepository.com/artifact/io.swagger/swagger-models/1.5.21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mvnrepository.com/artifact/io.swagger/swagger-models/1.5.21&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;gradle 설정이 완료되면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;ToySwaggerConfig 파일을 하나 생성해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/64e079d01da90f78e6c21975c5f3204b.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 WebMvcConfigurer를 implements 받고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;addResourceHandlers를 구현해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;swagger-ui가 보여질 화면의 리소스 위치를 설정해주는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음은 swagger를 만들 Docket을 bean으로 등록하고 생성할 Docket을 만들어준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;.apis는 api를 탐색할 위치(com.shlee.toy1,controller)&lt;/p&gt;
&lt;p&gt;.path 경로로 시작되는 위치(&lt;span style=&quot;color: #333333;&quot;&gt;controller에서 &lt;span&gt;@RequestMapping&lt;/span&gt;(&lt;span&gt;&quot;/v1&quot;)으로 시작되는 uri&lt;/span&gt;&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;.directModelSubstitute로 날짜형식을 맞춰주고&lt;/p&gt;
&lt;p&gt;.globalResponseMessage로 요청을 받았을때 어떻게 처리할지를 정해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.additionalModels의 typeResolver&lt;/span&gt;.resolve(&lt;span style=&quot;color: #000000;&quot;&gt;ResponseEntity&lt;/span&gt;.&lt;span&gt;class&lt;/span&gt;)는 ModelRef에서 지정한.&lt;/p&gt;
&lt;p&gt;ex) CommonError, CommonResponse, &lt;span style=&quot;color: #333333;&quot;&gt;string, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ResponseEntity &lt;/span&gt;같은 사용자 지정 모델값을 넣어주면 된다.&lt;/p&gt;
&lt;p&gt;필요 하지 않다면 다음과 같이 작성해도 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/d9550a4266ceee71573f6e1c0fa70457.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 .apiInfo를 통해 api정보를 지정해준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 서버를 실행후 &lt;span style=&quot;color: #006dd7;&quot;&gt;http://localhost:8080/swagger-ui.html&lt;/span&gt; 에 접속하면&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;controller와 연동된 api 목록이 나타나게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1827&quot; data-origin-height=&quot;607&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sGlSz/btqWXbyFiGX/7LQC4fI2zZNfefwds5oUFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sGlSz/btqWXbyFiGX/7LQC4fI2zZNfefwds5oUFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sGlSz/btqWXbyFiGX/7LQC4fI2zZNfefwds5oUFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsGlSz%2FbtqWXbyFiGX%2F7LQC4fI2zZNfefwds5oUFK%2Fimg.png&quot; data-origin-width=&quot;1827&quot; data-origin-height=&quot;607&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;test 시도 시 &lt;span style=&quot;color: #333333;&quot;&gt;response&lt;/span&gt;데이터가 잘 오는 것을 확인 할 수 있었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;responseImg.png&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;1467&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBGUln/btqWWtTUZuj/xd50ti2usKrMykEt3gJWU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBGUln/btqWWtTUZuj/xd50ti2usKrMykEt3gJWU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBGUln/btqWWtTUZuj/xd50ti2usKrMykEt3gJWU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBGUln%2FbtqWWtTUZuj%2Fxd50ti2usKrMykEt3gJWU1%2Fimg.png&quot; data-filename=&quot;responseImg.png&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;1467&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>전체/Spring Boot</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/287</guid>
      <comments>https://shlee0882.tistory.com/287#entry287comment</comments>
      <pubDate>Sun, 14 Feb 2021 17:59:23 +0900</pubDate>
    </item>
    <item>
      <title>Caused by: java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES) 해결하기</title>
      <link>https://shlee0882.tistory.com/286</link>
      <description>&lt;p&gt;최근&amp;nbsp;스프링 부트 기본 설정을 테스트로 잡아보면서 작업하는데&lt;/p&gt;
&lt;p&gt;DB 비밀번호를 간단하게 설정(숫자 0882) 하여 application.yml 파일을 작성하였더니 해당 오류가 발생하였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/5db960dc022ed3ecca84edbee6899476.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패스워드는 대문자, 소문자, 숫자, 특수문자를 포함한 암호 길이 8자 이상으로 설정해야 한다고 한다.&lt;/p&gt;
&lt;p&gt;그래서 아래와 같이 패스워드를 조합하여 설정하였더니 해당 오류가 발생하지 않았다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/f6581c515370c98ee866a704fe78228b.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/shlee0882/80ef828181a51f1432c79cf65bd69859.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;또한 스프링 2.3.x 버전에서는&amp;nbsp;&lt;/p&gt;
&lt;p&gt;application-local.yml&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;profile을 local&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;application-dev.yml&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;profile은 dev&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;application-prd.yml &lt;span style=&quot;color: #333333;&quot;&gt;profile을 prd로&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;파일단위별로 관리하여&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qUoOH/btqXb1OUGkP/G59fct9b6LD0OKcKIWQhrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qUoOH/btqXb1OUGkP/G59fct9b6LD0OKcKIWQhrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qUoOH/btqXb1OUGkP/G59fct9b6LD0OKcKIWQhrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqUoOH%2FbtqXb1OUGkP%2FG59fct9b6LD0OKcKIWQhrk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;active_profile.png&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;477&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbhMpy/btqWX55XVPc/lFNgsiXx76D2Nq6jtfNh5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbhMpy/btqWX55XVPc/lFNgsiXx76D2Nq6jtfNh5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbhMpy/btqWX55XVPc/lFNgsiXx76D2Nq6jtfNh5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbhMpy%2FbtqWX55XVPc%2FlFNgsiXx76D2Nq6jtfNh5k%2Fimg.png&quot; data-filename=&quot;active_profile.png&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;477&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당하는 active profiles가 무엇인지 설정한 뒤 실행하였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2.4.x 버전 부터 profile을 설정할 시 deprecated 오류가 발생하지만&lt;/p&gt;
&lt;p&gt;이전 설정을 &lt;span style=&quot;color: #006dd7;&quot;&gt;spring.config.use-legacy-processing=true&lt;/span&gt; 값을 통해&lt;/p&gt;
&lt;p&gt;2.3.x의 동일한 설정을 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>전체/Spring Boot</category>
      <author>effortDev</author>
      <guid isPermaLink="true">https://shlee0882.tistory.com/286</guid>
      <comments>https://shlee0882.tistory.com/286#entry286comment</comments>
      <pubDate>Sun, 14 Feb 2021 13:58:45 +0900</pubDate>
    </item>
  </channel>
</rss>