<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Jvito on Medium]]></title>
        <description><![CDATA[Stories by Jvito on Medium]]></description>
        <link>https://medium.com/@jvito11904?source=rss-f8e2380718a1------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*ruPCJblRtT2TLzEDelRCZA.jpeg</url>
            <title>Stories by Jvito on Medium</title>
            <link>https://medium.com/@jvito11904?source=rss-f8e2380718a1------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 16 May 2026 01:59:57 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@jvito11904/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[함수형 프로그래밍의 ‘Currying’과 ‘Closure’는 어떤 관계가 있을까?]]></title>
            <link>https://medium.com/@jvito11904/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%98-currying%EA%B3%BC-closure-%EB%8A%94-%EC%96%B4%EB%96%A4-%EA%B4%80%EA%B3%84%EA%B0%80-%EC%9E%88%EC%9D%84%EA%B9%8C-2583d351222b?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/2583d351222b</guid>
            <category><![CDATA[closure]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Mon, 09 Jan 2023 16:04:34 GMT</pubDate>
            <atom:updated>2023-01-09T16:08:05.820Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*axdDm2YgP3hwE2ich2KGmQ.png" /></figure><p>‘코어 자바스크립트’를 읽던 도중 Redux에서 미들웨어를 정의할 때 ‘Currying’을 사용하고 있다는 것을 발견했다.</p><pre>const loggerMiddleware = storeAPI =&gt; next =&gt; action =&gt; {<br>  console.log(&#39;dispatching&#39;, action)<br>  let result = next(action)<br>  console.log(&#39;next state&#39;, storeAPI.getState())<br>  return result<br>}</pre><p><a href="https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware">Redux Fundamentals, Part 4: Store | Redux</a></p><p>오늘은 Redux에서 사용한 Currying’과, ‘Currying’의 핵심이라고 볼 수 있는 ‘Closure’에 대해서 살펴보고자 한다.</p><h3>1. Currying function</h3><p>‘Currying function’이란 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것을 말한다.</p><p>책에 있는 예시를 통해 살펴보자.</p><pre>const curryingFunc = function (func) {<br>  return function (a) {<br>    return function (b) {<br>      return func(a, b);<br>    };<br>  };<br>};<br><br><br>const getMaxWith10 = curryingFunc(Math.max)(10);<br>console.log(getMaxWith10(8));     // 10<br>console.log(getMaxWith10(25));    // 25</pre><ul><li>curryingFunc 에 Math.max 함수를 인자로 넘겨 내부 함수를 리턴</li><li>반환된 함수에 10을 인자로 넘겨 내부 함수를 리턴</li><li>마지막으로, 10과 비교할 인자를 넘겨 Math.max 함수에 넘겨받은 두개의 수를 인자로 하는 함수 호출값을 리턴</li></ul><p>위와 같이 ‘Currying function’을 이용하여 함수를 대기 상태로 가지고 있다가, 마지막 인자가 전달되었을 때 (필요한 인자가 전부 주어졌을 때) 원본 함수가 실행되고 결과값을 받을 수 있다.</p><p>잠깐.. 다음으로 넘어가기 전에 위에 코드의 경우 함수를 계속 중첩시켜 가독성이 좋지않으므로 ES6의 화살표 함수를 통해 가독성을 높여보자.</p><pre>const curryingFunc = func =&gt; a =&gt; b =&gt; func(a, b);</pre><p><strong>‘Currying function’를 이용하여 우리가 원하는 시점에 최종적으로 함수 실행을 할 수 있는 ‘lazy execution’을 구현할 수 있다.</strong></p><p>그럼 이제 이러한 ‘Currying function’ 가능하도록 한 JS의 ‘Closure’에 대해 알아보자.</p><h3>2. Lexical Scope</h3><p>먼저, ‘Closure’의 개념을 알기전에 ‘Lexical Scope’ 관한 이해가 필요하다.</p><p><strong>‘자바스크립트는 언어 특성상 함수가 정의되는 시점에 상위 스코프가 결정된다.</strong>’이 말의 의미를 예시를 통해 살펴보자</p><pre>function init() {<br>    var name = &quot;Mozilla&quot;;<br>    function displayName() {<br>        alert (name);<br>    }<br>    displayName();<br>}<br><br>init();</pre><p>init이라는 외부함수가 존재함고 그 내부에 displayName의 이름을 가지는 내부함수를 &#39;정의&#39;하고 있다.</p><p>displayName 함수가 init이라는 함수 내부에서 정의되고 있으므로, init 함수를 상위스코프로 가진다.</p><p>이 부분에서 중요한 점은 실행되는 시점이 중요한게 아니라 함수가 정의되는 시점을 봐야하는 것이다.</p><p>조사하면서 이해에 도움이 되는 설명이 있었는데 <strong>’자식 함수는 부모 함수를 어휘적으로 묶는 함수라고 부릅니다.’</strong>이 설명대로 displayName 자식함수가 init이라는 부모함수를 묶었고, 부모함수에 정의된 name이라는 변수에 접근할 수 있었던 것이다!</p><p>사실 ‘Lexical’이라는 단어가 항상 가슴 속 깊이 이해되지 않았다. 하지만 아래 문장을 통해 조금 더 가까워진 느낌이 들었다.</p><blockquote><em>It’s called </em>lexical<em>(or </em>static<em>) because the engine determines (at </em><a href="https://en.wikipedia.org/wiki/Lexical_analysis"><em>lexing time</em></a><em>) the nesting of scopes just by looking at the JavaScript source code, without executing it.</em></blockquote><h3>3. Closure</h3><p>이제 Lexical Scope와도 친해졌으니 ‘Closure’에 대해 알아보자.</p><p>위에 예시를 다시 한번 더 살펴보자.</p><pre>function init() {<br>    var name = &quot;Mozilla&quot;;<br>    function displayName() {<br>        alert (name);<br>    }<br>    displayName();<br>}<br><br>init();</pre><p>displayName 자식 함수가 형성한 ‘Lexical Scope’에 의해 name 이라는 변수에 접근할 수 있었던 건 이제 원래 알던 사실이 되었다.</p><p>그렇다면, 부모 함수의 바깥에서 내부함수를 실행한다면 어떻게 될까?</p><pre>function makeFunc() {<br>  var name = &quot;Mozilla&quot;;<br>  function displayName() {<br>    alert(name);<br>  }<br>  return displayName;<br>}<br><br>var myFunc = makeFunc();<br>myFunc();</pre><p>makeFunc()를 실행하면서 내부함수를 리턴해주면서 외부함수는 실행이 완료되었다. (함수의 생명주기가 끝났다.)</p><p>그렇다면 외부함수에 정의된 ‘name’ 변수 생명이 다하여 사용할 수 없을 것이라고 생각하지만 우리가 배우고 있는 이 ‘Closure (myFunc)’ 아이 덕분에 외부함수를 통해 리턴받은 내부함수 (displayName)을 실행했을 때 ‘Mozilla’가 출력되는 것을 확인할 수 있다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m3ppDx6rbIe4FUNVMvX4fA.png" /></figure><p><strong>‘Closure’ 통해 ‘Lexical Scope’를 벗어나서도 (외부에서 실행되어도) 정의된 위치에서 변수를 기억하고 있다.</strong></p><p>여기까지가 Closure의 기본적인 개념이다.</p><p>조금더 자세히 들어가자면, 자바스크립트는 C, C++ 언어와는 다르게 가비지 컬렉팅을 통해 우리가 신경쓰지 않아도 자동적으로 메모리 관리를 해주고 있다.</p><p>위의 예시에서 함수의 실행 컨텍스트가 끝났을 때 가비지 컬렉터에 의해 변수가 소멸되어야 하지만, ‘Closure’를 통해 가비지 컬렉터 수집 대상에서 제외된다.</p><p>한마디로 함수 내부의 정의된 변수가 가비지 컬렉터에게<strong> “아직 나를 참조하고 있는 친구가 있어 그러니깐 아직 메모리 해제하지 말아줘”</strong> 라고 이야기 하는것이라고 생각하면 될 거 같다.</p><h3>4. 결론</h3><p>Redux에서는 함수형 프로그래밍을 지향하고 그 결과로 미들웨어를 정의할 때 ‘Currying’을 사용하고 있다.</p><p><a href="https://redux.js.org/faq/design-decisions#why-does-the-middleware-signature-use-currying">Design Decisions | Redux</a></p><p>Redux의 logger 미들웨어를 통해 위에서 배운 ‘Closure’를 어떻게 활용하였는지 살펴보자</p><pre>const loggerMiddleware = storeAPI =&gt; next =&gt; action =&gt; {<br>  console.log(&#39;dispatching&#39;, action)<br>  let result = next(action)<br>  console.log(&#39;next state&#39;, storeAPI.getState())<br>  return result<br>}<br><br>const middlewareEnhancer = applyMiddleware(loggerMiddleware)<br>const store = createStore(rootReducer, middlewareEnhancer)<br>const dispatchResult = store.dispatch({type: &#39;some/action&#39;})<br>console.log(dispatchResult)</pre><ul><li>store는 Redux를 사용할 때 처음 생성되고 이 때 인자로 전달된다</li><li>next인자는 dispatch 의미를 가지는데 store와 비슷하게 생성된 이후로 바뀌지 않는 속성이다.</li><li>결국, action type에 따라 결과가 매번 달라진다.</li><li>action을 인자로 받아 <strong>외부에서 실행되었을 때</strong>도 (store.dispatch({type: &#39;some/action&#39;})) <strong>‘Closure’를 통해 ‘Lexical Scope’에 캡쳐된 store, next에 접근하여 사용할 수 있는것이다.</strong></li><li>이후에도 다양한 타입의 action만을 인자로 받아서 ‘Currying function’을 실행할 수 있다.</li></ul><p>위에 과정을 살펴본대로 ‘Currying’은 ‘Closure’ 덕분에 가능한 것이다.</p><p><strong>이렇게 Redux에서는 ‘Clousre’를 활용하는 ‘Currying’를 통해 우리가 원하는 시점에 함수를 실행하는 ‘lazy execution’을 가능하게 하였고, 함수형 프로그래밍의 정수를 보여 주었다.</strong></p><h3>5. 참고 자료</h3><ul><li><a href="https://dmitripavlutin.com/simple-explanation-of-javascript-closures/">A Simple Explanation of JavaScript Closures</a></li><li><a href="https://dmitripavlutin.com/javascript-closures-interview-questions/">7 Interview Questions on JavaScript Closures. Can You Answer Them?</a></li><li><a href="https://im-developer.tistory.com/106">[JS/클로져] 자바스크립트의 Lexical scoping과 Closure (2)</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2583d351222b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Axios랑 친해져보자]]></title>
            <link>https://medium.com/@jvito11904/axios%EB%9E%91-%EC%B9%9C%ED%95%B4%EC%A0%B8%EB%B3%B4%EC%9E%90-f70f051a160b?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/f70f051a160b</guid>
            <category><![CDATA[network]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[axios]]></category>
            <category><![CDATA[asynchronous]]></category>
            <category><![CDATA[fencing]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Thu, 20 Oct 2022 17:05:07 GMT</pubDate>
            <atom:updated>2022-10-20T17:06:47.223Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qcSlUaRTMWxU2sCfInvBMA.jpeg" /><figcaption>노들섬 야경</figcaption></figure><p>클라이언트와 서버간의 데이터를 주고 받기 위해 비동기 HTTP 통신을 하게 된다. 대표적인 방식으로는 XMLHttpRequest, Fetch API, Axios가 존재한다. 오늘은 이 중에서 Axios와 조금 더 친해져보고 한다.</p><blockquote><em>XMLHttpRequest, Fetch API, Axios에 기본적인 내용들을 아래에 글을 참고하면 좋을 거 같다.</em></blockquote><blockquote><a href="https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-AJAX-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EB%B0%8F-%EC%9D%91%EB%8B%B5-fetch-api-%EB%B0%A9%EC%8B%9D"><em>[JS][AJAX] 📚 서버 요청 및 응답 (자바스크립트 fetch API)</em></a></blockquote><blockquote><a href="https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-%EC%84%A4%EC%B9%98-%EC%82%AC%EC%9A%A9"><em>[AXIOS] 📚 axios 설치 &amp; 특징 &amp; 문법 💯 정리</em></a></blockquote><blockquote><a href="https://axios-http.com/docs/api_intro"><em>Axios API</em></a></blockquote><h3>1. Axios</h3><p>Axios는 자바스크립트 내장 라이브러리인 ‘Fetch API’보다 조금 더 개발자 편의를 봐주는 다양한 기능을 제공하는 ‘서드파티 라이브러리’이다.</p><p>Axios의 주요 기능을 살펴보자</p><ul><li>브라우저를 위해 <a href="https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest">XMLHttpRequests</a> 생성</li><li>node.js를 위해 <a href="http://nodejs.org/api/http.html">http</a> 요청 생성</li><li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a> API를 지원</li><li><strong>요청 및 응답 인터셉트</strong></li><li>요청 및 응답 데이터 변환</li><li><strong>요청 취소</strong></li><li><strong>JSON 데이터 자동 변환</strong></li><li><a href="https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8_%EA%B0%84_%EC%9A%94%EC%B2%AD_%EC%9C%84%EC%A1%B0">XSRF</a>를 막기위한 클라이언트 사이드 지원</li></ul><p>Fetch API와 Axios의 차이를 보여주는 간단한 예시를 살펴보자.</p><pre>async function executeRequest() {<br>  const response = await fetch(&#39;/movies.json&#39;);<br>  const moviesJson = await response.json();<br>  console.log(moviesJson);<br>}<br>executeRequest(); <br>// logs [{ name: &#39;Heat&#39; }, { name: &#39;Alien&#39; }]</pre><p>Fetch API로 요청시 응답이 성공적이다면, 응답을 JSON으로 추출하는 코드를 직접 작성해야 한다.</p><p>Axios의 경우 조금 더 단순하게 처리할 수 있다.</p><pre>async function executeRequest() {<br>  const moviesJson = await axios(&#39;/movies.json&#39;);<br>  console.log(moviesJson);<br>}<br>executeRequest(); <br>// logs [{ name: &#39;Heat&#39; }, { name: &#39;Alien&#39; }]</pre><p>axios의 응답은 실제 JSON 객체를 반환한다. <strong>‘Fetch API’와는 다르게 JSON으로 추출하는 코드를 명시할 필요가 없어졌다.</strong></p><p><strong>또한 타임아웃 기능을 제공한다.</strong></p><p>우리가 Exprees에서 작업할 때 특정 endpoint에 응답 관련 로직을 작성하지 않고, 해당 endpoint에 요청을 보내면 서버가 응답을 주기전까지 클라이언트가 무한정으로 대기하고 있는 현상을 볼 수 있을 것이다.</p><p>이러한 불필요한 지연을 없애기위해 axios에서는 특정 시간이 경과했을 때 요청을 취소할 수 있다.</p><pre>const instance = axios.create({<br>  timeout: 30000,<br>});</pre><p>위에 예시와 같이 Axios의 편리한 기능을 제공하는데, 이번 실습에서는 ‘JSON 데이터 자동 변환’, ‘요청 및 응답 인터셉트’ 기능을 활용하였다.</p><p><strong>그 중 ‘요청 및 응답 인터셉트’ 기능에 대해 중점적으로 살펴보고자 한다.</strong></p><h3>2. 실습</h3><p>Express 서버에 API 요청을 담당하는 axios 코드를 별도의 모듈로 분리하여 관리하였다.</p><pre>import axios from &quot;axios&quot;;</pre><pre>const API = axios.create({ baseURL: &quot;&lt;http://localhost:8000&gt;&quot; });</pre><pre>API.interceptors.request.use((req) =&gt; {<br>  if (localStorage.getItem(&quot;profile&quot;)) {<br>    req.headers.Authorization = `Bearer ${<br>      JSON.parse(localStorage.getItem(&quot;profile&quot;)).token<br>    }`;<br>  }</pre><pre>  return req;<br>});</pre><pre>API.interceptors.response.use(<br>  (res) =&gt; {<br>    return res;<br>  },<br>  (err) =&gt; {<br>    if (err.response.status === 401) window.location.href = &quot;/login&quot;;</pre><pre>    return Promise.reject(err);<br>  }<br>);</pre><pre>export const login = (firebaseToken) =&gt;<br>  API.post(<br>    &quot;/login&quot;,<br>    {},<br>    {<br>      headers: {<br>        Authorization: firebaseToken,<br>      },<br>    }<br>  );</pre><pre>export const fetchDocs = () =&gt; API.get(&quot;/docs&quot;);<br>export const fetchDoc = (id) =&gt; API.get(`/docs/${id}`);<br>export const createDoc = (newDoc) =&gt; API.post(&quot;/docs&quot;, { ...newDoc });</pre><ul><li>baseURL을 설정하여 모든 요청이 Express 서버를 향하게 해주었다.</li><li>‘axios’ 인스턴스를 생성</li></ul><p>이 부분이 중요하다.</p><pre>API.interceptors.request.use((req) =&gt; {<br>  if (localStorage.getItem(&quot;profile&quot;)) {<br>    req.headers.Authorization = `Bearer ${<br>      JSON.parse(localStorage.getItem(&quot;profile&quot;)).token<br>    }`;<br>  }</pre><pre>  return req;<br>});</pre><ul><li>사용자가 이미 서버에 Firebase Token을 이용하여 ‘<strong>Authentication(인증)’ 후</strong> JWT 토큰을 발급 받았다면 클라이언트단 로컬스토리지에 해당 JWT 토큰이 보관되어 있다.</li><li>리액트에서 서버로 보내는 모든 HTTP request를 가로채어 로컬스토리지에 JWT 토큰이 존재한다면, HTTP header(Authorization)에 토큰을 심어준다.</li><li>서버에서는 리액트단에서 보낸 HTTP request header(Authorization)에 존재하는 JWT 토큰을 이용하여 ‘<strong>Authorization(인가)’</strong> 절차를 밟는다.</li></ul><blockquote><strong>Bearer</strong></blockquote><blockquote>토큰 앞에 붙는 ‘<strong>Bearer</strong>’는 인증 타입에 관한 명시이다. OAuth를 위해서 고안된 방법이고, <a href="https://tools.ietf.org/html/rfc6750">RFC 6750</a>에 표준명세서가 존재한다. OAuth 토큰을 위해 고안된 인증 타입이지만, JWT를 위한 인증 타입이 명시적으로 정해지지 않아 JWT을 사용할 때 ‘Bearer’ 인증 타입을 자주 사용한다.</blockquote><p>마찬가지로 HTTP response에도 인터셉터 기능을 적용할 수 있다.</p><p>만약, 인증되지 않은 사용자가 요청을 보내거나 JWT 토큰에 만료기한이 지났다면 서버에서 클라이언트단에서 보내는 요청을 거부할 수 있다. (401 Status Unauthorized) <strong>이 경우 우리는 사용자가 다시 유효한 JWT 토큰을 발급 발을 수 있도록 로그인 페이지로 리다이렉트 시켜주어야 한다.</strong></p><p>먼저 기존의 axios를 이용하여 위의 동작을 구현했을 때 아래와 같은 코드로 가능하다.</p><pre>const fetchData = async () =&gt; {<br>  try {<br>    const { data } = await axios.get(&#39;protected/endpoint&#39;);<br>    return data;<br>  } catch (error) {<br>    console.log(error);<br>    window.location.href = &#39;/&#39;;<br>  }<br>}</pre><p>하지만, 많은 페이지과 존재하고 하나의 페이지에서 여러 요청을 보내야 한다면, <strong>다수의 엔드포인트에 위의 코드를 작성해주어야 한다.</strong></p><p>이런 번거로운 작업을 우리 대신 처리해주는 인터셉터를 이용해보자.</p><pre>API.interceptors.response.use(<br>  (res) =&gt; {<br>    return res;<br>  },<br>  (err) =&gt; {<br>    if (err.response.status === 401) window.location.href = &quot;/login&quot;;</pre><pre>    return Promise.reject(err);<br>  }<br>);</pre><ul><li>서버에서 해당 HTTP request가 ‘<strong>Authorization(인가)’이 실패했다면 401(Unauthorized)로 응답</strong></li><li>/login 페이지로 리다이렉트</li></ul><p><strong>또한 클라이언트단 비동기 로직 에러핸들링이 굉장히 중요하다고 생각한다.</strong></p><p>서버에서의 에러핸들링도 중요하지만,<strong> 우리의 유저에게 보안에 관련된 내용을 빼고 유저가 에러를 인지하는데 필요한 정보만 전달하여 소통하는 것 또한 중요하다고 생각한다. </strong>서버에서 응답한 에러 Status에 따라 클라이언트단에서 어떤 화면 또는 메시지를 유저에게 보여줄지를 결정할 수 있다.</p><p>그러한 원할한 소통을 위해서는 클라이언트단 에러핸들링 로직이 잘 관리되어야 하고, <strong>axios 인터셉터 기능이 우리에게 에러핸들링 로직이 한곳에 잘 관리될 수 있도록 도와준다.</strong></p><p>이번 실습에서는 ‘401’에러에 대해서만 대응했지만, <strong>확장하여 여려 에러에 대응할 수 있을 것이다.</strong></p><p>위에서 ‘axios’ 라이브러리를 이용하여 정의한 ‘API 인스턴스’를 이용하여 비동기 요청을 담당하는 ‘redux thunk’에서 성공적으로 API를 호출할 수 있었다.</p><pre>import * as api from &quot;../api&quot;;</pre><pre>export const socialLogin = createAsyncThunk(&quot;auth/login&quot;, async (user) =&gt; {<br>  const { token } = await user.getIdTokenResult();<br>  const response = await api.login(token);</pre><pre>  return response.data;<br>});</pre><pre>export const getDocs = createAsyncThunk(&quot;docs/getDocs&quot;, async () =&gt; {<br>  const response = await api.fetchDocs();<br>  return response.data;<br>});</pre><pre>export const createDoc = createAsyncThunk(&quot;docs/addNewDoc&quot;, async (newDoc) =&gt; {<br>  const response = await api.createDoc(newDoc);<br>  return response.data;<br>});</pre><ul><li>API관련 인증 헤더,에러핸들링은 API모듈에서 담당</li><li>요청을 실행하고 reducer에 응답을 전달하는 로직은 Thunk에서 담당</li></ul><p>Axios에서 중요한 점은 <strong>HTTP request, response를 인스턴스를 이용하여 하나의 객체처럼 유기적으로 이용할 수 있다는 점</strong>이라고 생각한다.</p><h3>3. 마치며</h3><p>Fetch API와 Axios를 비교하고, Axios에서 제공하는 다양한 기능들 중에 ‘<strong>요청 및 응답 인터셉트’를 중점적으로 이용해보았다.</strong></p><p>마무리에 한가지 중요한 사실이 있다.</p><p>결과적으로 Axios라는 친구가 Fetch API 보다 더 좋고, 만능처럼 보이지만 결코 그렇지 않다.</p><ul><li>JS 내장 라이브러리인 ‘Fetch API’ 와는 다르게 ‘Axios’는 앱 번들 크기를 늘린다.</li><li>Axios는 <strong>서드파티 라이브러리</strong>이다.</li><li>이 말은 아직 해결되지 않은 버그들이 존재할 가능성이 높다는 것이다.</li></ul><p><strong>실제로 이전 회사에서 일하던 시절에 C++에서 공식 라이브러리 격인 서드파티 라이브러리 ‘boost’를 사용하면서 라이브러리 버그를 겪은 경험이 존재한다.</strong></p><p>MongoDB Server와 Client 사이에 패킷을 분석하여 해당 패킷이 금지된 쿼리 포함하고 있는지 확인하는 기능을 구현할 때 금지된 쿼리(JSON)와 요청 쿼리(JSON) 비교 로직을 boost에서 제공하는 ‘JSON comparison’을 사용했다.</p><p>하지만 JSON으로 파싱하는 과정에서 실수인 값(double)에 관해서 부동소수점 오차가 발생하여 같은 값인데 다르다고 판별하는 버그가 발생했다. (18.400000000000002 != 18.399999999999999)</p><p>결국, boost 라이브러리를 참고하여 실수(double)를 비교하는 로직에 epsilon(오차 허용 범위)를 지정하여 부동소수점 오차를 보정하도록 커스텀 하였다.</p><p>결국 모든 라이브러리가 그러하듯 Axios도 우리를 위한 은총알은 아니다. 상황에 맞게 적절하게 사용해야 한다.</p><p>타임아웃, 인터셉터 등 Axios에서 제공하는 기능들을 활용하지 않을 경우 가벼운 Fetch API를 쓰는 것이 적절하고 여러 endpoint가 존재하고 복잡한 인증 인가 절차가 있다면 Axios를 사용하는 것이 적절하지 않을까 생각해본다.</p><p><strong>Fetch API를 잘 활용하면 Axios에 주요 기능을 완벽하게 재현할 수 있다고 한다.</strong></p><p>아래에 글을 보면, decorator 패턴을 이용하여 Fetch API에서 반복적으로 응답을 JSON으로 변환하지 않고 Axios와 같이 요청시에 JSON 추출하고, 타임아웃을 걸 수 있다는 점을 확인할 수 있다.</p><p><a href="https://dmitripavlutin.com/enhance-fetch-with-decorator-pattern/">How to Greatly Enhance fetch() with the Decorator Pattern</a></p><h3>참고자료</h3><ul><li><a href="https://blog.bitsrc.io/performing-http-requests-fetch-vs-axios-b62b44fed10d">Performing HTTP Requests: Fetch Vs Axios</a></li><li><a href="https://blog.bitsrc.io/performing-http-requests-fetch-vs-axios-b62b44fed10d">Performing HTTP Requests: Fetch Vs Axios</a></li><li><a href="https://blog.logrocket.com/axios-vs-fetch-best-http-requests/">Axios vs. fetch(): Which is best for making HTTP requests? - LogRocket Blog</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f70f051a160b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[로그를 남겨보자]]></title>
            <link>https://medium.com/@jvito11904/%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EB%82%A8%EA%B2%A8%EB%B3%B4%EC%9E%90-19611f8386d9?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/19611f8386d9</guid>
            <category><![CDATA[logging]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[expressjs]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Tue, 27 Sep 2022 17:47:38 GMT</pubDate>
            <atom:updated>2022-09-27T17:49:29.859Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XmaChYiipGD246zB12at_Q.png" /></figure><p>개인적으로 어느 프로그램이든 로그를 남겨 보관하는 것은 귀찮지만, 굉장히 중요한 일이라고 생각한다.</p><p><strong>로그를 이용하여 실제 서비스를 시작했을 때 이용자들의 행동을 분석하고 서버 트래픽을 분석할 수 있다.</strong></p><p>멘토님께서 해주신 이야기인데, 실제로 이러한 로그를 이용하여 고객의 행동을 분석한 사례가 있다.</p><p>멘토님께서 “XXX”에서 일하실 때 어떤 고객이 결제를 하였지만 숙박예약이 되지 않았다고 항의 문의가 들어왔다.</p><p>CS(<strong>Customer Service</strong>)팀에서는 실제로 그 고객이 결제를 하였는지 확인하기 위해 개발팀에 <strong>‘로그 분석’</strong>을 요청하였다.</p><p><strong>또, 로그의 가장 중요한 역할은 서버에 에러가 발생했을 때 에러로그를 남기는 것이다.</strong></p><p>로그 시스템이 없는 서버에서 장애가 발생하여 서버가 다운되었을 때, 어떤 이유로 서버가 다운되었는지 분석 하기 굉장히 어렵고 시간이 많이 소모된다. 그것을 분석하기 위해 로그를 남기는 코드를 추가하고, 다시 그 상황을 재현할 수 밖에 없을 것이다.</p><p><strong>전에 회사에서 일하는 시절에 서버에 장애가 발생했을 때 로그에 소중함을 느꼈다.</strong></p><p>프록시 서버를 담당하는 프로그램이였는데, 통신을 중계해주는 에이전트, 정책 및 개별 DB 프로토콜을 분석하는 에이전트가 분리되어 있어 각각의 에이전트 로그 파일이 따로 존재하였다.</p><p>QA(Quality Assurance)팀에서 보내준 장애가 발생했을 때 패킷들과 2개의 로그파일을 이용하여 방대한 코드에서 어떤 부분에서 에러가 발생했는지 수월하게 찾을 수 있었다.</p><p>패킷에 HTTP 메시지를 파싱하는 모듈에서 ‘Content-length’를 잘못 계산했고 이로인해 초기화 하지않은 메모리에 접근하는 문제였다.</p><p><strong>만약, 로그파일이 존재하지 않아</strong> 방대한 코드의 어는 부분에서 에러가 발생했을지 예측하고, 예상되는 부분에 하나하나 콘솔을 찍어가면서 디버깅을 했다면 몇일동안 회사에서 보냈을 시간을 상상하면서 간담이 서늘해진다.</p><p>오늘은 이러한 로그시스템을 간단하게 express에서 구축할 수 있는 <strong>‘winston’, ‘morgan’</strong> 라이브러리를 살펴보고, 실제 ‘<strong>AWS Elastic Beanstalk’를 이용하여 배포한 서버에 적용시켜보려 한다.</strong></p><h3>1. Winston</h3><p>윈스턴은 node에서 가장 인기있는 logging 라이브러리이다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fIiRaNxf2d66opVYYBrRlw.png" /></figure><p>윈스턴의 주요 기능을 살펴보자</p><ul><li>로그 파일 관리</li><li>로그 레벨 관리</li><li>로그 메시지 포멧 커스텀</li></ul><p><strong>우리가 express에서 주로 쓰는 reqest 요청 로깅 미들웨어인 ‘morgan’과는 다르게 ‘winston’은 로그메세지를 별도의 로그 파일에 저장할 수 있다.</strong></p><p>먼저 ‘winston’ 라이브러리를 설치해주자.</p><pre>npm i winston</pre><p>그리고 winston 구성을 설정해 주어야 하는데 나의 경우에는 /src/loaders/logger.js경로에 해당 코드를 작성했다.</p><pre>const winston = require(&quot;winston&quot;);</pre><pre>const levels = {<br>  error: 0,<br>  warn: 1,<br>  info: 2,<br>  http: 3,<br>  debug: 4,<br>};</pre><pre>const level = () =&gt; {<br>  const env = process.env.NODE_ENV || &quot;development&quot;;<br>  const isDevelopment = env === &quot;development&quot;;</pre><pre>  return isDevelopment ? &quot;debug&quot; : &quot;warn&quot;;<br>};</pre><pre>const colors = {<br>  error: &quot;red&quot;,<br>  warn: &quot;yellow&quot;,<br>  info: &quot;green&quot;,<br>  http: &quot;magenta&quot;,<br>  debug: &quot;white&quot;,<br>};</pre><pre>winston.addColors(colors);</pre><pre>const format = winston.format.combine(<br>  winston.format.timestamp({ format: &quot;YYYY-MM-DD HH:mm:ss:ms&quot; }),<br>  winston.format.colorize({ all: true }),<br>  winston.format.printf(<br>    (info) =&gt; `${info.timestamp} [ ${info.level} ]: ${info.message}`<br>  )<br>);</pre><pre>const transports = [<br>  new winston.transports.Console(),<br>  new winston.transports.File({<br>    filename: &quot;/tmp/winston/error.log&quot;,<br>    level: &quot;error&quot;,<br>  }),<br>  new winston.transports.File({ filename: &quot;/tmp/winston/all.log&quot; }),<br>];</pre><pre>const Logger = winston.createLogger({<br>  level: level(),<br>  levels,<br>  format,<br>  transports,<br>});</pre><pre>module.exports = Logger;</pre><ul><li><strong>먼저 레벨을 설정했는데 서버 실행 환경(production, dev)에 따라 출력되는 로그의 레벨을 조정할 수 있다.</strong></li><li>예를 들어 실제 서비스가 구동되는 ‘production’ 환경에서는 0,1 레벨(error, warn)에 해당하는 로그만 출력한다.</li><li>이는 필요한 로그에만 집중할 수 있게하여, 로그파일의 용량을 줄일 수 있다.</li><li>로그가 텍스트로 이루어져 있지만 사용자가 많의 서비스에 경우에는 트래픽이 많아 로그 파일의 크기가 순식간에 증가한다.</li></ul><pre>Logger.error(&quot;에러 메시지&quot;);</pre><pre>....</pre><pre>Logger.debug(&quot;디버깅 메세지&quot;);</pre><ul><li><strong>로깅 레벨을 ‘warn’(1)로 설정했다면 위의 코드에서 error 로그는 남지만 debug 로그는 남지 않을 것이다.</strong></li><li>더 자세한 로그가 필요할 때 (<strong>’dev’ 환경</strong>) 로깅 레벨을 올려 모든 로그를 확인할 수 있을 것이다.</li><li>그리고 각 로그 메시지별 색깔을 지정할 수 있다.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/406/1*-t_gY6xLjih4jfgs57uw9w.png" /></figure><ul><li>위에 코드에서 보는것과 같이 포맷 또한 커스텀이 가능하다.</li></ul><p><strong>이 부분이 중요하다.</strong></p><pre>const transports = [<br>  new winston.transports.Console(),<br>  new winston.transports.File({<br>    filename: &quot;/tmp/winston/error.log&quot;,<br>    level: &quot;error&quot;,<br>  }),<br>  new winston.transports.File({ filename: &quot;/tmp/winston/all.log&quot; }),<br>];</pre><ul><li>로그를 어떤 곳에 남길지 정의할 수 있다.</li><li><strong>먼저, 콘솔에 모든 로그가 나오게 하고 에러로그는 특별하므로 별도의 ‘error.log’파일에 저장한다.</strong></li><li>그 외에 로그들은 ‘all.log’파일에 저장한다.</li></ul><p><strong>이제 ‘winston’을 이용하여 필요한 곳에 로그를 남겨보자!</strong></p><pre>module.exports = async ({ expressApp }) =&gt; {<br>  await mongooseLoader();<br>  Logger.info(&quot; ✅  MongoDB loaded and connected!&quot;);</pre><pre>  await expressLoader(expressApp);<br>  Logger.info(&quot; 🟢  Express loaded&quot;);<br>};</pre><pre>exports.errorHandler = (err, req, res, next) =&gt; {<br>  res.locals.message = err.message;<br>  res.locals.error = req.app.get(&quot;env&quot;) === &quot;development&quot; ? err : {};</pre><pre>  Logger.error(<br>    `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${<br>      req.method<br>    } - ${req.ip}`<br>  );</pre><pre>  res.status(err.status || 500);<br>  res.render(&quot;error&quot;);<br>};</pre><p><strong>‘winston’을 이용하여 서비스 상황에 맞게 로그를 기록하고 자신에게 필요한 정보를 포맷을 커스텀하여 저장할 수 있다.</strong></p><h3>2. Morgan</h3><p>Morgan은 익숙할 것이라 예상한다.</p><p><strong>‘Morgan’은 reqest 요청 로깅 미들웨어이다.</strong></p><p>이 라이브러리를 ‘Winston’과 결합해보자.</p><p>미들웨어를 새로 정의해 주었다.</p><pre>const morgan = require(&quot;morgan&quot;);<br>const Logger = require(&quot;../../loaders/logger&quot;);</pre><pre>const stream = {<br>  write: (message) =&gt; Logger.http(message),<br>};</pre><pre>const skip = () =&gt; {<br>  const env = process.env.NODE_ENV || &quot;development&quot;;</pre><pre>  return env !== &quot;development&quot;;<br>};</pre><pre>const morganMiddleware = morgan(<br>  &quot;:method :url :status :res[content-length] - :response-time ms&quot;,<br>  { stream, skip }<br>);</pre><pre>module.exports = morganMiddleware;</pre><ul><li>stream 을 보면 우리가 방금 생성한 <strong>‘winston’ 로거에 해당 ‘morgan’ 로그를 http 레벨로 기록하는 것을 볼 수 있다.</strong></li><li><strong>morgan도 요청 메시지 커스텀이 가능하다.</strong></li></ul><p>이제 라우터 상위에 ‘morganMiddleware’를 장착함으로써 모든 요청을 콘솔에 나오게 하고, 로그 파일에 저장할 수 있다.</p><pre>module.exports = () =&gt; {<br>  const app = Router();</pre><pre>  app.use(morganMiddleware);<br>  app.use(checkLoginState);</pre><pre>  signup(app);<br>  login(app);<br>  main(app);<br>  votings(app);<br>  myVotings(app);</pre><pre>  return app;<br>};</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/578/1*4r4_N5O3xMMj2eIBTbDzVA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/671/1*8li6Fq_a3GZcqCA5IiAhUA.png" /></figure><h3>3. 서버에 적용</h3><p>‘<strong>AWS Elastic Beanstalk’를 이용하여 서버를 배포하였다.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*n-34TAQ3CiDnJMIeFKEaUw.png" /></figure><p><strong>‘AWS Elastic Beanstalk’에서 어는 정도 로그를 제공해주지만 내가 원하는 포맷이 아니고, 콘솔말고는 별도의 로그 파일을 분리해주지 않는다.</strong></p><p>이제 우리가 만든 커스텀 로그 파일을 ‘AWS Elastic Beanstalk’에 로그를 요청해서 받아야한다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LyjG5xiLbzwdcQBJEhuBIw.png" /></figure><p>그러기 위해서는 몇가지 작업이 필요하다.</p><p>‘AWS Elastic Beanstalk’는 EC2(Linux) 장비를 이용하여 우리의 코드를 웹 애플리케이션으로 배포해주는데, 우리가 만든 로그파일은 위에서 ‘winston’에서 지정한 경로 { filename: &quot;/tmp/winston/all.log&quot; } 로 EC2 서버에 저장될 것이다.</p><p>하지만, 로그 요청을 보냈을 때 우리 로그파일은 넘어오지 않을 것이다.</p><p><strong>왜냐하면 EC2 장비에서 기본적으로 보내주는 로그 번들에 우리의 로그 파일 경로가 링크 되어있지 않기 때문이다.</strong></p><p>리눅스 장비에 이 경로/opt/elasticbeanstalk/task에 우리 로그파일 경로를 링크 걸어주어야 한다.</p><p>만약 우리가 리눅스 장비를 직접적으로 운영하고 있다면 터미널에서 명령어를 이용하여 간단하게 해결할 수 있지만, AWS Elastic Beanstalk는 우리가 직접적으로 EC2(Linux) 장비를 건드리지 않고, 간편하게 웹 애플리케이션 배포하기 위한 취지로 만들어져서 EC2 설정을 직접적으로 건드리지 못한다.</p><p>그래서 EC2 설정을 바꿀 수 있는 커맨드를 간접적으로 실행시켜주어야 한다. 우리 코드 디렉토리 최상위에 ‘.ebextensions’ 폴더에 .config 파일에 커맨드를 작성하여 서버가 배포될 때 실행시킬 수 있다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/350/1*w19s_98wssuEe22giYetMg.png" /></figure><p><a href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.logging.html#health-logs-extend">Viewing logs from Amazon EC2 instances in your Elastic Beanstalk environment</a></p><p>이제 링크를 걸어주는 명령어를 작성해보자.</p><pre>files:<br>  &quot;/opt/elasticbeanstalk/tasks/taillogs.d/cloud-init.conf&quot; :<br>    mode: &quot;000755&quot;<br>    owner: root<br>    group: root<br>    content: |<br>      /tmp/winston/*<br>  <br>  &quot;/opt/elasticbeanstalk/tasks/bundlelogs.d/applogs.conf&quot; :<br>    mode: &quot;000755&quot;<br>    owner: root<br>    group: root<br>    content: |<br>      /tmp/winston/*</pre><ul><li>taillogs 경로 설정파일에 링크를 걸어 ‘마지막100줄 요청’을 보냈을 때 우리의 로그가 포함되게 한다.</li><li>bundlelogs 경로 설정파일에 링크를 걸어 ‘전체 로그 요청’을 보냈을 때 우리의 로그가 포함되게 한다.</li></ul><p>이제 로그를 요청하여 우리의 로그를 성공적으로 받아오는지 확인해보자.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ImWYkhtLRhPdZSKxpro2cg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tw8cP_px0j5rq0Y8SWjy1w.png" /></figure><p><strong>100줄 로그, 전체 로그에서 우리 로그 파일이 포함되어 있는것을 확인할 수 있다.</strong></p><h3>4. 마치며</h3><p>node에서 제공해주는 고마운 로깅 라이브러리 ‘winston’, ‘morgan’를 이용하여 로그 시스템을 구축해보았다.</p><p>그리고 ‘AWS Elastic Beanstalk’ 배포한 서버에서 우리 로그 시스템을 적용할 수 있도록 해보았다.</p><p><strong>이제 필요에 따라 로그 포맷을 확장하고, 필요한 정보를 우리 입맛에 맞게 저장할 수 있게되었다.</strong></p><p>그리고 로그 시스템에서 또 중요한 고민사항이 있는데 바로 ‘<strong>Log Rotation’ 이다.</strong></p><p>만약 실서비스 하는 서버에서 로그를 계속 쌓아두고 있다면 언젠가 서버의 용량이 가득 찰 것이다.</p><p>이는 서버 성능 저하를 유발하고, 나아가 서버가 다운될 수 있는 위험이 존재한다.</p><p>그래서 우리는 로그가 꽉 차지 않도록, 과거에 로그를 주기적으로 지워주어야 한다.</p><p>하지만, ‘AWS Elastic Beanstalk’ 우리를 위하여 <strong>Log Rotation을 해준다.</strong></p><blockquote><strong><em>Log rotation settings on Linux</em></strong></blockquote><blockquote><em>On Linux platforms, Elastic Beanstalk uses </em><em>logrotate to rotate logs periodically. If configured, after a log is rotated locally, the log rotation task picks it up and uploads it to Amazon S3. Logs that are rotated locally don&#39;t appear in tail or bundle logs by default.</em></blockquote><p><strong>로그 시스템의 중요성은 우리가 운전할 때 블랙박스가 없다고 생각하면 와닿을거라 생각한다…!</strong></p><h4>참고자료</h4><ul><li><a href="https://www.digitalocean.com/community/tutorials/how-to-use-winston-to-log-node-js-applications">How To Use Winston to Log Node.js Applications on Ubuntu 16.04 | DigitalOcean</a></li><li><a href="https://xively.tistory.com/21">[node.js express] winston으로 node console log 관리하기. winston 사용 설정 자세한 설명!!</a></li><li><a href="https://aws.amazon.com/ko/premiumsupport/knowledge-center/elastic-beanstalk-customized-log-files/">Elastic Beanstalk에서 로그 파일 사용자 지정</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=19611f8386d9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JSConf Korea 2022 후기- 생애 첫 컨퍼런스]]></title>
            <link>https://medium.com/atant/jsconf-korea-2022-%ED%9B%84%EA%B8%B0-%EC%83%9D%EC%95%A0-%EC%B2%AB-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-1c8c455fc5bf?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/1c8c455fc5bf</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[jsconf]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[culture]]></category>
            <category><![CDATA[conference]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Tue, 20 Sep 2022 17:51:48 GMT</pubDate>
            <atom:updated>2022-09-21T11:17:32.227Z</atom:updated>
            <content:encoded><![CDATA[<p>이번에 생애 처음으로 참여하는 개발자 컨퍼런스로 JSConf Korea 2022에 다녀왔습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YKe6nO3lhwjjNKy6ekVVNQ.png" /><figcaption>노들섬에서 진행한 JSConf Korea 2022</figcaption></figure><p>평소에 이런 컨퍼런스를 영상으로만 봐서 현장 분위기가 어떨지 또 어떻게 진행되는지 굉장히 궁금했습니다. 그런데 이번에 운 좋게도 각국에서 펼쳐지는 <strong>JSConf라는 큰 행사에 참여할 수 있게 되었습니다!</strong> 작년에는 코로나 때문에 온라인으로 진행되었는데 이번에는 다행히도 <strong>오프라인으로 진행하였습니다.</strong></p><p><strong>자바스크립트를 좋아하고</strong> 관심이 많은 분이 모이기 때문에 그분들과 커뮤니케이션 할 수 있는 기회가 주어진다는 것에 굉장히 설레었습니다.</p><p>16일(금), 17일(토) 이틀에 걸쳐 진행하였고, 메인 발표가 진행되는 다목적 홀, 워크샵과 후원사 발표가 진행되는 2개의 세미나실로 구성되었습니다. 오프라인으로 진행되는 만큼 주최 측에서 스탬프 찍기, 배지 만들기 등 여러 이벤트를 준비하였는데 <strong>나중에 글에서 하나하나씩 소개해 드리겠습니다.</strong></p><h3><strong>Opening 👋</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XZtofyHeLdfw0JAhkM3AUw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LarVeR_T-d-Wqc3IO8OurA.jpeg" /><figcaption>10시 30분 컨퍼런스 시작전 참가자 등록</figcaption></figure><p>참가자 등록하고 다목적 홀에서 오프닝 공연과 이번에 <strong>JSConf Korea 후원사</strong>를 소개하면서 시작했습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uig_uLWGMP9qW7yA4UlqZg.jpeg" /><figcaption>JSConf Korea 2022 후원사</figcaption></figure><p>메인홀 뒤쪽에는 후원사별로 부스가 있었고, 각 회사에 대한 소개와 채용 과정에 대해 들을 수 있었습니다. 그리고 <strong>JS 문법 관련 퀴즈를 풀고 경품을 받거나, 응모 뒤에 경품 추첨</strong> 등 세심하게 신경 쓴 부분들이 많았습니다.</p><p>그리고 드디어 본격적으로 발표가 시작되었습니다. 발표자와 발표 내용에 대한 <strong>간단한 소개를 아래에서 확인할 수 있습니다.</strong></p><p><a href="https://2022.jsconf.kr/program?day=1">프로그램 | JSConf Korea 2022 🌈</a></p><h3>발표 🖥</h3><p>이번 JSConf 발표는 주제가 다채로웠고 모든 발표가 배울 점이 있었습니다. 그중 <strong>가장 기억에 남는 발표 3가지</strong>를 선정하여 간단하게 정리해 공유하고자 합니다.</p><h4><strong>React로 영수증 출력해보기: 자바스크립트로 POS를 만든다고? — 나석주</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m0VtMlsYwxRiIcwsA271ew.jpeg" /><figcaption>토스플레이스에 재직중인 나석주님의 발표</figcaption></figure><p>이번 컨퍼런스에서 가장 좋았던 세션입니다.</p><p>자바스크립트로 인쇄에 필요한 바이트 데이터를 다루고 <strong>JSX 문법</strong>을 활용하는 React를 통해 <strong>선언적으로</strong> 그리고 <strong>React스럽게</strong> POS기 코드를 작성한 방법에 대한 세션입니다.</p><p>JS에서도 <strong>바이너리 데이터</strong>를 다루기 위한 Buffer를 만들 수 있지 않을까? 라는 생각은 평소에 있었지만 직접 구현해보지 못했습니다. 이 발표에서 JS TypedArray인 <strong>Uint8Array</strong>을 활용하여 POS기로 인쇄할 데이터, 인쇄 관련 명령어 들을 Buffer에 쌓을 방법에 대해 알게 되었습니다. 거기에 쌓인 Buffer를 JSX 문법과 React 컴포넌트를 활용하여 <strong>선언적으로</strong> 코드를 작성할 수 있도록 한 부분이 인상 깊게 다가왔습니다. 특히, JSX 코드를 재귀적으로 돌며 Uint8Array를 반환하는 render 함수를 커스텀 한 부분을 듣고 입을 다물지 못했습니다…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wHzCF-Xf7Pg47yetSL8JXg.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SLl1MBgfVVwCLI9F1A7w5g.jpeg" /><figcaption>React를 활용하여 오프라인과 온라인을 대응하는 모습</figcaption></figure><p>발표를 다 듣고 가장 먼저 든 생각은 개인 프로젝트에서 이런 아이디어를 생각해서 진행했으면 정말 좋았겠다고 느꼈습니다. React와 JSX 문법을 깊게 공부할 수 있고 render 함수, JS로 바이너리 데이터를 다루는 방법까지 여러 방면에서 교훈을 얻을 수 있는 프로젝트가 아니었을까 생각해보게 되었습니다.</p><p>이것을 확장하여 오프라인에 영수증이 출력되기 전에 웹 브라우저에서 사용자가 인생네컷과 같은 스티커 사진처럼 <strong>다양하게 커스텀</strong> 할 수 있는 기능을 제공해주면 좋겠다고도 생각해 보았습니다.</p><blockquote>제가 작성한 발표에 대한 설명은 간략한 설명이었고, 디테일 한 설명과 코드를 보실 분들은 <strong>아래 링크를 통해 지식을 얻을 수 있습니다</strong>.</blockquote><p><a href="https://github.com/seokju-na/react-thermal-printer">GitHub - seokju-na/react-thermal-printer: React for thermal printing</a></p><h4><strong>성공적으로 실패하는 방법 — 엘리노어 럼지</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jqxChWjHlUGYtyjSLf8wrw.jpeg" /></figure><p>개발자로 지내면서 바보처럼 보일까 봐 질문을 꺼리고 내가 생각하거나 이전의 성과가 <strong>실패로 밝혀지는 것에 두려움을 느끼는 가면 증후군</strong>에 관한 세션입니다.</p><p>개발자로 일할 때 모르는 부분이 생겨 막히는 상황이 발생할 수 있고, 커뮤니케이션할 때 모르는 단어가 들릴 때가 종종 있었을 것입니다. 발표에서는 이러한 상황에서 <strong>“I don’t know”</strong>라고 말할 수 있는 용기가 중요하다고 이야기합니다. 또한 모르는 것을 인정하지 않고 도움을 요청하는 것에 두려움을 오래 느낄수록 <strong>가면 증후군에 걸릴 가능성이 커진다고</strong> 이야기합니다. 발표에서 특히 공감 가는 사례가 있었는데요 발표자분의 인턴 생활 때 API 응답을 캐싱 관련한 궁금증이 생겼지만, 혹시 본인이 틀렸을 가능성 때문에 쉽게 다른 사람에게 자신의 의견을 말할 수 없었다고 하였습니다. 하지만 용기를 내서 그 의견을 말했을 때 <strong>알고 보니 팀에 동료들이 놓치고 있는 부분이었다는 것입니다.</strong> 내 의견이 항상 옳은 게 아닌 것처럼 다른 사람의 의견이 항상 옳다고 가정하여 내 의견을 말하지 않는 것을 지양해야 한다고 느꼈습니다.</p><p>발표를 듣고 내가 모르는 것이나 실수를 밝히는 게 최악의 상황이 아니라 <strong>실수를 인정하지 않고 그대로 넘어가는 것이 최악의 상황</strong>이라고 생각하였습니다. 또한 실수를 인정했을 때 그것을 보완하여 <strong>성장할 기회</strong>가 생긴다는 말에 공감이 갔습니다.</p><p>이 발표가 기술적인 내용은 아니지만 기억에 남았던 이유는 최근에 제가 이런 가면 증후군에 걸린 것은 아닐까 고민해본 적이 있어서입니다. 😭 제가 모르는 것을 다른 사람들에게 밝히는 것이 <strong>두렵고 부끄러운 적이 많았습니다</strong>. 하지만 이 발표를 듣고 지금 제가 모르는 것을 인정하고 조사 후에 알려드린다고 말하는 것이 <strong>저와 우리 팀을 위한 효율적인 방향이라고 생각해보았습니다.</strong></p><blockquote>발표 PDF는 아래 링크를 통해 볼 수 있습니다.</blockquote><p><a href="https://github.com/eleanorRumsey/jsConfKorea2022">GitHub - eleanorRumsey/jsConfKorea2022: Content from my presentation in Seoul on September 16, 2022</a></p><h4><strong>스타트업 개발팀에서 협업하기 — 홍석주</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZDYdPTxNTsPIiYUnyVz2lg.jpeg" /><figcaption>세미나실에서 진행된 aaant팀의 CTO 홍석주님의 발표</figcaption></figure><p>초기 스타트업 <strong>aaant팀</strong>의 PM팀, 개발팀 그리고 디자인팀의 협업 방식에 대한 고민을 엿볼 수 있는 세션입니다.</p><p>개발자로 일을 하시는 분들이시라면 <strong>애자일</strong> 방법론에 대해 들어본 적이 있으실 겁니다. 하지만, 항상 개념적으로만 들어왔고 실무에서는 어떤 방식으로 진행할까 라는 궁금증이 항상 존재했습니다. 이 발표에서 <strong>aaant팀</strong>이 스쿼드를 이루어서 기획부터 QA를 거쳐 프러덕트에 들어가기까지 과정인 <strong>스프린트 방식을 간단하게 소개해 주셨습니다.</strong> 소개해주신 여러 방식 중에 중요하다고 생각한 부분은 바로 <strong>스쿼드 또는 개발팀 내부에서 Sync를 맞추는 것이였습니다.</strong> 그러기 위해 스쿼드에서 이틀에 한 번 개발팀 내부에서는 <strong>데일리로 짧고 잦은 미팅을 지향</strong>한다고 하셨습니다. 팀 프로젝트를 진행하면서 구성원들끼리 데일리로 지금 하는 일, 블로킹 된 부분을 공유하면서 Sync를 맞추었던 경험이 있어 <strong>매우 공감 가는 방법이었습니다.</strong></p><p>개발팀 협업 방식에서도 지금까지는 몰랐던 방법이 있었는데 Github Issue에 <strong>개발 전에 테스크에 대한 백그라운드나 설명에 관련된 글을 작성하여</strong> 팀원들과 공유하는 것입니다. 항상 Github Issue는 버그에 관련된 내용만 작성한다고 생각했는데 이렇게도 활용할 수 있다는 것이 신기하였습니다.</p><p>마지막으로 발표에서 기억에 남았던 내용은 개발팀에서의 개발 명세서를 보고 기능을 구현하는 것이 근본 역할이지만 <strong>프로덕트 자체에 관심을 가지고 개발해야한다는 점이었습니다.</strong></p><p>발표를 듣고 느낀 점은 협업에 관한 방법론은 앞으로도 꾸준히 발전하겠다고 생각하는데 제가 속한 팀에서 저도 협업을 <strong>더 애자일하고 효율적으로 진행할 수 있도록 고민하고 또 고민해야겠다고</strong> 생각하였습니다.</p><blockquote>제가 작성한 글에서는 간략하게 설명드렸는데 아래에 링크에서 더 자세히 aaant팀의 협업 방식을 확인할 수 있습니다.</blockquote><p><a href="https://medium.com/atant/working-in-a-startup-a81e732b12ca">Working in a Startup</a></p><blockquote>그리고 불변적인 새로운 원시 타입을 소개하는 <strong>자바스크립트에 그들이 온다: 기대되는 다음 피쳐 Records 와 Tuples 발표</strong>가 있었는데 해당 발표에 관련된 내용은 더 조사하여 따로 글을 작성할 계획입니다<strong> 🖋</strong></blockquote><h3>이벤트 🚥</h3><p>이번 JSConf Korea 2022에서는 다양한 이벤트들이 즐비하였습니다.</p><h4><strong>🧐 PopupQuiz</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eZQ0qq_tYuCTiuWc5ssy0w.jpeg" /><figcaption>발표자분의 관한 Fun Facts Quiz</figcaption></figure><p>발표 시작 전에 간단하게 발표자분을 알아갈 수 있는 시간이 있었는데요. 4개의 보기 중에 <strong>False인 보기를 고르는</strong> 문제였습니다. (처음에 True인 보기를 고르는 것으로 이해하여 당황했습니다 🙄)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3n6oFjQwpNSdJ0kjG-Y7Nw.jpeg" /></figure><p>문제를 맞힌 사람에게는 소정의 상품이 지급되었는데 운이 좋게도 한 문제를 맞혀 재미있어(?) 보이는 책을 받았습니다</p><h4><strong>🎖 배지 만들기</strong></h4><p>메인홀 뒤쪽 그리고 야외 부스에서 개발 관련 용어들이 적혀있는 종이를 잘라 배지를 만들 수 있었습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vGs48Ei45031lMdCyNP7WA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ABeVxIJm4108xA5Is0KwWQ.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0CPahPxIP04mvvKOE6Y5Ww.jpeg" /><figcaption>직접 만들어 보는 배지</figcaption></figure><p>기계에 종이와 코딩용지 그리고 클립을 넣고 펀치 하듯이 꾸욱 누르면 완성되는 배지였는데 생각보다 손기술을 요구하였습니다 🦾 잘못하면 코딩지가 떠버리는 현상이…</p><h4><strong>🎫 티겟 만들기</strong></h4><p>앞에서 소개해드린 <strong>React로 영수증 출력해보기 </strong>발표의 실제 제품을 체험할 수 있는 부스였습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J5ulrJOxXwvEKHFzcTuzcA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IKVUsZ2V0k1fXX-ini5v4A.jpeg" /><figcaption>자바스크립트로 만들어진 POS</figcaption></figure><p>맥북에 영수증에 출력하고 싶은 이름과 내용을 적고 카메라에 환하게 웃고 사진을 찍으면 POS기에서 영수증이 나왔습니다!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UetsDNt2zkR12ObmLcjtRA.jpeg" /><figcaption>영화관 포토 티켓 같은 느낌…</figcaption></figure><p>짠! 📮</p><h4><strong>💎 경품 추첨</strong></h4><p>후원사 부스별로 경품 추첨을 진행하였는데 키보드, 에어팟 맥스 등 탐나는 경품들이 많았습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9WScGXafEbGI8Ax8Qdiazg.jpeg" /><figcaption>이런 경품 당첨은 생에 처음…</figcaption></figure><p>카페에서 컨퍼런스에 참여한 분들과 커피챗을 하던 도중 1등에 당첨되었다는 전화를 받았습니다… 😆 1등 경품으로 레오폴드 키보드를 받았습니다! 감사합니다 잘 쓰겠습니다 🙏</p><h3>마치며 🌇</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eDt64brkdjPeaZgbWBm4GQ.jpeg" /><figcaption>귀가</figcaption></figure><p>생애 첫 번째 컨퍼런스로 JSConf를 갈 수 있게 되어 영광이었던 이틀이었습니다. 영어 울렁증이 있어 외국에서 온 개발자분들과 소통하지 못한 점과 제가 먼저 다른 분들에게 다가가 말을 건네지 못한 아쉬움이 있었지만, <strong>개발자로서 관심을 가지고 조사해 볼 수 있는 주제를 알게 되었던 훌륭한 발표들과</strong> 개발의 진심인 사람들과 <strong>커뮤니케이션 할 수 있는 기회를 얻을 수 있었습니다.</strong> 다음에 참여하는 컨퍼런스에서는 더 적극적으로 소통할 수 있도록 노력하겠습니다 🙋‍♂️</p><p>그럼 이만 글을 줄이겠습니다.</p><blockquote>다음 컨퍼런스는 10월 8일에 열리는 FEConf에 참여할 계획입니다.</blockquote><p><a href="https://2022.feconf.kr/">FECONF 2022</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1c8c455fc5bf" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atant/jsconf-korea-2022-%ED%9B%84%EA%B8%B0-%EC%83%9D%EC%95%A0-%EC%B2%AB-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4-1c8c455fc5bf">JSConf Korea 2022 후기- 생애 첫 컨퍼런스</a> was originally published in <a href="https://medium.com/atant">aaant</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[제목은 Express로 하겠습니다. 근데 이제 리액트를 곁들인…]]></title>
            <link>https://medium.com/@jvito11904/%EC%A0%9C%EB%AA%A9%EC%9D%80-express%EB%A1%9C-%ED%95%98%EA%B2%A0%EC%8A%B5%EB%8B%88%EB%8B%A4-%EA%B7%BC%EB%8D%B0-%EC%9D%B4%EC%A0%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8-16ec5d4e99ca?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/16ec5d4e99ca</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[monolithic-architecture]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[express]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Mon, 05 Sep 2022 06:05:34 GMT</pubDate>
            <atom:updated>2022-09-05T06:05:34.187Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/768/1*wDk-Fazo-QQKzk7JottEDQ.png" /></figure><p>오늘은 지금까지 배웠던 지식들을 활용하여 Node(Express)와 React를 합쳐보았다.</p><p>그리고 리액트 코드에서 ‘fetch’ API를 이용하여 Express에 요청하고 Express에서 mongoDB에 저장된 데이터를 가지고 와 화면에 보여주는 과정을 구현해보았다.</p><p>최종적으로 두개의 환경을 합쳤을 때 실제로 배포가 되는것까지 확인하기 위해 <strong>Heroku</strong>를 통해 배포해 보았다.</p><h3>1. 개발 환경</h3><p>먼저 client 디렉토리에는 리액트를, server 디렉토리에는 node + express 환경을 설정해주었다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/280/1*zn-q1pbCHOushrIegoMFfA.png" /></figure><p>그 후, client 에는 CRA(<strong>Create-react-app), server에는 express-generator를 이용하여 손쉽게 개발 환경을 구성하였다.</strong></p><pre>npm install -g create-react-app</pre><pre>create-react-app client</pre><pre>npm install -g express-generator</pre><pre>express server</pre><p>그리고 client 폴더에 있는 리액트 코드를 빌드하여 빌드된 파일을 server 폴더에 위치시켜줄 것이다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/556/1*6MQlHdGa2LGbDCQeyHw6hA.png" /></figure><p>그 후 Express에서 ‘build’ 폴더를 정적 디렉토리로 지정하여 리액트 ‘index.html’을 사용할 것이다.</p><p>‘<strong>build’ 폴더 내부에 있든 JS파일을 이용하여 리액트가 우리의 화면을 그려줄 것이다.</strong></p><p><strong>즉, 리액트와 Express가 하나의 서버에 존재하는 것이다.</strong></p><p>그럼 이제부터 생각하는 것을 이루어내기 위해 Server, Client 각각의 과정을 살펴보자.</p><h3>2. Server (Express)</h3><p>서버 쪽 코드는 이미 <strong>express-generator를 통하여 서버를 실행시키는데 필요한 기본적인 것들이 갖추여져 있다.</strong></p><pre>const createError = require(&quot;http-errors&quot;);<br>const express = require(&quot;express&quot;);</pre><pre>const app = express();</pre><pre>app.set(&quot;views&quot;, path.join(__dirname, &quot;views&quot;));<br>app.set(&quot;view engine&quot;, &quot;jade&quot;);</pre><pre>app.use(express.json());<br>app.use(express.urlencoded({ extended: false }));<br>app.use(express.static(path.join(__dirname, &quot;public&quot;)));<br>...</pre><p><strong>첫페이지는 서버에서 렌더링 된 html을 응답으로 주도록 하였다. (Sever Side Rendring)</strong></p><pre>app.get(&quot;/&quot;, (req, res) =&gt; {<br>  res.render(&quot;index&quot;);<br>});</pre><p>그 후 ‘/react’ 경로에는 리액트 앱을 사용한다. (리액트를 사용하는 경로라는 걸 명시하기 위해 사용한 url)</p><p>아래의 코드를 통해 리액트에서 빌드된 코드를 사용할 수 있게된다.</p><pre>app.use(&quot;/react&quot;, express.static(path.join(__dirname, &quot;build&quot;)));</pre><p>그 후 ‘/react’ 경로에 접근하였을 때 ‘index.html’파일을 응답해준다.</p><pre>app.use(&quot;/react&quot;, reactRouter);</pre><pre>reactRouter.get(&quot;/&quot;, function (req, res, next) {<br>  res.sendFile(path.join(__dirname, &quot;./build/index.html&quot;));<br>});</pre><ul><li>index.html을 보내주면 해당 파일에서 <strong>리액트 JS 파일을 요청한다.</strong></li><li><strong>그 후에는 우리가 알고있는 리액트처럼 JS파일을 이용해서 화면을 그린다.</strong></li></ul><p>그리고 서버 ‘/users’에 요청하면 <strong>MongoDB에 저장되어 있는 User 정보를 ‘JSON’으로 응답해주게 하였다.</strong></p><pre>app.use(&quot;/users&quot;, usersRouter);</pre><pre>usersRouter.get(&quot;/&quot;, async (req, res, next) =&gt; {<br>  try {<br>    const users = await User.find().lean();</pre><pre>    return res.json(users);<br>  } catch (error) {<br>    next(createError(500));<br>  }<br>});</pre><h3>2. Client (React)</h3><p>클라이언트 쪽도 CRA 명령어를 통해 이미 기본적인것들이 갖추어진 상태이다.</p><p>여기에 ‘react-router-dom’을 이용하여 기본적인 리액트 앱을 구성해보자.</p><pre>import { BrowserRouter } from &quot;react-router-dom&quot;;</pre><pre>const root = ReactDOM.createRoot(document.getElementById(&quot;root&quot;));<br>root.render(<br>  &lt;BrowserRouter basename=&quot;/react&quot;&gt;<br>    &lt;App /&gt;<br>  &lt;/BrowserRouter&gt;<br>);</pre><ul><li><strong>리액트 최상위 라우터의 기본 경로를 ‘/react’로 설정</strong></li></ul><pre>import React from &quot;react&quot;;<br>import { Link, Route, Routes } from &quot;react-router-dom&quot;;<br>import Home from &quot;./Home&quot;;<br>import Users from &quot;./Users&quot;;</pre><pre>function App() {<br>  return (<br>    &lt;div&gt;<br>      &lt;Link to=&quot;/&quot;&gt;<br>        Home<br>      &lt;/Link&gt;<br>      &lt;Link to=&quot;/users&quot;&gt;<br>        User<br>      &lt;/Link&gt;<br>      &lt;Routes&gt;<br>        &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;}&gt;&lt;/Route&gt;<br>        &lt;Route path=&quot;/users&quot; element={&lt;Users /&gt;}&gt;&lt;/Route&gt;<br>      &lt;/Routes&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><pre>export default App;</pre><p>그리고 ‘Users’ 컴포넌트에서 서버에 요청하여 DB에 저장되어 있는 유저 정보를 보여준다.</p><pre>import React, { useEffect, useState } from &quot;react&quot;;</pre><pre>function Users() {<br>  const [users, setUsers] = useState([]);</pre><pre>  useEffect(() =&gt; {<br>    const getUsers = async () =&gt; {<br>      const response = await fetch(&quot;/users&quot;);</pre><pre>      if (!response.ok) {<br>        const message = `An error has occured: ${response.status}`;<br>        throw new Error(message);<br>      }</pre><pre>      const users = await response.json();</pre><pre>      return users;<br>    };</pre><pre>    getUsers().then((data) =&gt; setUsers((prev) =&gt; prev.concat(data)));<br>  }, []);</pre><pre>  return (<br>    &lt;&gt;<br>      &lt;ul&gt;<br>        {users.map((user) =&gt; (<br>          &lt;li&gt;{user.name}&lt;/li&gt;<br>        ))}<br>      &lt;/ul&gt;<br>    &lt;/&gt;<br>  );<br>}</pre><ul><li>fetch(&quot;/users&quot;) 를 통해 Express서버에 요청 (경로를 주목할 필요가 있다)</li><li>리액트 ‘users’ state 갱신</li><li>화면에 리스트로 출력</li></ul><p>그리고 리액트 ‘package.json’에서 <strong>기본 홈페이지 경로를 ‘/react’로 설정해 주었다.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BSS-wVgrp31LArvMvVE4VQ.png" /></figure><h3>3. 합체</h3><p>지금부터 <strong>리액트와 Express를 합치기 위해 리액트에서 ‘build’ 스크립트를 통해 빌드된 폴더를 ‘server’ 폴더로 옮길것이다.</strong></p><p>그리고 ‘Heroku’배포를 위해 필요한 명령어들을 정의해 주어야 한다.</p><p>그러기 위해서는 client, server 전체를 아우르는 ‘package.json’이 필요하다.</p><pre>{<br>  &quot;name&quot;: &quot;express-react-project&quot;,<br>  &quot;version&quot;: &quot;1.0.0&quot;,<br>  &quot;description&quot;: &quot;배포시 필요한 스크립트 정의&quot;,<br>  &quot;scripts&quot;: {<br>		...<br>  },<br>  &quot;author&quot;: &quot;&quot;,<br>  &quot;license&quot;: &quot;ISC&quot;<br>}</pre><p><strong>이제 이 ‘package.json’파일을 통해 우리가 해야하는 일을 정의해보자.</strong></p><ol><li>clinet 폴더로 이동</li><li>npm ci (clinet에서 필요한 node module를 설치하기 위해)</li><li>client (리액트) 빌드 (npm run build)</li><li>빌드된 폴더를 ‘server/build’로 옮겨주기</li><li>server 폴더로 이동</li><li>npm ci (server에서 필요한 node module를 설치하기 위해)</li><li>npm run start (node ./bin/www)</li></ol><p>위 과정을 명령어 스크립트로 정리해보자.</p><pre>{<br>  &quot;name&quot;: &quot;express-react-project&quot;,<br>  &quot;version&quot;: &quot;1.0.0&quot;,<br>  &quot;description&quot;: &quot;배포시 필요한 스크립트 정의&quot;,<br>  &quot;scripts&quot;: {<br>    &quot;client-build&quot;: &quot;cd client &amp;&amp; npm ci &amp;&amp; npm run build cd ../&quot;,<br>    &quot;server-build&quot;: &quot;cd server &amp;&amp; npm ci &amp;&amp; cd ../&quot;,<br>    &quot;heroku-prebuild&quot;: &quot;npm run client-build &amp;&amp; npm run server-build &amp;&amp; mv ./client/build ./server/build&quot;,<br>    &quot;start&quot;: &quot;cd server &amp;&amp; npm run start&quot;<br>  },<br>  &quot;author&quot;: &quot;June&quot;,<br>}</pre><ul><li>heroku가 우리의 코드를 빌드할 때 ‘start’ 명령어를 실행하는데 이 때 60초가 초과되면 안된다.</li><li>heroku-prebuild 명령어를 통해 ‘start’ 명령어를 실행하기전 필요한 환경을 세팅해준다.</li></ul><p>그럼 이제 Heroku 명령어를 통해 배포를 진행해보자.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FM2XBpB6DlHyGDqhgtC5TA.png" /></figure><p>성공적으로 배포가 된것을 확인할 수 있다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DcCsjshYv8RASQPW2uJ5vA.png" /></figure><p><a href="https://express-react-project72.herokuapp.com/">이것은 Express</a></p><p>서버에 데이터 흐름을 이해하기 쉽게 <a href="http://draw.io">draw.io</a>를 이용하여 그려보았다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3rZ4MyV8cxBnVpDadLDlng.png" /></figure><h3>4. 마치며</h3><p>하나의 서버에서 Express, react가 구동되는 템플릿을 만들어 보았다.</p><p>두 가지를 따로 두고 구현했다면 <strong>CORS 정책을 신경써야 했지만 같은 ‘Origin’이기 때문에 리액트에서 Express에 요청을 보내는 것이 간단해졌다.</strong></p><p><a href="https://medium.com/@owen11904/cors%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-f26a3eb5e8a2">CORS란 무엇일까?</a></p><p>그리고 웹에서 첫페이지는 화면을 사용자에게 빠르게 보여주고, SEO에 강점이 있는 ‘<strong>SSR’</strong>, 그리고 내부에서는 페이지 새로고침이 없어 사용자 경험을 개선할 수 있는 ‘CSR’을 도입해 보았다.</p><p><a href="https://medium.com/@owen11904/csr-vs-ssr-ed30c7e20bc1">CSR vs SSR</a></p><p>그리고 만약 우리가 위에서 설정한 환경에서 클라이언트 리액트 코드를 개발할 때 리액트 <strong>‘package.json’에 proxy 값을 Express 서버로 설정해 둔다면 문제없이 라이브 코딩이 가능할 것이다.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/279/1*VAjRWpp4iPlSByVzdki1yg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/741/1*av_gUvQdFc7lLc6bfjnZ_g.png" /></figure><p><strong>오늘 자료를 구성하면서 크게 느낀 부분은 JS언어 만으로도 풀스택 개발이 가능하다는 점이다.</strong></p><p>다만, Express의 경우 유연하고 진입장벽이 낮은 장점은 있지만 서버 보안, 예외 처리 등을 위한 규칙이 존재하지 않는다고 느꼈다.</p><p>규칙이 없다는 것은 좋을 때도 있지만 서버와 같이 안전성을 위해 보수적으로 코드를 짜야하는 경우에는 규칙과 템플릿이 존재하는 것이 좋다고 생각한다. (Ex: Java Spring, Python django)</p><p>우리가 하고 있는 작은 프로젝트에서는 괜찮을 수도 있지만 실제 제품과 같은 큰 서버에서는 틀이 없다는 것은 단점으로 적용할 수도 있을 거 같다.</p><p><strong>그래서 찾아보니 이미 NestJS 라는 대체제가 존재한다고 한다!</strong></p><p><a href="https://docs.nestjs.com/">Documentation | NestJS - A progressive Node.js framework</a></p><p><strong>앞으로의 개발에서 오늘 만든 Express + React 템플릿을 자주 사용할 수 있을 거 같다.</strong></p><h3>4.1 참고 자료</h3><ul><li><a href="https://im-developer.tistory.com/164">[Node.js] Express를 backend로 하는 Create-react-app 시작하기</a></li><li><a href="https://daveceddia.com/create-react-app-express-backend/">Create React App with an Express Backend</a></li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FntFeJ30GE40%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DntFeJ30GE40&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FntFeJ30GE40%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/56f2a320b9da613254adac3fb4b934f1/href">https://medium.com/media/56f2a320b9da613254adac3fb4b934f1/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=16ec5d4e99ca" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[CSR vs SSR]]></title>
            <link>https://medium.com/@jvito11904/csr-vs-ssr-ed30c7e20bc1?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/ed30c7e20bc1</guid>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[csr]]></category>
            <category><![CDATA[ssrs]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Sat, 03 Sep 2022 04:54:44 GMT</pubDate>
            <atom:updated>2022-09-03T04:54:44.202Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*72KU4Zd-M3NEYukYJfvaHg.png" /></figure><h3>1. SSR (Server Side Rendring)</h3><p><strong>서버로 부터 완전하게 만들어진 html파일을 받아와 페이지 전체를 렌더링 하는 방식이다.</strong></p><h3>1.1 과정</h3><ul><li>유저가 웹사이트에 요청을 보낸다.</li><li>서버는 필요하다면 데이터베이스에서 데이터를 가지고 오고, 페이지를 렌더링하는데 필요한 작업을 수행한 HTML 파일을 만든다.</li><li>유저는 이제 서버가 렌더링 해준 HTML을 이용하여 콘텐츠를 볼 수 있다. (<strong>Time to view</strong>)</li><li>하지만 아직 페이지에서 상호작용을 할 수 있는 JS파일이 존재하지 않기 때문에 페이지에서 클릭했을 때 반응이 일어나지 않는 부분이 존재한다.</li><li>브라우저에서 필요한 상호작용에 필요한 JS 파일을 서버로 부터 받는다.</li><li>JS 파일을 브라우저에서 컴파일 후 인터랙티브한 페이지가 완성되었다. <strong>(Time To Interact)</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7oSYbWcmuPtTKpSNNufTLw.png" /></figure><h3>1.2 장점</h3><ul><li>HTML 컨텐츠가 이미 담겨져 있기에 <strong>SEO(Search Engine Optimizaion)</strong>에 좋다.</li><li>사용자에게 <strong>첫 콘텐츠를 빨리 보여줄 수 있는 장점</strong>이 존재한다.</li></ul><h3>1.3 단점</h3><ul><li><strong>매번 페이지를 요청할 때마다 새로고침 되는 치명적인 단점이 존재한다.</strong></li><li>TTV(Time To View)와 TTI(Time To Interact)의 공백이 발생한다.</li><li>페이지를 구성하는 모든 리소스를 서버에서 처리하므로 요청이 많아질 때 <strong>서버 부담이 증가한다.</strong></li></ul><p><strong>중요한 부분은 렌더링 주체가 ‘서버’ 라는 점이다.</strong></p><h3>2. CSR (Client Side rendring)</h3><p><strong>CSR은 페이지를 브라우저에서 서버로 부터 받은 JS 파일을 이용하여 직접 렌더링 하는 것을 의미한다.</strong></p><h3>2.1 과정</h3><ul><li>유저가 웹사이트에 요청을 보낸다.</li><li>브라우저는 링크를 이용하여 화면을 그리는데 필요한 HTML, JS 파일을 다운받는다.</li><li>브라우저는 JS 코드를 실행하여 리액트를 구동한다.</li><li>이 때 API를 통해 데이터베이스에 존재하는 데이터를 사용할 경우 API를 호출한다.</li><li>서버에서 데이터베이스에서 조회한 데이터를 응답한다.</li><li>API로 부터 받은 데이터를 채워준 뒤 리액트가 VirtualDOM에 콘텐츠를 렌더링 한다.</li><li>VirtualDOM이 구성이 완료되면 이를 브라우저 DOM 붙힌다.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u-qgF9L5ymWOyD-watAIOg.png" /></figure><h3>2.2 장점</h3><ul><li>렌더링이 필요한 부분만 바꾸기 때문에 페이지 새로고침이 발생하지 않는다.</li><li>렌더링 주체가 브라우저 이므로 서버에 부하를 줄일 수 있다.</li></ul><h3>2.3 단점</h3><ul><li><strong>JS 파일을 번들링</strong>해서 한번에 받기 때문에 초기 유저가 콘텐츠를 보기까지의 속도가 느리다.</li><li>기본 HTML이 비어있어<strong> SEO(Search Engine Optimizaion)</strong>에 최적화 되어 있지 않다.</li></ul><p><strong>중요한 부분은 렌더링 주체가 ‘브라우저’ 라는 점이다.</strong></p><h3>3. 실습</h3><p><strong>리액트로 구성된</strong> CSR을 사용하는 프로젝트 페이지에서의 <strong>네트워크 탭</strong>을 통하여 파일을 어떻게 로드하고 있는지 살펴보자.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d2lmJ_2tiSmEl2gGcXEJfQ.png" /></figure><p>처음으로 HTML 파일을 로드 하는 부분이다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g_vWlqdNu15yxmBq3v_OJQ.png" /></figure><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;ko&quot;&gt;<br>  &lt;head&gt;<br>    &lt;meta charset=&quot;utf-8&quot; /&gt;<br>    &lt;link rel=&quot;shortcut icon&quot; href=&quot;%PUBLIC_URL%/favicon.ico&quot; /&gt;<br>    &lt;meta<br>      name=&quot;viewport&quot;<br>      content=&quot;width=device-width, initial-scale=1, shrink-to-fit=no&quot;<br>    /&gt;<br>    &lt;meta name=&quot;theme-color&quot; content=&quot;#000000&quot; /&gt;<br>    &lt;title&gt;Chatting&lt;/title&gt;<br>  &lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;noscript&gt; You need to enable JavaScript to run this app. &lt;/noscript&gt;<br>    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;</pre><ul><li>실제 네트워크 탭에서 받은 응답이다.</li><li>위에서 보는것과 같이 HTML에 파일에 <strong>‘root’태그</strong>를 제외한 페이지를 구성하는 어떠한 태그들이 존재하지 않는 것을 볼 수 있다.</li></ul><p>그 후 리액트 관련 JS파일과 UI를 구현하고 상호작용에 필요한 JS파일을 다운받아 오는 것을 볼 수 있다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e_F1VHgxgI5fvtri9zZluQ.png" /></figure><p>mainJS 파일은 M<strong>infy</strong>, <strong>Uglify</strong> 되어 알아볼 수 없는 코드로 구성되어 있는 것을 확인할 수 있다.</p><pre>/*! For license information please see main.37b3cf38.js.LICENSE.txt */<br>function(){var e={892:function(e){e.exports=function(){&quot;use strict&quot;;var e=1e3,t=6e4,n=36e5,r=&quot;millisecond&quot;,o=&quot;second&quot;,i=&quot;minute&quot;,a=&quot;hour&quot;,u=&quot;day&quot;,l=&quot;week&quot;,c=&quot;month&quot;,s=&quot;quarter&quot;,f=&quot;year&quot;,d=&quot;date&quot;,p=&quot;Invalid Date&quot;,h=/^(\\d{4})[-/]?(\\d{1,2})?[-/]?(\\d{0,2})[Tt\\s]*(\\d{1,2})?:?(\\d{1,2})?:?(\\d{1,2})?[.:]?(\\d+)?$/,v=/\\[([^\\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/<br>...</pre><p>JS 코드를 이용하여 페이지에 필요한 콘텐츠를 동적으로 구성한다.</p><p>그 후 탭을 눌러 페이지를 이동하여도 필요한 데이터가 없다면 더 이상 서버에 HTML 파일을 요청하지 않는 것을 확인하였다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iqsF0rmnI5owbZP-izB7cw.png" /></figure><p>그럼 이제 <strong>node + Express</strong>로 구성된 <strong>SSR</strong>을 사용하는 프로젝트 페이지를 살펴보자.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e78r4WxWN27iGaGv5in4IQ.png" /></figure><pre>&lt;!DOCTYPE html&gt;<br>&lt;html&gt;<br>  &lt;head&gt;<br>    &lt;link rel=&quot;stylesheet&quot; href=&quot;/codemirror/lib/codemirror.css&quot;&gt;<br>    &lt;link rel=&quot;stylesheet&quot; href=&quot;/codemirror/theme/midnight.css&quot;&gt;<br>    &lt;script src=&quot;/codemirror/lib/codemirror.js&quot;&gt;&lt;/script&gt;<br>    &lt;script src=&quot;/codemirror/mode/javascript/javascript.js&quot;&gt;&lt;/script&gt;<br>    &lt;script src=&quot;/codemirror/addon/edit/matchbrackets.js&quot;&gt;&lt;/script&gt;<br>    &lt;script src=&quot;/codemirror/addon/edit/closebrackets.js&quot;&gt;&lt;/script&gt;<br>    &lt;script src=&quot;/codemirror/addon/hint/show-hint.js&quot;&gt;&lt;/script&gt;<br>    &lt;script src=&quot;/codemirror/addon/hint/javascript-hint.js&quot;&gt;&lt;/script&gt;<br>    &lt;script src=&quot;/codemirror/addon/selection/active-line.js&quot;&quot;&gt;&lt;/script&gt;</pre><pre>    &lt;title&gt;CodeWars&lt;/title&gt;<br>  &lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;h2&gt;피보나치 수열&lt;/h2&gt;<br>    &lt;h4&gt;정답자: 15명&lt;/h4&gt;<br>    &lt;h4&gt;난이도: Level 1&lt;/h4&gt;<br>    &lt;div&gt;설명: 피보나치 수는 F(0) = 0, F(1) = 1일 때, 1 이상의 n에 대하여 F(n) = F(n-1) + F(n-2) 가 적용되는 수 입니다.&lt;/div&gt;</pre><pre>    &lt;form action=&quot;/problems/627904cdd626d53e513898aa&quot; method=&quot;post&quot;&gt;<br>      &lt;textarea id=&quot;code&quot; name=&quot;code&quot;&gt;&lt;/textarea&gt;<br>      &lt;button type=&quot;submit&quot; class=&quot;submitButton&quot;&gt;submit&lt;/button&gt;<br>    &lt;/form&gt;</pre><pre>    &lt;script &gt;<br>      CodeMirror.fromTextArea(document.getElementById(&quot;code&quot;), {<br>        lint: true,<br>        lineNumbers: true,<br>        tabSize: 2,<br>        styleActiveLine: true,<br>        mode:  &quot;text/javascript&quot;,<br>        matchBrackets: true,<br>        autofocus: true,<br>        autoCloseBrackets: true,<br>        theme: &quot;midnight&quot;,<br>      }).setValue(&quot;function solution(arg) {\\n  // here!\\n}&quot;);<br>    &lt;/script&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;</pre><ul><li>CSR 방식과 다르게 이미 데이터베이스를 조회를 이용하여 구성한 정보를 포함하여 페이지 콘텐츠를 포함하고 있는 HTML 인것을 볼 수 있다.</li><li>이 경우 <strong>CSR 방식보다는 SEO에 장점을 가질 것이라는 예상을 할 수 있다.</strong> (페이지 콘텐츠가 많아 <strong>시맨틱 태그</strong>를 포함하고 있다면 더욱 그럴것이다.)</li></ul><p>그 후 필요한 JS 파일을 다운받는다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VTnK0v_6s5T1Sdi8jLzlgw.png" /></figure><ul><li>위에서 빨간줄을 이용해 표현한 구간이 <strong>SSR의 단점 TTV(Time to view), TTI (Time to Interact) 간극</strong>에 포함된다. (HTML 파싱하여 DOM 트리를 구성하는 시간까지 고려하면 더 정확한 시간을 구할 수 있다.)</li><li>HTML을 서버쪽에서 렌더링하여 보내주어 빠르게 유저가 해당 페이지를 볼 수 있지만, 유저의 이벤트에 따른 상호작용에 필요한 JS 파일을 다운받고 실행시키는데 까지 잠깐의 시간이지만 공백이 발생한다.</li></ul><p><strong>그리고 마지막으로 CSR과 SSR의 로드한 파일의 크기를 비교해보자.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/687/1*hSNuDe_SiTJPCakfsUKrvA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/813/1*FLE8HHJm5SZFwmL3cOWNUA.png" /></figure><ul><li>위에 CSR 방식의 JS 파일은 크기의 단의가 KB이고, 아래 SSR 방식은 Byte단위 크기이다.</li><li>정확한 비교를 위해서는 같은 페이지를 렌더링해야 하지만, 다른 페이지라고 하여도 한번에 불러오는 파일의 크기가 명확하게 차이가 나는 것을 볼 수 있다.</li><li>이는 <strong>CSR</strong>의 경우 한번에 요청으로 <strong>번들된 파일</strong>을 다운받아 오기 때문이다.</li><li>그리고 이것은 <strong>초기 진입속도가 SSR 비해 느려지는 이유중의 하나이다</strong>. (CSR의 단점)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dqpjkP_XRCcukSnCr3lWvw.png" /></figure><h3>4. 정리</h3><p>CSR과 SSR의 기본 개념과 각각의 장단점을 살펴보았다.</p><p>조사하고 느낀점은 어떤 방식이 더 우월하다는 것이 없다라고 느꼈다.</p><p><strong>페이지 이동이 많고 유저와 상호작용이 많은 넷플릭스의 경우 CSR을 적극적으로 활용하고, 페이지 이동이 적고 SEO를 최적하여 검색 결과 상위에 위치해야 하는 회사 홈페이지의 경우 SSR이 적절할 것이다.</strong></p><p><strong>서비스의 성격</strong>에 따라 적절한 방식을 채용하고, 또 필요에 따라서 <strong>두가지를 섞는 방법</strong>도 고려해야 할 것이다.</p><p>그리고 이번 주제를 조사하면서 <strong>CSR과 SSR를 적절하게 섞을 수 있는 리액트 프레임워크인 ‘NextJS’</strong> 에도 눈길이 갔다.</p><p><a href="https://nextjs.org/">Next.js by Vercel - The React Framework</a></p><p>다음 조사 주제로 좋은 재료인 거 같다.</p><h3>4.1 참고 자료</h3><ul><li><a href="https://towardsdev.com/server-side-rendering-srr-in-javascript-a1b7298f0d04">A Deep Dive into Server-Side Rendering (SSR) in JavaScript</a></li><li><a href="https://web.dev/rendering-on-the-web/">Rendering on the Web</a></li><li><a href="https://d2.naver.com/helloworld/7804182">NAVER D2</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ed30c7e20bc1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Express 오픈소스 탐험]]></title>
            <link>https://medium.com/@jvito11904/express-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%ED%83%90%ED%97%98-7560b29a5878?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/7560b29a5878</guid>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[express]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Fri, 02 Sep 2022 09:13:23 GMT</pubDate>
            <atom:updated>2022-09-02T09:13:23.830Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/768/1*mNlXzkXv3QFrbRz9ZNS1aA.png" /></figure><p>Express를 이용하여 응답을 보낼 때 ‘response’ 객체에 여러 메서드들이 존재한다. 만약 ‘JSON’형식으로 응답을 한다면 send(), json() 두 개의 메서드가 가능하다.</p><pre>app.get(&#39;/&#39;, function(req, res){<br>    res.json({ user: &#39;geek&#39; });<br>});</pre><pre>app.get(&#39;/&#39;, function(req, res){<br>    res.send({ user: &#39;geek&#39; });<br>});</pre><p>오늘은 GitHub에 올려진 ‘Express’ 프레임워크 오픈소스 코드를 이용하여 두 가지 메서드가 내부에서 어떻게 동작하는지 알아보고자 한다.</p><p><a href="https://github.com/expressjs/express/blob/master/lib/response.js">express/response.js at master · expressjs/express</a></p><h3>1. res.send()</h3><p>먼저 소스에 보면 간략하게 파라미터에 대한 설명이 주석으로 되어 있다.</p><pre>/**<br> * Send a response.<br> *<br> * Examples:<br> *<br> *     res.send(Buffer.from(&#39;wahoo&#39;));<br> *     res.send({ some: &#39;json&#39; });<br> *     res.send(&#39;&lt;p&gt;some html&lt;/p&gt;&#39;);<br> *<br> * @param {string|number|boolean|object|Buffer} body<br> * @public<br> */</pre><ul><li>response를 보내는 메서드이다.</li><li>인자로 string, number 등 기본 데이터 타입을 넣을 수 있다.</li></ul><p><strong>아래부터 코드에 나오는 ‘this’는 </strong><strong>res.send() Dot notation으로 호출하였으므로 ‘res’ 객체다.</strong></p><p>그리고 코드를 보고 send에 대해서 새롭게 안 사실인데, <strong>인자에 순서 상관없이 ‘stauts’를 넣을 수 있다.</strong></p><pre>// allow status / body<br>  if (arguments.length === 2) {<br>    // res.send(body, status) backwards compat<br>    if (typeof arguments[0] !== &#39;number&#39; &amp;&amp; typeof arguments[1] === &#39;number&#39;) {<br>      deprecate(&#39;res.send(body, status): Use res.status(status).send(body) instead&#39;);<br>      this.statusCode = arguments[1];<br>    } else {<br>      deprecate(&#39;res.send(status, body): Use      res.status(status).send(body) instead&#39;);<br>      this.statusCode = arguments[0];<br>      chunk = arguments[1];<br>    }<br>  }</pre><ul><li>첫번째 인자가 ‘number’ 타입이면 그것을 ‘statusCode’로 이용한다.</li><li>두번째 인자가 ‘number’ 타입이면 그것을 ‘statusCode’로 이용한다.</li><li>인자 순서도 상관없이 알아서해주는 너무나도 친철한 Express이다.</li></ul><p>이제 중요한 body의 타입 관련 로직이 나온다.</p><pre>switch (typeof chunk) {<br>    // string defaulting to html<br>    case &#39;string&#39;:<br>      if (!this.get(&#39;Content-Type&#39;)) {<br>        this.type(&#39;html&#39;);<br>      }<br>      break;<br>    case &#39;boolean&#39;:<br>    case &#39;number&#39;:<br>    case &#39;object&#39;:<br>      if (chunk === null) {<br>        chunk = &#39;&#39;;<br>      } else if (Buffer.isBuffer(chunk)) {<br>        if (!this.get(&#39;Content-Type&#39;)) {<br>          this.type(&#39;bin&#39;);<br>        }<br>      } else {<br>        return this.json(chunk);<br>      }<br>      break;<br>  }</pre><ul><li>여기서 chunk는 우리가 인자로 넘겨준 body({ user: ‘geek’ })이다.</li><li>타입이 string일 때 우리가 send()를 호출 전 직접적으로 Content-Type 명시하지 않았다면 ‘html’타입으로 설정된다.</li><li>object일 경우가 중요한데 우리가 body에 json을 넘기게 되면 위에 코드에 나와 있듯이 json()을 호출하는 것을 볼 수 있다.</li></ul><p>그럼 우리가 res.send({ user: &#39;geek&#39; })를 호출하면 send 메서드 내부에서 type을 확인하고 ‘object’이니 res.json({ user: &#39;geek&#39; })을 호출한다.</p><p>이제 마지막 부분이다.</p><pre>if (req.method === &#39;HEAD&#39;) {<br>    // skip body for HEAD<br>    this.end();<br>  } else {<br>    // respond<br>    this.end(chunk, encoding);<br>  }</pre><ul><li>end 메서드를 통해 최종적으로 응답을 보내는 걸 확인할 수 있다.</li></ul><h3>2. res.json()</h3><p>send메서드와 마찬가지로 간략하게 파라미터에 대한 설명이 주석으로 되어 있다.</p><pre>/**<br> * Send JSON response.<br> *<br> * Examples:<br> *<br> *     res.json(null);<br> *     res.json({ user: &#39;tj&#39; });<br> *<br> * @param {string|number|boolean|object} obj<br> * @public<br> */</pre><ul><li>JSON response를 보내는 메서드이다.</li><li>인자로 string, number 등 기본 데이터 타입을 넣을 수 있다.</li></ul><p><strong>아래부터 코드에 나오는 ‘this’는 </strong><strong>res.json() Dot notation으로 호출하였으므로 ‘res’ 객체다.</strong></p><p>send와 마찬가지로 json메서드에서도 <strong>인자에 순서 상관없이 ‘stauts’를 넣을 수 있다.</strong></p><pre>// allow status / body<br>  if (arguments.length === 2) {<br>    // res.json(body, status) backwards compat<br>    if (typeof arguments[1] === &#39;number&#39;) {<br>      deprecate(&#39;res.json(obj, status): Use res.status(status).json(obj) instead&#39;);<br>      this.statusCode = arguments[1];<br>    } else {<br>      deprecate(&#39;res.json(status, obj): Use res.status(status).json(obj) instead&#39;);<br>      this.statusCode = arguments[0];<br>      val = arguments[1];<br>    }<br>  }</pre><p>그리고 인자로 넣어준 데이터를 JSON 문자열로 변환하여 body에 담게된다.</p><pre>var body = stringify(val, replacer, spaces, escape)</pre><p>이 부분이 중요하다.</p><pre>// content-type<br>  if (!this.get(&#39;Content-Type&#39;)) {<br>    this.set(&#39;Content-Type&#39;, &#39;application/json&#39;);<br>  }</pre><pre>return this.send(body);</pre><ul><li>여기서 <strong>Content-Type</strong> 헤더가 세팅되지 않았을 경우 <strong>Content-Type을 application/json</strong> 을 세팅한다.</li><li>그리고 마지막으로 res.send(body)를 호출한다.</li></ul><p><strong>하지만 이 때 body는 </strong><strong>stringify 메서드를 통해 반환된 ‘string’ 타입이다.</strong></p><h3>3. 실행 흐름</h3><p>두개의 메서드가 연결되어 있는 것을 보았다.</p><p>그럼 이제 res.json({ user: &#39;geek&#39; }), res.send({ user: &#39;geek&#39; } 를 호출했을 때 각각의 실행흐름을 살펴보자.</p><h3>3.1 res.send({ user: &#39;geek&#39; })</h3><ol><li>res.send(object)</li><li>object type 이니 분기로직에 의해 res.json() 호출</li><li>res.json(object)</li><li>object를 stringify메서드로 <strong>string</strong>으로 변경</li><li><strong>Content-Type을 application/json으로 명시</strong></li><li>res.send(string)</li><li>res.end(string) 호출하여 최종적으로 응답</li></ol><h3>3.2 res.json({ user: &#39;geek&#39; })</h3><ol><li>res.json(object)</li><li>object를 stringify메서드로 string으로 변경</li><li><strong>Content-Type을 application/json으로 명시</strong></li><li>res.send(string)</li><li>res.end(string) 호출하여 최종적으로 응답</li></ol><h3>4. 정리</h3><p>각각의 메서드를 직접 오프소스를 확인하여 어떻게 동작하는지 살펴보았다.</p><p>실행 흐름을 보아서 알겠지만, send()는 범용적으로 다양한 데이터 type에 대응하는 것을 볼 수 있고, json()은 JSON 데이터 type에 특화되어 있는 걸 볼 수 있다.</p><p>또 두 개의 메서드는 유기적으로 연결되어 있다.</p><p><strong>만약 내가 응답으로 보내고 싶은 데이터가 ‘JSON’이 확실하다면 불필요하게 함수를 한번 더 호출하는 send() 보다는 json()이 적절할 거 같다!</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7560b29a5878" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React Design Pattern]]></title>
            <link>https://medium.com/@jvito11904/react-design-pattern-247b69f915cf?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/247b69f915cf</guid>
            <category><![CDATA[acv]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[design-patterns]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Wed, 31 Aug 2022 07:52:32 GMT</pubDate>
            <atom:updated>2022-09-03T12:49:26.281Z</atom:updated>
            <content:encoded><![CDATA[<h3>1. VAC(View Asset Component)</h3><p>VAC (View Asset Component)는 렌더링에 필요한 JSX와 스타일을 관리하는 컴포넌트를 의미한다.</p><p>VAC 패턴은 View 컴포넌트에서 JSX 영역을 Props Object로 추상화하고, JSX를 VAC로 분리해서 개발하는 설계 방법이다.</p><p>이런 설계는 비즈니스 로직 뿐만 아니라 UI 기능 같은 View 로직에서도 렌더링 관심사를 분리하는데 목적이 있다.</p><p>VAC Container을 JSX, Style을 제외한 비지니스 로직, 상태관리 등을 맡고 VAC에서는 JSX, Style을 관리하여 렌더링을 담당한다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*pZila4T772Efq7Mr2juv0w.png" /></figure><p><strong>결국, VAC 패턴은 기존의 컴포넌트에서 JSX 영역을 별도로 분리하여 협업시 충돌을 최소화하고, 컴포넌트의 역할을 확실하게 분리하는 것을 목적으로 한다.</strong></p><h3>2. 적용</h3><p>이번 개인 프로젝트에서 VAC 패턴이 필요로 하는 부분에 적재적소에 적용시켰다.</p><h3>VAC Container</h3><pre>function Search() {<br>  const { hideModal } = useModal();<br>  const [filter, setFilter] = useState(&quot;&quot;);<br>  const [debouncedFilter] = useDebounce(filter, 500);<br>  const { data } = useQuery(<br>    [&quot;getPhotos&quot;, debouncedFilter],<br>    () =&gt; getPhotos(debouncedFilter),<br>    {<br>      enabled: Boolean(debouncedFilter),<br>      refetchOnWindowFocus: false,<br>    },<br>  );</pre><pre>  const SearchProps = {<br>    photos: data,<br>    onInputChange: e =&gt; setFilter(e.target.value),<br>    onPhotoClick: () =&gt; hideModal(),<br>  };</pre><pre>  return &lt;SearchView {...SearchProps} /&gt;;<br>}</pre><ul><li>Fetching, CustomHook, state 등 비지니스 로직 상태 관련 코드 담당</li><li>‘Props Object’ 를 통해 VAC 컴포넌트에 전달</li><li>JSX 로직이 분리되어 있어 비즈니스 로직에 집중할 수 있었고 ‘VAC Debugger’을 통해 테스트를 진행</li></ul><h3>VAC</h3><pre>function SearchView({ photos, onInputChange, onPhotoClick }) {<br>  return (<br>    &lt;SearchBarWrapper&gt;<br>      &lt;SearchBar<br>        type=&quot;text&quot;<br>        placeholder=&quot;Search using tags related to photos.&quot;<br>        onChange={onInputChange}<br>      /&gt;<br>      &lt;SearchIcon /&gt;<br>    &lt;/SearchBarWrapper&gt;<br>    &lt;PhotosWrapper&gt;<br>      {photos?.map(photo =&gt; (<br>        &lt;Link key={photo._id} to={`/${photo._id}`} onClick={onPhotoClick}&gt;<br>          &lt;PhotoEntry {...photo} /&gt;<br>        &lt;/Link&gt;<br>      ))}<br>    &lt;/PhotosWrapper&gt;<br>  );<br>}</pre><pre>SearchView.propTypes = {<br>  photos: PropTypes.array.isRequired,<br>  onInputChange: PropTypes.func.isRequired,<br>  onPhotoClick: PropTypes.func.isRequired,<br>};</pre><pre>const SearchBarWrapper = styled.div`<br>  display: flex;<br>  margin-top: 2rem;<br>  border: 0.3rem solid #265d6e;<br>  border-radius: 40px;<br>  justify-content: space-around;<br>  align-items: center;<br>  padding: 0.3rem 2rem;<br>`;</pre><pre>...</pre><ul><li>JSX 로직과 , Style 관련 코드를 담당</li><li>‘Container’에서 넘겨준 ‘Props Object’를 통해 이벤트 바인딩</li><li>‘propTypes’을 통해 타입 검사</li></ul><h3>3. 소감</h3><p><strong>결과적으로 이 패턴은 이번 프로젝트 나아가 나에게 잘 맞는 패턴이였다.</strong></p><p>JSX 코드를 담당하는 컴포넌트, 비지니스 로직, State를 관리하는 컴포넌트가 구분이 되어 관심사 분리가 확실하게 되는 것을 느꼈다.</p><p>또한, 이번 프로젝트는 나혼자 진행하였지만 협업시에는 아직 View (JSX) Component가 없어도 VAC 개발툴을 이용하여 비지니스 로직을 작성할 수 있는 점이 더 큰 장점으로 다가올 거라 생각한다.</p><p>리액트는 웹 개발 특성상 ‘Agile’하게 개발을 진행하여야 된다고 생각한다. 그러한 생각으로 먼저 코드를 작성한 후 분리, 모듈화가 필요하다고 생각되면 그 상황에 최선의 구조를 적용하는 리팩토링 과정을 선호한다.</p><p>VAC 패턴은 다른 패턴(Presentational, 아토믹 디자인)과는 다르게 제약이 많지 않고, 설계에 과도하게 오랜시간을 가지지 않도록하여 내게 좋은 개발 경험을 안겨주었다.</p><h3>4. 참고자료</h3><ul><li><a href="https://wit.nts-corp.com/2021/08/11/6461">React에서 View의 렌더링 관심사 분리를 위한 VAC 패턴 소개 | WIT블로그</a></li><li><a href="https://tv.naver.com/v/23162062">React VAC Pattern - View 로직과 JSX의 의존성을 최소화 하자!</a></li><li><a href="https://all-dev-kang.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-VAC-%ED%8C%A8%ED%84%B4-%EC%A0%81%EC%9A%A9-%ED%9B%84%EA%B8%B0-%EB%B0%8F-%EC%9E%A5%EB%8B%A8%EC%A0%90">[리액트] VAC 패턴 적용 후기 및 장단점</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=247b69f915cf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React SOLID]]></title>
            <link>https://medium.com/@jvito11904/react-solid-8b2fa62d67bf?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/8b2fa62d67bf</guid>
            <category><![CDATA[c]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[solid]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Wed, 31 Aug 2022 06:03:33 GMT</pubDate>
            <atom:updated>2022-08-31T06:03:33.600Z</atom:updated>
            <content:encoded><![CDATA[<h3>1. SOLID?</h3><p>SOLID는 로버트 마틴이 주장한 유지보수가 쉽고 확장이 용이한 코드를 만들 때 적용하면 좋은 객제 지향 프로그래밍 원칙이다.</p><p>주로 객체 지향 언어(C++, Java)에 많이 사용되지만 언어에 상관없이 적용할 수 있는 훌륭한 개발 방법론이고, 객체 지향 설계의 정수라고 할 수 있다.</p><ul><li>SRP(Single Responsibility Principle): 단일 책임 원칙</li><li>OCP(Open Closed Priciple): 개방 폐쇄 원칙</li><li>LSP(Listov Substitution Priciple): 리스코프 치환 원칙</li><li>ISP(Interface Segregation Principle): 인터페이스 분리 원칙</li><li>DIP(Dependency Inversion Principle): 의존 역전 원칙</li></ul><blockquote><em>원칙들에 대해서 이해를 돕기 위해 아래의 글을 읽는 것을 추천한다.</em></blockquote><blockquote><a href="https://velog.io/@juhwan9408/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84-5%EC%9B%90%EC%B9%99-SOLID"><em>객체 지향 설계 5원칙 — SOLID</em></a></blockquote><p>JS에서는 인터페이스 개념이 존재하지 않고, 리액트에서는 함수형 프로그래밍을 강조하여 조금의 어색한 부분이 있을 수 있지만 이번 프로젝트에서 리액트에 SOLID원칙을 적용시켜 보았다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*JtfaTQpQ28nyYxgC5P0zDw.png" /></figure><h3>1. 단일 책임 원칙(Single responsibility principle)</h3><p>올바른 객체지향 설계를 위한 원칙 중 하나인 단일 책임 원칙은 모든 클래스는 하나의 책임만 가져야 한다라고 강조한다.</p><p>‘책임’ 기준의 상당히 모호하고 사람마다 다르게 생각될 수 있다. SOLID 원칙 창시자의 말을 빌리면</p><blockquote><em>같이 수정해야될 것들은 묶고 따로 수정해야될 것들은 분리하는 것</em></blockquote><p>이러한 클래스의 단일 책임 기준을 리액트에서 ‘컴포넌트’에 적용시킬 수 있다. 즉, 컴포넌트는 한 가지 일을 하는게 이상적이라는 원칙을 가지고 설계를 진행할 수 있다.</p><p><strong>이번 프로젝트에서 단일 책임 원칙 테크닉을 이용하여 컴포넌트 계층 구조를 나누어 보았다.</strong></p><p>이번 개인 프로젝트에서 <strong>단일 책임 원칙을</strong> 강조하는 리액트에서 추천하는 설계 가이드 <strong>‘React로 사고하기’</strong> 과정을 따라 페이지 및 컴포넌트를 설계하였다.</p><p><a href="https://ko.reactjs.org/docs/thinking-in-react.html">React로 사고하기 - React</a></p><h4>메인 페이지 목업</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*csSAEH87hSxqbCzPf6lTKA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/390/1*84y8pgbjlojsWG6Xqwlokw.png" /></figure><h4>1단계: UI를 컴포넌트 계층 구조로 나누기</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_EFP0NQi5i_HJ4rPmHjiyQ.png" /></figure><ul><li>MainPage: 전체페이지</li><li>PhtotView: 사진을 보여준다.</li><li>Header: 페이지 헤더 ( 사진 작성자, 나라, 도시 정보를 보여준다.)</li><li>AsideButtons: 해당 관심사의 맞는 모달을 띄우는 버튼들의 집합</li><li>BottomButtons: 페이지 아래에 배치되어 있는 버튼들의 집합</li></ul><p>하나의 컴포넌트가 커지게 되었을 때 컴포넌트들의 집합으로 묶고 하나의 일을 담당하는 하위 컴포넌트를 배치하였다.</p><p>위의 설계 구조에 따라 실제 개발 시 ‘MainPage’ 컴포넌트 배치이다.</p><pre>&lt;PhotoWrapper&gt;<br>    &lt;PhotoView /&gt;<br>    &lt;MainPageHeader /&gt;<br>    &lt;AsideButtons /&gt;<br>    &lt;BottomButtons /&gt;<br>&lt;/PhotoWrapper&gt;</pre><p>그리고, ‘AsideButtons’ 컴포넌트들의 집합을 하나의 동작을 담당하는 하위 컴포넌트로 분리하였다.</p><pre>&lt;Wrapper&gt;<br>    &lt;ProfileIcon /&gt;<br>    &lt;UploadIcon /&gt;<br>    &lt;BookmarkIcon /&gt;<br>    &lt;SearchIcon /&gt;<br>&lt;/Wrapper&gt;</pre><p>마찬가지로, ‘BottomButtons’ 컴포넌트들의 집합을 하나의 동작을 담당하는 하위 컴포넌트로 분리하였다.</p><p>그 후 ‘React로 사고하기’ 가이드에 따라 State를 배치(Recoil을 이용하여 전역으로 관리) 하고, 역방향 데이터 흐름을 추가하였다.</p><p><strong>리액트를 초기에 학습할 때 부터 ‘React로 사고하기’ 절차를 밝고 개발을 진행하였다.</strong></p><p>이 절차에서 컴포넌트 게층 구조를 나눌 때 기준을 ‘<strong>단일 책임 원칙</strong>’으로 잡았을 때 유지보수에 확실한 장점을 보인다는 것을 느꼈다.</p><p><strong>개발을 하는 도중 어떤 기능에 문제가 생긴다면 그 기능을 담당하고 있는 컴포넌트에만 관심을 가지고 수정하면 되기 때문에 커플링을 줄이고, 효율적으로 개발할 수 있었다.</strong></p><p>예를 들어 위에 예시에서 제가 개발한 서비스에서 사진을 보여주는 기능에 문제가 생겼을 때 저는 주저하지 않고 ‘PhotoView’ 컴포넌트 코드를 확인하고 이 컴포넌트를 수정할 것이다.</p><p><strong>또 하나, 단일 책임 원칙을 잘 나타내는 단어가 있다. 바로 ‘모듈화’ 이다. 컴포넌트에 존재하는 큰 덩어리 코드들을 각자에 역할에 따라 분리하는 것이다.</strong></p><p>이번 프로젝트에서 GraphQL을 이용하였다. REST API를 사용하였을 때 ‘Axios’ 객체를 이용하여 모듈화를 하였듯이 ‘graphql-request’ 객체를 이용하여 모듈화를 진행하였다.</p><pre>import { GraphQLClient } from &quot;graphql-request&quot;;</pre><pre>const endpoint = process.env.REACT_APP_API_SERVER_URL;<br>const API = new GraphQLClient(endpoint);</pre><pre>export const getRandomPhotoAndBookmarks = async (exceptionId, userId) =&gt; {<br>  const { randomPhoto: photo, user } = await API.request(<br>    GET_RANDOM_PHOTO_WITH_BOOKMARKS,<br>    {<br>      photoId: exceptionId,<br>      userId,<br>    },<br>  );</pre><pre>  return { photo, user };<br>};</pre><pre>export const getUserBookmarks = async userId =&gt; {<br>  const { user } = await API.request(<br>    GET_USER_BOOKMARKS,<br>    {<br>      userId,<br>    },<br>    {<br>      authorization: `Bearer ${<br>        JSON.parse(localStorage.getItem(&quot;loginData&quot;)).accessToken<br>      }`,<br>    },<br>  );</pre><pre>  return user;<br>};</pre><pre>...</pre><p>위와 같이 API 통신을 담당하는 모듈을 만들었다.</p><p>북마크 컴포넌트에서는 위의 API 모듈과 ‘React Query’를 이용하여 특정 유저의 북마크 리스트를 보여줄 수 있었다.</p><pre>function Bookmarks() {<br>  const userData = useRecoilValue(loginState);<br>  const { data } = useQuery(<br>    [&quot;getBookmarks&quot;, userData._id],<br>    () =&gt; getUserBookmarks(userData._id),<br>    {<br>      refetchOnWindowFocus: false,<br>    },<br>  );</pre><pre>  return (<br>    &lt;&gt;<br>      {data.bookmarks.map(bookmark =&gt; (<br>        &lt;PhotoEntry key={bookmark._id} {...bookmark} /&gt;<br>      ))}<br>    &lt;/&gt;<br>  );<br>}</pre><p>컴포넌트에서는 ‘엔드포인트 설정‘, ‘authorization 설정’ 등 API 관련 추가 조작 없이 데이터를 페칭한 다음 렌더링하고만 있다. 이것은 API 로직, 렌더링 로직을 책임을 분리하였기 때문에 가능한 일이다. 그 결과 코드가 직관적여지고 보다 쉽게 유지 관리할 수 있게 되었다.</p><p>(조금 더 개선할 수 있는 여지는 ‘React Query’ 관련 로직도 커스텀 훅으로 분리하여 캡슐화 할 수 있다.)</p><p><strong>결과적으로, ‘단일 책임 원칙’은 좋은 설계의 이점을 여과없이 보여주는 테크닉이였다.</strong></p><h3>2. 개방-폐쇄 원칙 (Open-closed principle, OCP)</h3><p><strong>개방-폐쇄 원칙은 소프트웨어 구성요소(컴포넌트,클래스,모듈,함수)는 확장을 위해 열려야 하지만 수정을 위해 닫혀 있어야 한다 라고 강조한다.</strong></p><p>프로젝트에서 컴포넌트 내부의 코드를 변경하지 않고 확장할 수 있는 방식으로 구조화 시켜보았다.</p><p><strong>사실 이 원칙은 ‘컴포넌트의 재사용성’을 생각하면 쉽게 이해할 수 있을 것이다.</strong></p><p>거창하게 적용시키지 않고, 우리가 자주쓰는 모달에 이 원칙을 적용시켜 보았다.</p><p>먼저, 원칙을 적용시키지 않고 모달을 설계해보자.</p><pre>export default function Modal({ title, type}) {<br>  const { hideModal } = useModal();</pre><pre>  return ReactDom.createPortal(<br>    &lt;ModalOverlay onClick={hideModal}&gt;<br>      &lt;ModalContainer onClick={e =&gt; e.stopPropagation()}&gt;<br>        &lt;ModalTitle&gt;{title}&lt;/ModalTitle&gt;<br>        {type === &quot;LoginModal&quot; &amp;&amp; &lt;LoginView /&gt;}<br>        {type === &quot;UploadModal&quot; &amp;&amp; &lt;UploadView /&gt;}<br>				...<br>      &lt;/ModalContainer&gt;<br>    &lt;/ModalOverlay&gt;,<br>    document.getElementById(&quot;portal&quot;),<br>  );<br>}</pre><p>위에 구조에서 아래와 같은 단점이 존재한다.</p><ul><li>새로운 모달 타입이 생길 때 마다 Modal 컴포넌트로 돌아가서 조건부 렌더링 로직을 수정해야 한다.</li><li>확정성이 고려되지 않았고, 개방-폐쇄 원칙에 위배된다.</li></ul><p>이 문제를 해결하기 위해 컴포넌트 합성을 사용할 수 있다. children prop를 사용해서 Modal을 사용할 컴포넌트에게 이 책임을 위임할 수 있다. 그렇다면 더 이상 Modal 컴포넌트는 다른 타입의 모달이 생겨도 수정에는 닫힌 구조를 가질 수 있다.</p><pre>export default function Modal({ children, title }) {<br>  const { hideModal } = useModal();</pre><pre>  return ReactDom.createPortal(<br>    &lt;ModalOverlay onClick={hideModal}&gt;<br>      &lt;ModalContainer onClick={e =&gt; e.stopPropagation()}&gt;<br>        &lt;ModalTitle&gt;{title}&lt;/ModalTitle&gt;<br>          {children}<br>      &lt;/ModalContainer&gt;<br>    &lt;/ModalOverlay&gt;,<br>    document.getElementById(&quot;portal&quot;),<br>  );<br>}</pre><p>위에 구조에서 Recoil을 이용하여 전역적으로 모달창을 띄우는 상태와 타입을 함께 관리하는 GlobalModal을 통해 확장하였다.</p><pre>function GlobalModal() {<br>  const { modalType, modalProps } = useRecoilValue(modalState) || {};</pre><pre>  if (modalType === &quot;LoginModal&quot;) {<br>    return (<br>      &lt;Modal {...modalProps}&gt;<br>        &lt;Login /&gt;<br>      &lt;/Modal&gt;<br>    );<br>  }</pre><pre>  if (modalType === &quot;UploadModal&quot;) {<br>    return (<br>      &lt;Modal {...modalProps}&gt;<br>        &lt;Upload /&gt;<br>      &lt;/Modal&gt;<br>    );<br>  }</pre><pre>	...<br>}</pre><p>이제 Modal 컴포넌트 자체를 수정하지 않고도 합성을 사용하여 확장에는 열려있는 구조가 완성되었다.</p><p><strong>리액트에서 이 원칙을 잘 활용하면 컴포넌트의 확장성과 재사용성을 높일 수 있는 기회가 될 수 있을것이라 생각한다.</strong></p><h3>3. 리스코프 치환 원칙 (Liskov substitution principle)</h3><p><strong>이 원칙은 자식 클래스는 부모 클래스에서 가능한 행위를 수행해야한다는 원칙이다. 쉽게 말해 하위 타입 객체가 상위 타입 객체를 대체할 수 있다는 뜻이다.</strong></p><p>함수형 컴포넌트 구조로 가고있는 리액트에서는 거의 적용할 수 없는 규칙이다 게다가, 공식문서에서 이 원칙에서 불가피한 ‘상속’ 대신 위에서 사용한 ‘합성’을 강력하게 추천하고 있다.</p><p><a href="https://www.articlegenie.co/genie-mode?url=https%3A%2F%2Freactjs.org%2Fdocs%2Fcomposition-vs-inheritance.html#genie-id-p1">Composition vs Inheritance - React</a></p><p><em>(’아티클 지니’를 통해 공유하고자 하는 문단을 하이라이트 하였습니다.)</em></p><h3>4. 인터페이스 분리 원칙 (Interface segregation principle)</h3><p><strong>인터페이스 분리 원칙은 하나의 클래스는 자신이 이용하지 않는 인터페이스는 의존하지 말아야 한다는 원칙이다. 이것을 리액트에서는 컴포넌트에 사용하지 않는 props에 의존하지 말아야 한다로 해석할 수 있다.</strong></p><p>사실 이 원칙을 리액트에서 잘 적용하기 위해서는 타입스크립트를 사용하여야 된다고 생각한다. 그럼에도 불구하고 이번 프로젝트에서 불필요한 props를 넘겨주지 않는다라는 것에 집중하겨 적용시켜 보았다.</p><p>먼저 원칙을 적용시키기 전에는 ‘photo’ 객체를 자식 컴포넌트에 모두 전달하여 특정 프러퍼티를 사용하였다.</p><pre>function MainPage() {<br>	const userData = useRecoilValue(loginState);<br>	const { data, refetch } = useQuery(<br>	    [&quot;randomPhoto&quot;, userData?._id],<br>	    () =&gt; getRandomPhotoAndBookmarks(currentPhotoId,        <br>                                             userData?._id),<br>	    {<br>	      refetchOnWindowFocus: false,<br>	    },<br>	  );<br>	<br>	return (<br>        &lt;PhotoWrapper&gt;<br>	    &lt;PhotoView photo={data.photo}/&gt;<br>	    &lt;MainPageHeader /&gt;<br>	    &lt;AsideButtons /&gt;<br>	    &lt;BottomButtons /&gt;<br>	&lt;/PhotoWrapper&gt;<br>);<br>}</pre><pre>function PhotoView({ photo }) {<br>	return &lt;img src={photo.imageUrl} /&gt;;</pre><pre>PhotoView.propTypes = {<br>  photo: PropTypes.shape({<br>		imageUrl: PropTypes.string,<br>	  country: PropTypes.string,<br>	  city: PropTypes.string,<br>	  tags: PropTypes.array,<br>  })<br>};</pre><p>이 경우, 사용하지 않는 props에 의존하고 있어 재사용성이 낮아지는 결과를 초래할 수 있다.</p><p>예를 들어 이번에 오버패칭을 방지하는 GraphQL을 이용하여 내가 필요로 하는 데이터만 들고와여 정제하지 않고도, 바로 사용할 수 있도록 하였다. 그렇기 때문에 ‘photo’ 데이터가 각 컴포넌트에서 ‘tags’ 프러퍼티가 없는 등 다른 형태의 객체일 수 있다. 이 때 propTypes 검사를 통해 리액트에서 ‘<strong><em>Warning’을</em></strong> 뱉을 것이다. (타입스크립트의 경우 에러이다.)</p><p>그럼 이 문제를 해결해보자.</p><pre>function MainPage() {<br>	const userData = useRecoilValue(loginState);<br>	const { data, refetch } = useQuery(<br>	    [&quot;randomPhoto&quot;, userData?._id],<br>	    () =&gt; getRandomPhotoAndBookmarks(currentPhotoId, userData?._id),<br>	    {<br>	      refetchOnWindowFocus: false,<br>	    },<br>	  );<br>	<br>	return (<br>		&lt;PhotoWrapper&gt;<br>		  &lt;PhotoView photo={data.photo.imageUrl}/&gt;<br>	    &lt;MainPageHeader /&gt;<br>	    &lt;AsideButtons /&gt;<br>	    &lt;BottomButtons /&gt;<br>		&lt;/PhotoWrapper&gt;<br>         );<br>}</pre><pre>function PhotoView({ imageUrl }) {<br>	return &lt;img src={imageUrl} /&gt;;</pre><pre>PhotoView.propTypes = {<br>	imageUrl: PropTypes.string.isRequired,<br>};</pre><p>필요한 props에만 의존하도록 PhotoView 컴포넌트를 리팩토링하여, 다른 데이터 객체, 컴포넌트에서도 대응할 수 있게 되었다.</p><p><strong>인터페이스 분리 원칙을 통해 컴포넌트들 간의 의존성을 낮추고, 재사용성을 높일 수 있다.</strong></p><h3>5. 의존관계 역전 원칙 (Dependency inversion principle)</h3><p>SOLID 원칙 중 가장 이해가 쉽지 않은 부분이다.</p><p><strong>한마디로, 객체는 직접적으로 저수준 모듈(객체, 클래스) 보다는 추상화 된 인터페이스와 같은 고수준 모듈에 외존해야 한다는 것이다.</strong></p><p>왜 그럴까? 스택오버플로우에서 깔끔하게 설명한 부분이 있다.</p><p><a href="https://www.articlegenie.co/genie-mode?url=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F62539%2Fwhat-is-the-dependency-inversion-principle-and-why-is-it-important#genie-id-p21">What is the dependency inversion principle and why is it important?</a></p><p><em>(’아티클 지니’를 통해 공유하고자 하는 문단을 하이라이트 하였습니다.)</em></p><p>궁극적으로는 각 객체간의 관계를 최대한 느슨하게 유지하기 위한 것이다.</p><p>이것을 리액트에 적용하면 한 컴포넌트가 다른 컴포넌트에 직접적으로 의존해서는 안되며, 둘 다 공통된 추상화에 의존해야 한다.</p><p>이 원칙이 적용된 예시를 통해 살펴보자.</p><p>이번 프로젝트에서 VAC(View Asset Component) 패턴을 사용하였는데 이 패턴을 통해 의존관계 역전 원칙을 일부 따를 수 있었다.</p><p>구글 로그인 버튼을 이용하여 ‘login’ API 요청을 보내는 Login 컴포넌트이다.</p><pre>import { login } from &quot;../../api&quot;;</pre><pre>function Login() {<br>  const loginMutation = useMutation(login);<br>  const handleLogin = async credentialResponse =&gt; {<br>    const profileObj = jwtDecode(credentialResponse.credential);</pre><pre>    const { name, email } = profileObj;<br>    loginMutation.mutate(<br>      { name, email },<br>      {<br>        onSuccess: response =&gt; {<br>          localStorage.setItem(&quot;loginData&quot;, JSON.stringify(response));<br>        },<br>      },<br>    );<br>  };</pre><pre>  const LoginProps = {<br>    onLoginButtonClick: handleLogin,<br>  };</pre><pre>  return &lt;LoginView {...LoginProps} /&gt;;<br>}</pre><pre>function LoginView({ onLoginButtonClick }) {<br>  return (<br>    &lt;Wrapper&gt;<br>      &lt;Description&gt;<br>        Fall into the beautiful landscape and the sound of your imagination.<br>      &lt;/Description&gt;<br>      &lt;GoogleLogin<br>        onSuccess={onLoginButtonClick}<br>        onError={() =&gt; {<br>          console.log(&quot;Login Failed&quot;);<br>        }}<br>      /&gt;<br>    &lt;/Wrapper&gt;<br>  );<br>}</pre><pre>LoginView.propTypes = {<br>  onLoginButtonClick: PropTypes.func.isRequired,<br>};</pre><p>LoginView 컴포넌트는 직접적으로 api 모듈에 직접 의존하지 않고, Login 컴포넌트에서 <strong>props로 주입받은 함수를 통해 추상화되었다.</strong></p><p>로그인 로직을 구체적인 구현은 LoginView 컴포넌트의 상위 컴포넌트의 Login 컴포넌트의 책임이 되었다. (로그인 로직은 ‘React Query Mutation’을 통해 구현하였다.)</p><p><strong>Login 컴포넌트 api 모듈과 LoginView 컴포넌트 사이에 추상화된 인터페이스 역활을 하며 컴포넌트와 모듈간의 직접적인 의존을 부섰다.</strong></p><p>하지만, VAC 패턴으로는 완벽하게 의존성을 없애는데에 한계가 존재했고 presentational container pattern을 적용하면 조금 더 객체 지향적으로 이 원칙을 따를 수 있을거라 예상한다.</p><h3>6. 결론</h3><p>완벽하지 않지만, SOLID를 원칙을 이번 프로젝트를 기회삼아 리액트에 적용해 보았다.</p><p>필자는 아직 실력있는 시니어 개발자들처럼 개발전 아키텍처를 설계하는 능력이 부족하다. 그래서 설계를 완벽하게 하고 개발을 시작하는 것이 아니라 ‘리액트로 사고하기’와 같이 간략한 구조 설계 후 개발을 일단 시작한다. 그 다음 하나의 컴포넌트에 ‘monolithic’ 코드 덩어리를 작성한 후 SOLID원칙에 따라 모듈화 또는 분리를 진행하였다.</p><p>객체 지향 원칙이 리액트에서도 적용된다는 부분이 신기하였고, 객체 지향 원칙이 정말 잘 만들어진 구조이기 때문에 이렇게 범용적으로 적용할 수 있다 생각하였다. 결과적으로 이 원칙은 나에게 좋은 개발 경험을 안겨주었다.</p><p><strong>하지만, 기존의 설계 패턴이나 소프트웨어 구조 원칙이 그러하듯이 이것을 맹신하여 상황에 맞지 않게 원칙을 지키려고 한다면 코드는 더 복잡해지고 오히려 유지보수가 어려워 질 수도 있다라고 느꼈다.</strong></p><p><strong>앞으로 이런 좋은 원칙이나 설계 패턴을 참고하여 팀과 프로젝트 상황에 맞게 적재적소에 적용할 수 있는 능력을 길러야 겠다라고 생각하였다.</strong></p><h3>7. 참고자료</h3><ul><li><a href="https://medium.com/dailyjs/applying-solid-principles-in-react-14905d9c5377">Applying SOLID principles in React</a></li><li><a href="https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898">The S.O.L.I.D Principles in Pictures</a></li><li><a href="https://velog.io/@eddy_song/alan-kay-OOP">창시자 앨런 케이가 말하는, 객체 지향 프로그래밍의 본질</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8b2fa62d67bf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[당신이 알고있는 RESTFul API는 진정한 RESTFul API 일까?]]></title>
            <link>https://medium.com/@jvito11904/%EB%8B%B9%EC%8B%A0%EC%9D%B4-%EC%95%8C%EA%B3%A0%EC%9E%88%EB%8A%94-restful-api%EB%8A%94-%EC%A7%84%EC%A0%95%ED%95%9C-restful-api-%EC%9D%BC%EA%B9%8C-18a99929ba39?source=rss-f8e2380718a1------2</link>
            <guid isPermaLink="false">https://medium.com/p/18a99929ba39</guid>
            <category><![CDATA[css]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[network]]></category>
            <category><![CDATA[rest-api]]></category>
            <dc:creator><![CDATA[Jvito]]></dc:creator>
            <pubDate>Mon, 22 Aug 2022 11:50:54 GMT</pubDate>
            <atom:updated>2022-08-22T11:50:54.082Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xjvjk4wFmdUzvBNJVAoEpg.png" /></figure><p>RESTFul API에 대해서 알아보던 중 궁금한 점이 생겼다.</p><p>진정한 RESTFul 의미는 무엇일까?</p><p>나의 백엔드 서버는 ‘RESTFul‘ 한가?</p><p>그래서 RESTFul API에 대해 조사해보고, 실제로 그것을 만들기 위해 노력해보았다.</p><h3>1. API(Application Programming Interface) 란?</h3><p>REST API를 알기전에 API의 정의를 먼저 살펴보자.</p><blockquote><em>API는 </em><a href="https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%93%A8%ED%84%B0"><em>컴퓨터</em></a><em>나 </em><a href="https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%93%A8%ED%84%B0_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8"><em>컴퓨터 프로그램</em></a><em>사이의 연결이다. 일종의 소프트웨어 </em><a href="https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4_(%EC%BB%B4%ED%93%A8%ED%8C%85)"><em>인터페이스</em></a><em>이며 다른 종류의 </em><a href="https://ko.wikipedia.org/wiki/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4"><em>소프트웨어</em></a><em>에 서비스를 제공한다.</em></blockquote><blockquote>- wikipedia</blockquote><p>전혀 무슨말인지 모르겠다.</p><p>그럼 조금 더 쉽게 API에 대해서 알아보자.</p><p>우리가 식당에 갔을 때 요리사에게 바로 주문을 하는 것이 아니라, 점원에게 요청을 하고 점원이 다시 우리의 주문을 요리사에게 요청하고 만들어진 요리를 점원이 가져다준다. (요리사에게 바로 요청하지 않는 이유는 쉽게 떠오를 수 있다)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2gG_YugCKDJs0w7LKF6oOA.png" /></figure><p><strong>즉, 보통에 웹에서 클라이언트(손님), DB(서버) 사이에서 API가 점원의 역활을 해준다.</strong></p><h3>1.1 API의 역활</h3><ul><li>서버와 DB 대한 출입구 역활</li></ul><p>만약 API가 없다면 우리는 DB에 접근할 수 있는 권한을 얻어 직접 쿼리를 날려 우리가 원하는 데이터를 가지고 와야한다.</p><pre>SELECT * FROM inventory WHERE status = &quot;A&quot; AND qty &lt; 30</pre><pre>db.inventory.find( { status: &quot;A&quot;, qty: { $lt: 30 } } )</pre><p>API가 대신 접근권한도 관리하고, 우리 대신 효율적인 쿼리를 만들어 데이터를 가지고와 필요하다면 가공시켜 우리에게 응답을 줄 것이다.</p><ul><li>모든 접속을 표준화한다.</li></ul><p>기계/운영체제에 상관없이 동일한 요청에 대해서는 동일한 결과를 주도록 <strong>‘표준화’</strong> 시킨다.</p><h3>2. REST (Representaional State Transfer)?</h3><p>REST는 HTTP 프로토콜을 그대로 활용하여 웹의 장점을 최대한 활용할 수 있는 아키텍처 원칙 세트이다.</p><p><strong>리소스를 이름으로 구분하여 해당 리소스의 정보를 주고 받는 모든 것을 의미한다.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aR72WNfwDrqBd-nWn_zlkg.png" /></figure><h3>2.1 왜 등장했을까?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/495/1*YIaPzUTIM2rdl3vOR7FUTA.png" /></figure><p>위에 분 로이필딩께서 ‘REST’를 제안하셨다.</p><p>로이필딩은 박사논문으로 ‘REST’를 발표했다. 그는 논문에서 ‘REST’를 만들게 된 이유로 아래와 같이 설명했다.</p><blockquote><strong><em>“</em>웹의 구조를 서술하는 비공식 문서들과 두 개의 소개 형식의 논문 등이 있었지만 실제 웹 구현이 너무 빨라 이미 시대에 뒤떨어진(out-dated) 경우가 많았다<em>” 또 “</em>그 당시 이미 캐시와 프록시 등이 웹에는 존재했지만 이에 대해 인식하는 표준은 없다시피 했다.<em>“</em></strong></blockquote><blockquote><strong>그렇기에 웹에 대한 새로운 표준, 또는 표준의 확장이 꼭 필요했다.</strong></blockquote><p>또, 로이필딩은 웹의 최대 장정 중 하나인<strong> ‘하위호환성’</strong>에 대해서도 고민하였다. 기존에 웹에서도 동작하고 ‘HTTP’ 프로토콜 기능을 증가시킬 방법으로 ‘REST’를 고안한 것이다.</p><h3>2.2 REST 구성요소</h3><ul><li>자원(Resource): URI</li><li>행위(Verb): HTTP Method</li><li>표현(Representation of Resource)</li></ul><h3>2.3 REST의 특징</h3><ul><li>Server-Client (서버-클라이언트 구조)</li><li>Stateless (무상태)</li><li>Cacheable (캐시 처리 가능)</li><li>Layered System (계층화)</li><li>Code-On-Demand (optional)</li><li>Uniform Interface (인터페이스 일관성)</li></ul><p>REST의 구체적인 특징을 자세하게 보고싶다면 아래에 글을 추천한다.</p><p><a href="https://gmlwjd9405.github.io/2018/09/21/rest-and-restful.html">[Network] REST란? REST API란? RESTful이란? - Heee&#39;s Development Blog</a></p><p><strong>REST라는 말이 어려워 보이지만 결국 권위있는 사람이 제시하는 HTTP를 사용하는 웹과 관련된 애플리케이션의 일관성을 위한 가이드 라인이다.</strong></p><h3>3. REST API?</h3><p>그렇다면 이제 위에 개념을 합쳐보자.</p><p><strong>‘Rest API’는 REST 가이드라인을 기반으로 API를 설계한 것을 ‘REST API’라고 한다.</strong></p><p>하지만, 실무에서 쓰이는 이 ‘REST API’라는 말이 로이필딩의 의도와는 다르게 변질된 거 같다.</p><p>왜냐하면 2016년에 Microsoft에서 <strong>Microsoft REST API Guidelines</strong>를 발표했지만 로이필딩은 그것은 REST API가 아니라 그냥 HTTP API라고 해야한다고 말했다.</p><p><a href="https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#microsoft-rest-api-guidelines">api-guidelines/Guidelines.md at vNext · microsoft/api-guidelines</a></p><p>대부분의 REST API라고 지칭하는 것들은 REST 가이드라인을 완벽하게 지키지 않았다.</p><p>특히, ‘Uniform Interface’의 제약 조건 중 ‘Self-descriptive messages’(메시지가 스스로 설명해야 한다. 즉, 메세지만 보고 무슨 뜻을 의미하는지 알아야한다.) ‘HATEOAS’ (애플리케이션의 상태는 HyperLink를 이용해서 전이되어야 한다.)를 거의 지키지 못하고 있다.</p><p>이에 대한 설명은 아래에 영상을 보기를 추천한다. REST API에 대한 근본적인 설명과 함께 웹의 특징을 잘 설명해주셔서 이해하기가 좋았다.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FRP_f5dMoHFc%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DRP_f5dMoHFc&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FRP_f5dMoHFc%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/e14e11e2ca1a33239956ab7af11552f9/href">https://medium.com/media/e14e11e2ca1a33239956ab7af11552f9/href</a></iframe><p>그리고 실무에서 REST API라고 지칭하지만, 사용자 ‘Authorization’ 방식으로 쿠키, 세션을 사용한다.</p><p>이것은 REST 규칙중 하나인 <strong>‘Stateless (무상태)’</strong>를 이미 위반한 것이다.</p><p>그러니깐 구글링 하였을 때 나오는 대부분의 ‘REST API’ 관련 블로그 글들은 <strong>로이필딩이 말하는 전통적인 ‘REST API’ 보다는</strong> ‘<strong>Microsoft REST API Guidelines’, 또는 실무에서 주로 쓰이는 API 설계 방식이라고 보는게 맞을 꺼 같다.</strong></p><p>REST API를 설계할 때 대표적인 방식을 살펴보면 <strong>‘URI는 동사보다는 명사, 대문자보다는 소문자를 사용하여야 한다’</strong></p><pre>HTTP GET &lt;http://www.appdomain.com/users/123&gt;<br>HTTP GET &lt;http://www.appdomain.com/users/123/address&gt;</pre><p>또 REST API 설계 방식 중 하나인 <strong>‘리소스에 대한 행위는 적절한 ‘HTTP Method (GET, PUT, POST, DELETE)’로 표현해야 한다</strong>’에 대해 살펴보자</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UaVO8wpmpBan72anlhp9BQ.png" /></figure><p>위에서 대표적인 CRUD에 대해 어떤 Method를 사용해야하는지 설명하고 있다.</p><p>그 중 PUT과 PATCH를 살펴보자.</p><ul><li>PUT: 해당되는 경우 객체를 바꾸거간 명명된 객체를 만든다.</li><li><strong>PATCH: 객체에 대한 부분 업데이트 적용</strong></li></ul><p>RFC문서에서도 PUT과 PATCH 역활을 위와 비슷하게 설명하고 있다.</p><ul><li><a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6">HTTP/1.1: Method Definitions</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc5789">RFC 5789: PATCH Method for HTTP</a></li></ul><p>오늘은 이 규칙을 중점적으로 나의 API 서버는 과연 이것을 잘 지키고 있는가를 살펴보고자 한다.</p><h3>4. 실습</h3><p>‘Express’를 이용한 서버에서 ‘Mongoose’를 통해 MonogDB에 기본적인 CRUD를 담당한다.</p><p>그 중 ‘UPDATE’에 주목해보자.</p><pre>exports.update = asyncHandler(async (req, res, next) =&gt; {<br>  const updatedArticle = await Article.findByIdAndUpdate(<br>    req.params.article_id,<br>    req.body,<br>    { new: true }<br>  );</pre><pre>  res.json({ result: &quot;ok&quot;, article: updatedArticle });<br>});</pre><pre>router.put(<br>  &quot;/:article_id&quot;,<br>  verifyToken,<br>  verifyArticleId,<br>  articlesController.update<br>);</pre><p>만약 클라이언트가 아래와 같이 요청을 보낸다면</p><pre>HTTPRequest<br>PUT `/articles/${articleId}`<br>{<br>  title: &quot;Hello World&quot;<br>}</pre><p>아래와 같이 MongoDB에 저장된 데이터가 업데이트 되어야 한다.</p><pre>{<br>  _id: &quot;627620efd2a10c5dd2d0cabd&quot;<br>  title: &quot;Hello World&quot;<br>}</pre><p>하지만, 내가 사용한 ‘findByIdAndUpdate’는 <strong>전체 데이터를 ‘replace’ 하는것이 아니라 클라이언트가 보낸 데이터를 이용하여 이미 존재하는 아이템에 해당 속성(’title’)을 업데이트 할 것이다.</strong></p><p><a href="https://mongoosejs.com/docs/api/model.html#model_Model.findOneAndUpdate">Mongoose v6.5.2:</a></p><p>위에 ‘<strong>Microsoft REST API Guidelines’에 따르면 PUT Method은 전체 요청으로 받은 객체를 통채로 교체할 때 (replace) 때 사용해야 하지만 코드에서는 그러고 있지 않다.</strong></p><p>그럼 이것을 조금더 REST스럽게 바꿔보자. PUT과 PATCH에 대한 동작을 각각 정의해주었다.</p><pre>exports.replace = asyncHandler(async (req, res, next) =&gt; {<br>  const replacedArticle = await Article.findOneAndReplace(<br>    req.params.article_id,<br>    req.body,<br>    {<br>      upsert: false,<br>      new: true,<br>    }<br>  );</pre><pre>  res.json({ result: &quot;ok&quot;, article: replacedArticle });<br>});</pre><pre>exports.update = asyncHandler(async (req, res, next) =&gt; {<br>  const updatedArticle = await Article.findByIdAndUpdate(<br>    req.params.article_id,<br>    req.body,<br>    { new: true }<br>  );</pre><pre>  res.json({ result: &quot;ok&quot;, article: updatedArticle });<br>});</pre><pre>router.put(<br>  &quot;/:article_id&quot;,<br>  verifyToken,<br>  verifyArticleId,<br>  articlesController.replace<br>);</pre><pre>router.patch(<br>  &quot;/:article_id&quot;,<br>  verifyToken,<br>  verifyArticleId,<br>  articlesController.update<br>);</pre><p>replace를 관련 로직은 ‘mongoose’에 <strong>‘findOneAndReplace’</strong> 메서드를 사용하였다.</p><p><a href="https://mongoosejs.com/docs/api.html#model_Model.findOneAndReplace">Mongoose v6.5.2: API docs</a></p><p>이제 ‘PUT’ Method에 대해서 적절한 방식으로 동작하고 클라이언트가 보낸 데이터를 이용하여 ‘replace’, ‘update’가 가능해졌다.</p><p>포스트맨을 활용하여 API가 제대로 확인해보자.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6sC67ewXF-P1srxcC6fKcQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ktC3DCfBX2J6rBLJ6Jerwg.png" /></figure><p>위에서 보는것과 같이 REST API를 규칙을 따르려고 한 의도대로 PUT은 ‘replace’ PATCH는 ‘update’ 동작하고 있는 것을 볼 수 있다.</p><h3>5. 정리</h3><p>API와 REST에 대한 간단한 개념을 살펴보고 REST API에 대한 오해와 대표적인 규칙들에 대해 살펴보았다.</p><p>그리고 직접 그 규칙을 따르기 위해 노력해보았다.</p><p><strong>직접 실습해 본 결과 내가 한 부분이 굉장히 작은 부분임에도 불구하고 규칙을 전부 따르기가 굉장히 어렵다는 것을 느꼈다.</strong></p><p><strong>그리고 진정 REST 규칙을 전부 지켜서 설계한 API를 RESTFul API라 지칭한다.</strong></p><p>그럼 우리 전부 RESTFul API를 향해 가야할까?</p><blockquote><em>“REST emphasizes evolvability sustain on uncontrollable system. </em><strong><em>If you think you have control over the system or aren’t interested in evolvability, don’t waste your time arguing about REST</em></strong><em> . — Roy T. Fielding</em></blockquote><p>로이필딩은 시스템 전체를 통제할 수 있다면 그리고 진화에 관심이 없다면(🥲 ) REST에 대해 따지느라 시간을 허비하지 말라고 한다.</p><p><strong>내가 생각하기에는 REST를 지키기 위해 노력하는 것도 중요하지만, 실무에서 클라이언트, 서버 단 개발자들 끼리의 소통과 사내에서 약속된 문서를 통해 효율적인 API 설계도 중요하지 않을까 생각해본다.</strong></p><p>하지만 마지막으로 진정 중요한 것은…!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/495/1*YIaPzUTIM2rdl3vOR7FUTA.png" /></figure><p>로이필딩이 말한 REST 규칙을 모두 지키지 않고 ‘RESTFul API’라고 지칭하지 말자.</p><p>이분이 화내신다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UUc0uaty6Vdk5QzKC8f86w.png" /></figure><h3>5.1 참고자료</h3><ul><li><a href="https://velog.io/@kjh03160/%EA%B7%B8%EB%9F%B0-REST-API%EB%A1%9C-%EA%B4%9C%EC%B0%AE%EC%9D%80%EA%B0%80">그런 REST API로 괜찮은가?</a></li><li><a href="https://www.redhat.com/ko/topics/api/what-is-a-rest-api">REST API(RESTful API, 레스트풀 API)란 - 서버, 구현, 사용법</a></li><li><a href="https://shoark7.github.io/programming/knowledge/what-is-rest">논문을 통한 REST에 대한 고찰</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=18a99929ba39" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>