<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Developer With-key</title>
        <link>https://velog.io/</link>
        <description>주니어 프론트엔드 개발자 입니다. </description>
        <lastBuildDate>Sun, 11 Dec 2022 08:41:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Developer With-key</title>
            <url>https://images.velog.io/images/with-key/profile/11d9c39d-2922-4bbe-a25e-0fd3b9ed9b35/21151853c6eef0c2fd86f87913942c51.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Developer With-key. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/with-key" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[손으로 연습하는 함수형 프로그래밍 1]]></title>
            <link>https://velog.io/@with-key/%EC%86%90%EC%9C%BC%EB%A1%9C-%EC%97%B0%EC%8A%B5%ED%95%98%EB%8A%94-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-1</link>
            <guid>https://velog.io/@with-key/%EC%86%90%EC%9C%BC%EB%A1%9C-%EC%97%B0%EC%8A%B5%ED%95%98%EB%8A%94-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-1</guid>
            <pubDate>Sun, 11 Dec 2022 08:41:13 GMT</pubDate>
            <description><![CDATA[<p>요즘 함수형 프로그래밍에 빠져있는 듯 하다. 선언적인 코드와 유연하게 함수를 자르고 붙임으로써 여러 결과를 얻을 수 있는 장점이 있는 듯하다. 아직 실제 구현 코드에서 이것을 자연스럽게 사용하는 것이 익숙하지 않아서 정확히 함수형 프로그래밍이란 &#39;이것&#39;이다 라고 할 순 없는 수준이다. 그래도 계속 반복적인 연습과 익숙해지기 위해 연습을 많이 해보고 있다.</p>
<p>함수형 프로그래밍에 대한 여러 이론적 개념도 중요하겠지만 함수를 붙이고 자르고 하는 것이 함수형 프로그래밍에 있어서 가장 기본이 되는 테크닉이고 이것을 내가 자유자재로 무의식적인 수준에서 가지고 놀 수 있어야 겠구나... 하는 생각이 들었다. 여러가지가 있지만 이 글에서는 <code>curry</code>, <code>go</code>, <code>pipe</code> 같은 기능을 하는 함수가 있는데 이것을 간단하게 설명해보고자 한다.</p>
<blockquote>
<p>아래 내용을 이해하기 위해서는 iterable, iterator에 대해서 어느정도 이해를 하고 있어야 한다.</p>
</blockquote>
<h1 id="go">go</h1>
<pre><code class="language-js">const go = (...args) =&gt; {
  return reduce((acc, el)=&gt; el(acc), args);
};</code></pre>
<p><code>go</code>함수는 인자로 받은 값과 함수를 연속적으로 실행시켜서 결과로 값을 반환하는 함수이다. 여기서 값이란, 함수가 아니라는 뜻이다. </p>
<p>go 함수의 특징은 인자로 입력되는 함수들이 연쇄적으로 실행되는데 이전 함수의 결과가 다음 실행될 함수의 인자로 다시 입력이 되는 형태를 가진다.</p>
<p>이 말이 무슨 말인지 아래 코드를 보면서 설명한다. go 함수에 첫번째 인자로 0, 그리고 3개의 함수를 인자로 넣었다. 이 경우 0이 다음 함수의 인자로 들어가서 두번째 인자이자, 첫번째 함수는 1을 반환한다. 그리고 다시 그 1일이 다음 함수에 인자로 들어가서 <code>1 + 10 = 11</code> 이라는 값을 반환하고 마찬가지로 11 이라는 값이 세번째 함수의 인자로 들어가서 <code>11 + 100 = 111</code> 이라는 값을 반환하고 마지막으로 111이 <code>console.log</code>의 인자로 들어가서 화면에 값이 출력되고 go 함수가 종료된다.</p>
<pre><code class="language-js">go(
  0,
  a =&gt; a + 1, 
  a =&gt; a + 10, 
  a =&gt; a * 100,
  console.log // 111
) </code></pre>
<p>이것이 가능한 이유는 go 내부에 있는 reduce 함수로 인해 가능하다. <code>js array proto</code>가 제공하는 기본 메서드와는 아주 조금 다르다. </p>
<pre><code class="language-js">const reduce = (f, acc, iter) =&gt; {
      // iter가 없다면, acc의 iteator를 iter로 대입하고,
      // 그 첫번째 값을 acc로 대입한다.
    if(!iter){
      iter = acc[Symbol.iterator]();
      acc = iter.next().value;
    }

      for(const el of iter){
        acc = f(acc, el);
    }

  return acc;
}</code></pre>
<p>Array뒤에 붙어서 <code>(e.g. Array.reduce())</code> 사용되는 것과 달리 직접 대상 배열, 더 정확히 말하면 <code>iterable</code>을 인자로 받는다. 그리고 Array 메서드가 아니기때문에 iterable은 모두 이 커스텀 reduce를 사용할 수 있다.</p>
<p>함수 구현 중간에 있는 로직으로 iterable를 인자로 받지 않았을때는 해당 값의 첫번째 값은 acc로 사용되도록 기능이 갖춰져 있다. </p>
<p>아무튼 이 <code>reduce</code>를 통해 go에서는 인자로 받은 함수를 연속적으로 실행하고 그것의 값을 acc로서 반환할 수 있는 것이다. 참고로 go에서 인자를 <code>...</code> 를 통해 받고 있는데 이것도 역시 iterable 이기때문에 reduce를 사용하여 go를 만들 수 있게되는 것이다.</p>
<h1 id="pipe">pipe</h1>
<p>pipe도 go와 비슷한 기능을 하는데 go와 다른 점은 값을 반환하는 것이 아니라 함수를 반환한다는 점이다. 즉 go는 인자를 입력했을 때 즉각적으로 연산이 되어 값이 반환된다면 pipe는 함수를 반환함으로써 지연 실행을 할 수 있다.</p>
<p>그래서 pipe는 go함수를 이용해서 만들 수 있는데, go를 지연 실행하는 식으로 만들면 결국 그게 pipe가 된다는 점을 알게 된다.</p>
<pre><code class="language-js">// go
const delayGo = (a) =&gt; go(a, ...args);

// pipe
const pipe = (...fs) =&gt; {
  return (a) =&gt; go(a, ...fs);
}</code></pre>
<p>그래서 이렇게 작성하면 go와 동일하게 작동하는 것을 알 수 있다.</p>
<pre><code class="language-js">const readyPipe = pipe(
  a =&gt; a + 1,
  a =&gt; a + 10,
  a =&gt; a + 100,   
)

const r = readyPipe(0) // 111</code></pre>
<h1 id="정리">정리</h1>
<p>정리하자면 <code>go</code>와 <code>pipe</code> 모두 여러 함수의 동작을 합쳐주는 기능을 가진 함수다. 어떤 데이터를 넣고 그 데이터에 작동시킬 여러 함수, 가령 <code>map</code>, <code>filter</code>, <code>reduce</code> 와 같은 함수들을 인자로 넣어서 결과를 얻는 것이다.</p>
<p><code>go</code>는 그 값을 즉각적으로 받을 수 있고, <code>pipe</code>는 일단 적용시킬 함수를 먼저 넣고 나중에 데이터를 넣어 값을 얻을 수 있다. 간단하지만 조금 더 실용적인 예시를 든다면 아래 코드와 같다.</p>
<pre><code class="language-js">// data
const products = [
  {
    name: &quot;반팔티&quot;,
    price: 15000,
    q: 1
  },
  {
    name: &quot;긴팔티&quot;,
    price: 20000,
    q: 2
  },
  {
    name: &quot;핸드폰케이스&quot;,
    price: 15000,
    q: 3
  },
  {
    name: &quot;후드티&quot;,
    price: 30000,
    q: 4
  },
  {
    name: &quot;바지&quot;,
    price: 25000,
    q: 5
  }
];

const goResult = go(
  products, (products) =&gt; map(p =&gt; p.price, products), 
  products, (products) =&gt; reduce((acc, price) =&gt; acc + price, products), 
  console.log // product의 가격의 합이 반환
)


const redayPipe = pipe(
  (products) =&gt; map(p =&gt; p.price, products),
  (products) =&gt; reduce((acc, price) =&gt; acc + price, products)
)

const pipeResult = readyPipe(products) // // product의 가격의 합이 반환</code></pre>
<h1 id="curry">curry</h1>
<p>여기에서 끝나지 않고, 커링이라는 개념이 있다. 커링이란, 여러 인자를 받는 함수를 단일 인자로 받는 여러개의 함수로 구성한 것을 의미한다. 처음부터 함수를 그렇게 만들어도 되지만, 우리가 만든 함수가 모두 커링이 적용되어 있을리가 없다.</p>
<p>그래서 커링이 적용되어 있지 않은 함수가 커링이 된 것처럼 함수의 모양을 바꿀 수 있도록 기능을 제공하는 함수를 만들어서 사용을 한다.</p>
<p>무슨 말이나면, 아래 처럼 원래 있던 함수를 커링된 함수로 만들어주는 중간 유틸 함수를 만든다고 생각하면 된다.</p>
<pre><code class="language-js">// 2개의 인자를 받는 함수를
const showNumber = (a, b) =&gt; a + b

// 이렇게 커링을 해서 
const curriedShowNumber = curry(showNumber);

// 이렇게 사용할 수 있도록 만들어주는 것이다.
curriedShowNumber(1)(2) // 3</code></pre>
<p>커리 함수의 구현은 아래와 같다. 일단 curry는 함수를 리턴하는 함수이며 가장 첫 인자로 함수를 받는다. 그리고 리턴된 함수에서 2가지로 분기가 되는데,</p>
<p>리턴된 함수의 인자의 갯수가 1개 초과인 경우라면 그냥 가장 처음에 받았던 함수를 실행시켜버린다. 근데 만약 인자의 갯수가 1개라면, 또 다시 함수를 리턴하고 그 함수에서 가장 처음에 받았던 함수를 실행한 값을 리턴하도록 만든다.</p>
<p>이 구현으로 인해 만약 curried 함수가 인자를 1개 이상을 받더라도 비록 커링이 실제로 적용되진 않았지만 그냥 함수 본연의 기능은 실행되게 된다.</p>
<pre><code class="language-js">const curry = (f) =&gt; (arg, ..._) =&gt; _.length ? f(arg, ..._) : (..._) =&gt; f(arg, ..._);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 개발자가 배운 Nest.js - ep.1]]></title>
            <link>https://velog.io/@with-key/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%B0%B0%EC%9A%B4-Nest.js-ep.1</link>
            <guid>https://velog.io/@with-key/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%B0%B0%EC%9A%B4-Nest.js-ep.1</guid>
            <pubDate>Wed, 14 Sep 2022 16:02:23 GMT</pubDate>
            <description><![CDATA[<h3 id="nestjs를-배우려는-이유">Nest.js를 배우려는 이유</h3>
<ol>
<li>웹 개발자로서 혼자 웹사이트를 만들줄은 알아야지 라고 생각했다. 그래서 얕게나마 백엔드 웹 프레임워크 하나 정도는 언젠가 배워야겠다고 생각을 했었다. 새로운 프레임워크들이 계속 나오겠지만, 하나만 잡고 평생 써먹으리라.. 하는 가성비 좋은 생각!</li>
<li>굳이 백엔드를 배우지 않더라도 대체할 수 있는 플랫폼이나 툴이 많지만 항상 미지의 영역이라고 생각했던 부분을 직접 파헤져 보고 싶었다. &quot;대체 JWT는 어떻게 만들어서 나에게 주는 것인가!?&quot;</li>
<li>JS에 익숙한 상태였기 때문에 node를 기반의 프레임워크를 선택하고자 했다.  express를 선택하지 않은 이유는 nest가 express에 비해 더 자율성이 더 적은 것 같았다. 자율성이 크면 클수록 초심자인 내 입장에서는 배우는데 시간이 더 오래 걸릴 것 같았다. 또한 회사 프로젝트는 스프링으로 구축이 되어 있는데 nest.js가 스프링과 흡사하다는 여러 의견도 선택의 이유가 되었다. 스프링은 아니지만 백엔드 팀원들과의 커뮤니케이션에 손톱만큼의 도움이라도 될까 싶었다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/with-key/post/c3fd86a7-311b-462f-a7d3-0ca6e4e6633f/image.png" alt=""></p>
<h3 id="어떻게-공부할까">어떻게 공부할까?</h3>
<ol>
<li>공식문서도 있고, 책도 있고, 강의도 있었는데 강의가 제일 효율적인 것 같다고 생각했다. 전~혀 모르기때문에 구글에서 찾은 파편화된 정보를 이어 붙이고 확인하고 하는 시간에 차라리 더 공부하자. 하는 생각이었다. </li>
<li>강의는 노마드코더의 우버잇츠 클론코딩으로 정했다. 여러 강의를 봤지만, 강의가 나에게 개인적으로 잘 맞는 편이고 더불어 백/프가 모두 있는 강의이기때문에 겸사 겸사 리액트도 더 공부할 수 있는 강의이다. </li>
<li>강의 커리큘럼 진행하면서 우버잇츠 클론 프로젝트를 진행한다. 근데 경험상 강의만 보면 따라치게 되기때문에 별로 남는게 없다. 위장 지식만 가득해질뿐! 그래서 호기롭게 바로 회사 동료들과 백엔드 포지션으로 사이드 프로젝트를 시작해버렸다. 이제 죽이되든 밥이되든 해야한다. </li>
</ol>
<h3 id="무엇을-공부할까">무엇을 공부할까?</h3>
<ol>
<li>일단 프레임워크는 nestjs! 그리고 DB는 postgresql, 그리고 프론트에서도 직접 사용할 일이 없어 써보지 못한 graphql로 만든다! 그리고 나중에 언급하겠지만, SQL을 쓰지 않고 타입스크립트로 SQL 명령을 내려주는 (오메..) ORM은 typeORM을 사용한다. (이 표현들이 맞는지 모르겠지만, 아직 배우고 있는 중이라...적절하지 못한 표현일수도 있습니다. 😅)</li>
<li>그리고 배포는....! 노마드코더 강의에서는 heroku를 통해서 진행하는데, 이왕 시작하는거 한번 더 해보자 하는 생각에 항상 말로만 듣던 로드밸런서.. ec2, RDB 이런 것도 사용해보기 위해 aws beanstalk을 사용해보려고 한다. 사실 프론트엔드 배포를 vercel과 같은 플랫폼을 이용해왔어서 aws에 대해 아는게 없다.. 🤔 그래서 과감하게 프론트엔드 배포도 vercel이 아닌 nginx를 띄우고 그 안에서 배포를 하고 이 과정에서 Docker도 사용해보고..  Travis CI 도 이용해서 배포 자동화까지 구축하는게 목표다. </li>
<li>그리고 아직 한번도 시도해보지 못한 테스트코드도 이번 공부 계획에 포함했다. 백엔드 테스트코드까지는 조금 버거울 것(변명..) 같아서 프론트엔드 테스트코드부터 중점적으로 해보려고 한다. jest와 cypress를 배워보자! </li>
</ol>
<h3 id="첫-날-느낌">첫 날 느낌</h3>
<ol>
<li>노마드코더 우버잇츠가 나름 &quot;고급&quot; 강의였다. 그래서 강의를 껐다. 일단 nest.js의 기본 강의를 수강했다. Controller, Entity, DTO, Service, Repository 등 생소한 용어들이 마구 나오기 시작했다. 리액트 훅을 처음 들은 느낌이랄까. Component, Props, State 를 처음 들었을 때 느꼈던 그 느낌을 오래만에 다시 느꼈다.</li>
<li>nestjs는 굉장히 견고했고, 무척 타이트했다. 개발하는 방식이 딱딱 정해져있어서 그 방식만을 따라가야했다. 그래서 굉장히 만족스러웠다. Repository 패턴을 적용하면서 Controller, Service, Repository의 목적을 알게 됐고 또한 목적에 맞게 퍼즐 맞춰지듯이 코드가 착착 정리가 됐다. </li>
<li>이러한 패턴으로 코딩을 지속하면서 &quot;리액트가 굉장히 자유분방했구나&quot; 하는 다시금 생각이 들었다. 가령 리액트에서는 컴포넌트 하나를 만들더라도 방법이 굉장히 많아서 항상 고민을 하게 된다. 컴포넌트의 상태값은 어떻게 분리하는게 좋을지, 서버로부터 데이터는 어떤식으로 fetching을 하고 어떻게 관리를 하는게 좋을지.. 등. 어쩌면 Controller, Service, Repository 들의 역할이 리액트 컴포넌트에서도 같은 구조를 가질 수도 있지 않을까 하는 생각이 문득 들었다. 정말 아주 문득 든 생각이라 사실 아직 구체화는 되진 않았다.</li>
<li>클래스의 향연이었다. 리액트에서는 함수 컴포넌트를 주로 쓰게 되면서 클래스를 쓸 일이 거의 없었다. 사실 프레임워크이다보니 클래스를 클래스로 쓰는게 아니라 &quot;그렇게 써야해&quot; 라고 해서 아직 클래스 자체의 장점을 몸소 느끼진 못했지만 어찌됐건 컴포넌트 만들듯이 클래스를 만들고 있는건 사실이니까. 이전보다 클래스와 많이 친해졌다. Service에서는 주로 Repository를 통해 DB에서 가져온 데이터(값)를 가공하고 반환하는 로직이 많이 들어있는 것 같았다. 리액트에서도 서버에서 가져온 값을 가공하거나 처리하기 위해 로직들이 필요한데 그럴 때 클래스를 써볼까? 하는 생각이 들었다. 물론 커스텀훅이나 또는 전역상태관리쪽에 그 로직들을 담을 수 있어서 굳이 클래스까지 거칠 필요가 있나? 싶은 생각도 같이 들었지만 그 로직을 다시 클래스로 담아서 처리한다면? 하는 생각이 들어서 곧 한번 해보려고 한다.</li>
</ol>
<h3 id="다음-이야기">다음 이야기</h3>
<ol>
<li>nest 기초 강의를 끝내고 다시 우버잇츠로 돌아오다. 근데 graphql이 다시 새롭다. 갑자기 튀어나온 Resolver..!</li>
<li>Entity? DTO? </li>
<li>모듈? 다이나믹 모듈..?</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[nest에 typeORM, postgreSQL  설정하기]]></title>
            <link>https://velog.io/@with-key/nest%EC%97%90-typeORM-postgreSQL-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@with-key/nest%EC%97%90-typeORM-postgreSQL-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 10 Aug 2022 17:16:02 GMT</pubDate>
            <description><![CDATA[<p>(1) db설치 후, 유저 비밀번호 생성하기</p>
<pre><code># 비밀번호 설정
alter user &lt;username&gt; with password &#39;1234&#39;;</code></pre><ul>
<li><p>기타 명령어
  유저 이름 조회</p>
<pre><code>\du</code></pre><p>유저 생성</p>
<pre><code>create user &lt;username&gt;;</code></pre><p>옵션 부여 </p>
<pre><code># 슈퍼유저 권한 부여
alter user &lt;username&gt; with superuser;</code></pre></li>
</ul>
<p>(2) nestjs/typeorm, typeorm, pg 설치</p>
<pre><code class="language-bash">npm install --save @nestjs/typeorm typeorm pg</code></pre>
<p>(3) <code>app.module.ts</code> 설정
<code>TypeOrmModule</code> 을 <code>imports</code> 에 추가한다.</p>
<pre><code class="language-ts">TypeOrmModule.forRoot({
      type: &#39;postgres&#39;,
      host: &#39;localhost&#39;,
      port: 포트번호,
      username: &#39;username&#39;,
      password: &#39;비밀번호&#39;,
      database: &#39;dbname&#39;,
      synchronize: true,
      logging: true,
    }),</code></pre>
<p>(4) db정보 환경변수로 관리하기 
패키지 설치</p>
<p>dotenv를 내장하고 있다.</p>
<pre><code>npm install --save @nestjs/config</code></pre><p>cross-env는 가상 변수를 설정할 수 있게 해준다. OS에 상관없이 쓸 수 있게 해준다.</p>
<pre><code>npm install cross-env</code></pre><p><code>package.json</code> scripts를 수정한다. 이 스크립트가 실행될 때 env에서 ENV를 dev로 변경해주는 것을 실행하게 되는 것 같다.</p>
<pre><code class="language-json">&quot;start:dev&quot;: &quot;cross-env ENV=dev nest start --watch&quot;,</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[nest 에서 authentication 흐름 살펴보기]]></title>
            <link>https://velog.io/@with-key/5.10-Custom-Decorator-Recap-authentication</link>
            <guid>https://velog.io/@with-key/5.10-Custom-Decorator-Recap-authentication</guid>
            <pubDate>Tue, 09 Aug 2022 16:59:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>노마드코더 우버 잇츠 5.10 ~ 11 강의에 대한 정리 내용입니다.</p>
</blockquote>
<h2 id="authentication-정리">authentication 정리</h2>
<h3 id="part-1">part 1</h3>
<p>(1) 클라이언트에서 header를 통해 토큰을 서버로 보냄
(2) req.headers의 값을 사용해서 db 내에 있는 user의 정보를 얻기 위해 미들웨어를 생성함
(3) 생성한 미들웨어를 사용하기 위해서 app.module.ts에서 등록함</p>
<pre><code class="language-ts">export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(JwtMiddleware).forRoutes({
      path: &#39;/graphql&#39;,
      method: RequestMethod.POST,
    });
  }
}</code></pre>
<p>(3) 미들웨어에서는 jwtService를 이용해서 토큰을 <code>verify()</code>함</p>
<pre><code class="language-ts">import { Injectable, NestMiddleware } from &#39;@nestjs/common&#39;;
import { NextFunction, Request, Response } from &#39;express&#39;;
import { JwtService } from &#39;./jwt.service&#39;;
import { UserService } from &#39;../users/users.service&#39;;

@Injectable()
export class JwtMiddleware implements NestMiddleware {
  constructor(
    private readonly jwtService: JwtService,
    private readonly userService: UserService,
  ) {}
  async use(req: Request, res: Response, next: NextFunction) {
    if (&#39;x-jwt&#39; in req.headers) {
      const token = req.headers[&#39;x-jwt&#39;];
      const decoded = this.jwtService.verify(token.toString());
      if (typeof decoded === &#39;object&#39; &amp;&amp; decoded.hasOwnProperty(&#39;id&#39;)) {
        try {
          const user = await this.userService.findById(decoded[&#39;id&#39;]);
          req[&#39;user&#39;] = user; // request 안에 user 라는 property를 새롭게 만들어 준 것
        } catch (e) {
          console.log(e);
        }
      }
    }

    // 동작이 끝나면 next()가 실행되도록 구현
    next();
  }
}</code></pre>
<p><strong>(4) verify()를 통해 payload를 알수있고, 거기에서 얻은 아이디를 통해 유저를 조회함. 유저를 조회할때는 userService를 이용함 (미들웨어 전체 코드 중에서 일부)</strong>
<code>findById</code>라는 메서드는 useService내에서 생성됐고, <code>Repository</code>를 통해 typeOrm의 <code>findOne</code>을 통해 db를 조회했다.</p>
<pre><code class="language-ts">if (typeof decoded === &#39;object&#39; &amp;&amp; decoded.hasOwnProperty(&#39;id&#39;)) {
  try {
    const user = await this.userService.findById(decoded[&#39;id&#39;]);
    req[&#39;user&#39;] = user; // request 안에 user 라는 property를 새롭게 만들어 준 것
  } catch (e) {
    console.log(e);
  }</code></pre>
<p><strong>(5) 유저를 찾으면, <code>req[&#39;user&#39;] = user</code> 를 통해서 request 객체에 user의 정보를 붙인다.</strong>
미들웨어의 역할은 여기에서 끝난다. req 이후에 미들웨어를 가장 먼저 만나기때문에 request 객체를 이렇게 다룰 수 있는 것이다. 만약 user의 정보가 없었다면 request객체에는 아무것도 붙는게 없게 될 것 이다. 아무튼 이렇게 미들웨어에서 변경된 request객체는 모든 resolver에서 사용할 수 있게 된다.</p>
<pre><code class="language-ts">const user = await this.userService.findById(decoded[&#39;id&#39;]);
req[&#39;user&#39;] = user; // request 안에 user 라는 property를 새롭게 만들어 준 것</code></pre>
<h3 id="part-2">part 2</h3>
<p><strong>(1) 개요</strong>
<strong>part1</strong>에서는 클라이언트로부터 받은 request.headers의 토큰을 조회하고, verify, 그리고 request객체에 추가하기 까지 했다. <strong>part2</strong>에서는 request객체에 추가한 user 정보를 바탕으로 다음 request 요청에 대한 후속 작업을 진행한다.</p>
<p>(2) <code>GraphQLModule.forRoot()</code> 를 통해 context를 조회할 수 있는데, 여기에서 request객체를 조회할 수 있다. 이 context는 apollo server의 context다. 그리고 여기에서 request에 우리가 원하는 정보를 추가할 수 있고, 여기에서 추가한 정보는 모든 Resolver에서 조회할 수 있다. 그리고 <code>canActivate</code>의 <code>context</code>에서도 조회할 수 있다.</p>
<pre><code class="language-ts">GraphQLModule.forRoot&lt;ApolloDriverConfig&gt;({
      driver: ApolloDriver,
      autoSchemaFile: true,
      context: ({ req }) =&gt; ({ user: req[&#39;user&#39;] }), // 이렇게
    }),</code></pre>
<p><strong>(3) 한편 가드를 사용했는데, 가드의 역할도 미들웨어와 비슷하다. (차이점은 아래에서 설명)</strong>
가드안에서 <code>implements CanActivate</code> 하고, canActivate를 구현한다. canActivate는 true또는 false를 반환한다. 이 반환값에 따라 request의 프로세스가 이어지거나 또는 멈춘다. canActivate의 인자에서는 context를 받아올 수 있는데, 이것은 <code>app.module.ts</code>의 <code>GrapthQLModule.forRoor({context})</code> 의 값이다. 안에 들어있는 데이터는 같지만, 형태가 다르다. <code>app.module.ts</code>는 gql context이고, canActivate에서 나온건 http context이다. 그래서 모양을 바꾸는 과정이 필요하다. (http ➡️ gql), (1) 에서 우리가 추가한 <code>{user: req[&#39;user]}</code>를 여기에서 확인할 수 있고, 이것을 활용해서 다음 로직을 구현할 수 있다.</p>
<p>여기에서 구현할 로직은 ExcutionContext에서 user 정보가 있으면, true를 반환한고 없으면 false를 반환하는 것이다. 그 반환값에 따라 request가 계속 진행될 것인지 아니면 멈출 것인지 결정된다.</p>
<pre><code class="language-ts">@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const gqlContext = GqlExecutionContext.create(context).getContext();
    const user = gqlContext[&#39;user&#39;];
    if (!user) {
      return false;
    } else {
      return true;
    }
  }
}</code></pre>
<p>(4) 가드를 거쳐 마지막 <code>Resolver</code>다. Resolver 에서는 한번더 데코레이터의 힘을 이용하는데 이것은 우리가 만든 커스텀 데코레이터이다. <code>createParamDecorator</code>를 이용해서 데코레이터를 만들 수 있다. 팩토리 function 에서는 <code>data</code>와 <code>context</code>를 가져올 수 있다. 단, context가 http context이기 때문에 gql context로 변환하는 과정이 필요하고, 유저가 있다면 해당 <strong>최종적으로 유저의 정보를 반환</strong>한다.</p>
<pre><code class="language-ts">export const AuthUser = createParamDecorator(
  // 안에석 구현되는 함수를 factory function 이라고 한다.
  (data: unknown, context: ExecutionContextHost) =&gt; {
    const gqlContext = GqlExecutionContext.create(context).getContext();
    const user = gqlContext[&#39;user&#39;];
    return user;
  },
);

// 데코레이터가 사용될 때

@Query(() =&gt; User) // resolver
@UseGuards(AuthGuard) // 미들웨어 이후로 실행됨 ➡️ 유저의 정보가 있다면 req 진행, 없다면 block
me(@Authuser() authUser: User) // 데코레이터에서 반환하는 값을 변수로 잡아 사용
  return authUser;
}</code></pre>
<p>이렇게 데코레이터를 통해서 최종적으로 user의 값을 반환받아 resolver에서 해당 값을 반환하면 클라이언트에게 그 값이 반환된다. 끝!!</p>
<h2 id="기타-개념">기타 개념</h2>
<ul>
<li><p>가드와 미들웨어의 차이
가드와 미들웨어는 실행시기가 다르다.
미들웨어는 next() 함수를 호출한 후 어떤 핸들러가 실행될지 알 수 없다. 반면에 가드는 excutionContext 인스턴스에 액세스 할 수 있으므로 다음에 실행될 작업을 정확히 알 고 있다. 그리고 <strong>가드는 미들웨어 바로 이후에 실행되어 2차적으로 인증을 할 수 있게 한다.</strong></p>
</li>
<li><p>모든 가드는 cacActivate 를 구현해야 한다. (implements CanActivate</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[5.9 Guard]]></title>
            <link>https://velog.io/@with-key/5.9-Guard</link>
            <guid>https://velog.io/@with-key/5.9-Guard</guid>
            <pubDate>Tue, 09 Aug 2022 06:17:02 GMT</pubDate>
            <description><![CDATA[<h3 id="guard">Guard</h3>
<p>(1) 가드란?
<strong>가드는 함수다. 클라이언트에서 온 Request를 더 진행할지, 말지 결정할 수 있는 함수다.</strong> @Injectable 할수 있다. 즉 <code>Provider</code> 다. 그리고 <code>CanActivate</code>를 <code>implements</code> 한다.</p>
<p><code>CanActivate</code> 의 역할은 가드에서 true를 return 하면, request를 진행시키고 false를 return 하면 request를 멈추게 한다.</p>
<p>현재 챕터에서 가드를 사용하는 이유가 뭘까?
전 챕터에서 <code>Resolver</code> 레이어에서 <code>gql context</code>를 통해 request가 발생할 때 유저의 정보를 받아 처리했다. (어떤 처리? : client에서 token을 보내고 해당 토큰의 유저 정보를 db에서 조회하는 과정) 하지만 <code>Resolver</code>에 로직이 있는 것은 별로 좋지 못한 모양새인듯하다. 그래서 이것을 <code>Resolver</code> 전 레이어에서 처리하기 위해 가드(미들웨어인 것 같다)를 생성하고, 가드에서 분기 로직을 처리한다. 가드에서 true를 return 하면 request가 계속 진행되고, false를 return 하면 request가 멈춘다.</p>
<p>(2) 가드 생성하기
auth 모듈안에서 <code>auth.guard.ts</code>로 생성한다. </p>
<pre><code class="language-ts">@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    console.log(context);
    return false; // canActivate()는 boolean을 return 해야 한다.
  }
}</code></pre>
<p><strong>ExecutionContext: request의 context에 접근할 수 있게 해주는 것. 참고로 graphql의 context가 아니라 현재 pipline의 context다.</strong></p>
<p>(3) 생성한 가드 사용하기
AuthGuard를 생성하고 이것을 사용하려면, <code>Resolver</code>에서 사용한다.</p>
<pre><code class="language-ts">@Query(() =&gt; User)
  @UseGuards(AuthGuard) // @UserGuard안에 생성한 가드를 넣어준다.
  me() {}</code></pre>
<p>(4) query 요청해보기</p>
<pre><code class="language-gql">{
    me {
        eamil
    }
}</code></pre>
<p>query를 요청하면, 가드에서 표시한 context가 표시된다. 다만 현재 context는 http context이기때문에 gql context로 변경해야 한다고 한다. 그리고 변경한 gql guard에서 user정보를 꺼내서 user 정보의 유무에 따라 각각 boolean을 return 한다.</p>
<pre><code class="language-ts">// http context -&gt; gql context로 변경

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const gqlContext = GqlExecutionContext.create(context).getContext();
    const user = gqlContext[&#39;user&#39;];
    if (!user) {
      return false;
    } else {
      return true;
    }
  }
}</code></pre>
<h3 id="기타-개념">기타 개념</h3>
<ul>
<li>authentication : 누가 자원을 요청하는지 확인하는 과정 (token을 통해 id를 확인하는 과정)</li>
<li>authorization: 유저가 어떤 일을 하기전에 permission을 가지고 있는지 확인하는 과정</li>
</ul>
<h3 id="오류-해결">오류 해결</h3>
<pre><code class="language-ts"> @Query(() =&gt; User)
  @UseGuards(AuthGuard)
  me(@Context() context) {
    return true;
  }</code></pre>
<p>Resolver 에서 @Query 부분에서 User를 넣으면 아래와 같이 에러가 발생했다. 에러가 발생한 원인은 Entity에서 @OnjectType을 넣어주지 않아서 발생한 것이었다. 이유는 좀 더 알아봐야겠다.
<img src="https://velog.velcdn.com/images/with-key/post/c85eadea-ce34-46b9-bd8a-52329981dfdc/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[5.8 GQL context]]></title>
            <link>https://velog.io/@with-key/5.8-GQL-context</link>
            <guid>https://velog.io/@with-key/5.8-GQL-context</guid>
            <pubDate>Tue, 09 Aug 2022 03:10:10 GMT</pubDate>
            <description><![CDATA[<h3 id="graphql-context">graphql context</h3>
<p><strong>(1) gql context란?</strong>
모든 graphql request에서 공유할 수 있는 값. request란, <code>@Query()</code>, <code>@Mutation()</code>을 뜻하고, resolver에 있는 <code>@Query</code>, <code>@Mutation</code> 에서 <code>@Context</code>로 불러와서 사용할 수 있다. <code>GraphQLModule.forRoot()</code>에 등록된 context는 매 request마다 호출된다.</p>
<p><strong>(2) context를 사용하는 방법</strong>
첫째, <code>GraphQLModule.forRoot()</code>에서 context를 먼저 등록해줘야 한다.</p>
<pre><code class="language-ts">GraphQLModule.forRoot&lt;ApolloDriverConfig&gt;({
      driver: ApolloDriver,
      autoSchemaFile: true,
      context: ({ req }) =&gt; ({ user: req[&#39;user&#39;] }),
    }),</code></pre>
<p>둘째, Resolver에 있는 @Query 에서 @Context를 사용해서 받는다.</p>
<pre><code class="language-ts">@Query(() =&gt; Boolean)
  me(@Context() context) {
    console.log(context.user); // user의 정보를 확인할 수 있음
    return true;
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[5.6 ~ 5.7 Nest middleware]]></title>
            <link>https://velog.io/@with-key/5.6</link>
            <guid>https://velog.io/@with-key/5.6</guid>
            <pubDate>Mon, 08 Aug 2022 01:36:55 GMT</pubDate>
            <description><![CDATA[<h2 id="client로부터-받은-http-headers-값-resolver에서-조회하기">client로부터 받은 http headers 값 Resolver에서 조회하기</h2>
<p><strong>(1) 미들웨어를 먼저 구현한다.</strong>
미들웨어에서는 client에서 온 http headers를 받아 해당 사용자가 누구인지 찾는 작업을 할 것 이다. middleware의 구현은 별도의 클래스에서 구현한다. (또는 함수 자체를 구현해서, 그것을 consumer에 등록해서 사용할수도 있다.) nest와 express 타입을 이용해서 구현한다.</p>
<pre><code class="language-ts">// src/jwt/jwt.middleware.ts

import { NestMiddleware } from &#39;@nestjs/common&#39;;
import { NextFunction, Request, Response } from &#39;express&#39;;

export class JwtMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // 어떤 동작을 하고
    console.log(req)

    // 동작이 끝나면 next()가 실행되도록 구현
    next();
  }
}</code></pre>
<p><strong>(2) 구현한 미들웨어를 사용한다. 예시에서는 모든 모듈에서 사용할 수 있도록 app.module.ts에 등록해본다.</strong></p>
<pre><code class="language-ts">export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(JwtMiddleware).forRoutes({
      path: &#39;/graphql&#39;,
      method: RequestMethod.POST,
    });
  }
}</code></pre>
<p><code>forRoutes</code>를 통해서 어떤 path에서 이 미들웨어를 작동시킬 것인지, 나아가 어떤 메소드에서 이 미들웨어를 작동시킬 것인지까지 설정 할 수 있다.</p>
<ul>
<li>특정 경로만 제외하고 싶을 때는 <code>apply().exclude</code> 를 통해서 제외할 수 있다.</li>
</ul>
<p><strong>(3) 미들웨어 로직을 구체화 한다.</strong>
미들웨어 내에서 DI를 하기 위해 <code>@Injectable()</code>를 붙여준다. <code>@Injectable()</code>가 없는 레이어에서는 DI를 할 수 없다.</p>
<ul>
<li>jwtService를 DI 해주고, jwtService를 이용해서 <code>this.jwtService.verify()</code>를 통해 client 받아온 token을 vaify 한다.</li>
<li>그리고 varify한 토큰을 통해 db에서 사용자 조회를 한다. 이것을 하기 위해 userService에 <code>findById()</code>라는 메서드를 생성하고, jwtMiddleware에 DI 한다. 그러면 아래와 같은 에러 메시지가 터미널에 표시된다.
<img src="https://velog.velcdn.com/images/with-key/post/84755063-f8a2-40d6-ba8e-f8693e0d78f4/image.png" alt="">
위 에러가 나타나는 이유는 <code>UserService</code>를 <code>Provider</code>로 가지고 있는 <code>user.module.ts</code>에서 <code>export</code> 해주지 않고 있기 때문이다. <pre><code class="language-ts">@Module({
imports: [TypeOrmModule.forFeature([User]), ConfigService], // Repository
exports: [UserService], // 다른 Provider에서 사용하고자 하는 것을 exports에 추가한다.
providers: [UserService, UsersResolver], // Service, Resolver
})
export class UsersModule {}</code></pre>
exports에 useService를 추가하고, 다시 jwtMiddleware에 돌아와서 <code>this.useService.findById()</code>를 이용해서 유저의 정보를 db에서 조회하는 메서드를 구현한다.</li>
</ul>
<h3 id="기타-개념">기타 개념</h3>
<ul>
<li>implements 와 extends는 다르다. implements는 새로 생기는 클래스가 implements된 클래스 처럼 행동해야 한다는 것을 의미한다.</li>
<li>verify는 시크릿키로 검증하지만, decode는 검증하지 않고 해독된 payload를 반환한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js에서 동적모듈 만들기]]></title>
            <link>https://velog.io/@with-key/5.3-5.5-JWT-Module-custom-module</link>
            <guid>https://velog.io/@with-key/5.3-5.5-JWT-Module-custom-module</guid>
            <pubDate>Sun, 07 Aug 2022 13:46:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>노마드코더 우버 잇츠 5.3 ~ 5.5 에 대한 정리내용입니다.</p>
</blockquote>
<h2 id="동적모듈로-만들기">동적모듈로 만들기</h2>
<p>(1) JwtModule에 <code>static forRoot()</code>를 추가해줌으로써 동적모듈로 만들 수 있다. <code>forRoot()</code> 함수는 Dynamic Modeule을 반환한다. <code>static</code> 메서드는<code>new</code> 키워드 없이 바로 클래스를 사용할 수 있게 해준다. </p>
<pre><code class="language-ts">@Module({})
export class JwtModule {
  static forRoot(): DynamicModule {
    return {
      // 동적모듈은 또 다른 모듈을 반환하는 모듈이다. JwtModule를 반환하는 것이다.
      module: JwtModule,  
      // 이 모듈의 Provider들을 다른 모듈에서 사용할 수 있게 하려면, exports 해야 한다.
      exports: [JwtService],
      // 현재 이 모듈에서 가지고 있는 Provider는 JwtService 가 있다는 뜻이다.
      // 이렇게 모듈에서 Provider가 추가될 때 마다 등록을 해준다.
      providers: [JwtService],
    };
  }
}</code></pre>
<h2 id="global-module-로-설정하기">global module 로 설정하기</h2>
<p>(1) global module 이란?</p>
<ul>
<li>global module은 다른 Service 레이어에서 사용하고자 할 때 imports를 하지 않아도 사용할 수 있다. 그래서 여러 곳에서 사용하는 모듈을 global로 설정하면 매번 imports 하지 않고 사용할 수 있다.</li>
<li>global module이 아닌 모듈을 Service 레이어에서는 어떤 모듈을 사용하고자 한다면, 해당 서비스의 모듈에서 imports 해줘야만 사용이 가능하다는 뜻이다. <strong>예를 들어, usesService에서 JwtModule을 사용하고자 한다면, useModule에서 JwtModule을 imports 해줘야 한다.</strong></li>
</ul>
<p>(2) global module로 만드는 방법은?</p>
<ul>
<li><code>@Global()</code> 데코레이터를 붙여 준다.</li>
</ul>
<h2 id="appmodule-➡️-jwtmodule-➡️-jwtservice-로-options-값-전달하기">app.module ➡️ jwt.module ➡️ jwt.service 로 options 값 전달하기</h2>
<p>(1) options으로 들어갈 forRoot()의 인자 타입 구현하기
jwt.interface.ts 파일을 생성하고 options의 타입을 구현한다. 그리고 <code>forRoot(options: 생성한 타입)</code>을 넣어 설정한다.</p>
<p>(2) appModule에서 jwtModule로 그리고 다시 jwtService 레이어로 보내기 
<code>jwt.module.ts</code>가 <code>app.module.ts</code> 에서 받은 options 값을 <code>JwtService</code> 레이어로 보내기 위해 <code>exports</code>에 값을 추가한다. 이때 <code>provide</code>와 <code>useValue</code>를 사용한다.</p>
<pre><code class="language-ts">export class JwtModule {
  static forRoot(options: JwtModuleOptions): DynamicModule {
    return {
      module: JwtModule,
      exports: [
        {
          provide: CONFIG_OPTIONS, // jwt.contants.ts 에서 상수로 관리
          useValue: options, // app.module.ts에서 forRoot()로 받은 값
        },
        JwtService,
      ],
      providers: [
        // Provider가 등록해줘야 exports 할 수 있다.
          {
            provide: CONFIG_OPTIONS,
            useValue: options,
          },
          JwtService,
        ],
    };
  }
}</code></pre>
<p>(3) jwt.module.ts에서 보냈다. 이제 DI를 통해서 서비스 레이어에서 받는다. 서비스 레이어에서는 constructor를 이용해서 provider를 DI 한다. 그러면 이제 options 값을 사용할 수 있다. <code>@Inject()</code> 인자에 있는 상수는 <code>jwt.module.ts</code> 에서 <code>exports</code>로 설정해준 값과 동일해야 한다.</p>
<pre><code class="language-ts">import { Inject, Injectable } from &#39;@nestjs/common&#39;;
import { CONFIG_OPTIONS } from &#39;./jwt.contants&#39;;
import { JwtModuleOptions } from &#39;./jwt.interfaces&#39;;

@Injectable()
export class JwtService {
  constructor(
    @Inject(CONFIG_OPTIONS) private readonly options: JwtModuleOptions,
  ) {}
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[nest #3 MVC + Repository pattern]]></title>
            <link>https://velog.io/@with-key/nest-3-MVC-Repository-pattern</link>
            <guid>https://velog.io/@with-key/nest-3-MVC-Repository-pattern</guid>
            <pubDate>Mon, 20 Jun 2022 17:24:29 GMT</pubDate>
            <description><![CDATA[<h3 id="provider">Provider</h3>
<pre><code class="language-ts">  @Module({
    imports: [TypeOrmModule.forFeature([Store])],
    providers: [StoreResolver, StoreService], // &lt;&lt; 이거
  })
  export class StoreModule {}</code></pre>
<ul>
<li>Provider는 nest의 기본 개념이다. 대부분의 기본 nest 클래스는 서비스, 레포짓토리, 팩토리, 헬퍼 등이 Provider에 포함된다.</li>
<li>Provider들을 다른 클래스에 주입하면서 서로 다양한 관계를 만들 수 있다. 가령, <code>Controller</code> 에서는 많은 클래스들을 필요로 한다. 예를 들어 Service가 필요한다고 했을 때, 이것을 <code>Controller</code> 내부에서 모두 구현 할 수 없다. 어딘가에 따로 정의가 되고 이것을 <code>Controller</code> 에서 사용 할 수 있도록 연결 시켜야 한다. 이때 서비스 클래스를 별도의 파일에서 구현하고, 그것을 <code>Controller</code>에 주입하여 연결시킨다. </li>
<li><code>xxx.module.ts</code> 안에서 <code>providers</code>에 여러 <code>providers</code> 를 넣어 사용할 설정을 한다.</li>
</ul>
<h3 id="controller">Controller</h3>
<pre><code class="language-ts">  @Controller(&#39;store&#39;)
  export class StoreController {
    storeService: StoreService;

    construtor(storeService: StoreService){
      this.storeService = storeService;
    }
  }

  // 또는 
  @Controller(&#39;store&#39;)
  export class StoreController {
    construtor(private storeService: StoreService) {}
  }
// private를 생성자 안에서 사용하면, 
// 접근제안자가 생성자 파라미터는 암묵적으로 class property로 선언된다.
// private를 사용하는 이유는 property를 class 안에서만 사용하기 위함이다.</code></pre>
<ul>
<li>컨트롤러란, 들어오는 요청을 처리하고 클라이언트에 응답을 하는 역할을 한다.</li>
<li>Controller에서 Service를 연결해서 사용하고자 한다면, controller class에서 <code>dependency injection</code> 해줘야 한다.</li>
</ul>
<h3 id="service">Service</h3>
<pre><code class="language-ts">@Injectable()
export class StoreService {
  getStore(): string {
    return &#39;hello store&#39;;
  }
}</code></pre>
<ul>
<li>소프트웨어 개발내의 공통 개념이다. 딱히 Nest.js, Js에서만 사용하는 개념이 아니다.</li>
<li><strong><code>@Injectable</code> 라는 데코레이터로 감싸져서 모듈에 제공되며, 이 서비스 인스턴스는 앱 전체에서 사용될 수 있다.</strong></li>
<li>서비스는 컨트롤러에서 데이터의 유효성 체크를 하거나 데이터베이스에 아이템을 생성하는 등의 작업을 하는 부분을 처리한다. </li>
<li>서비스는 Controller에서 처리하기 복잡한 것들을 처리하기 위한 역할을 하기 위해 존재한다. 보통 DB와의 커뮤니케이션이 이루어진다.</li>
<li>Service를 다양한 컴포넌트에서 사용하고자 할때는 module.ts 의 Provider에 등록이 되어야 한다.</li>
<li>Service Class에 붙어있는 <code>@Injectable</code> 은 해당 service를 다른 컴포넌트에서도 사용 할 수 있게 만들어주는 데코레이터이다.</li>
</ul>
<h3 id="dto">DTO</h3>
<ul>
<li>계층 간 데이터 교환을 위한 객체이다.</li>
<li>DB 에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용하는 객체를 말한다. </li>
<li>interface나 class를 이용해서 정의될 수 있으나, Nest에서는 class를 이용할 것을 추천하고 있다</li>
<li>DTO를 사용하는 이유는 데이터의 유효성을 체크하는데 효율적이고, 더 안정적인 코드로 만들어주며 그 자체가 데이터의 타입이 되기도 한다.</li>
</ul>
<h3 id="repository-패턴">Repository 패턴</h3>
<ul>
<li>데이터베이스에 관련된 일(작업)을 서비스에서 하지 않고, Repository라는 별도의 클래스에서 하는 것을 레포짓토리 패턴이라고 한다. </li>
<li></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[nest #2 : entity, repository, service, eco ]]></title>
            <link>https://velog.io/@with-key/nest-2-entity-repository-service-eco-system</link>
            <guid>https://velog.io/@with-key/nest-2-entity-repository-service-eco-system</guid>
            <pubDate>Sun, 19 Jun 2022 17:50:22 GMT</pubDate>
            <description><![CDATA[<h3 id="entity">entity</h3>
<p>graphQL의 <code>@ObjectType</code> 에서 병행해서 구현 할 수 있다. <code>@ObjectType</code>는 graphQL에서 schema 생성을 위해 필요한 것이고, <code>@entity</code>는 DB의 모델과 같아서 postgreSQL에 Entity와 같은 모양으로 table이 생성된다. <code>app.modules.ts</code> 에서 <code>synchronize</code> 동기화를 언제 할 것인지 설정 할 수 있다.</p>
<h3 id="repository">repository</h3>
<p><code>repository</code>란, <code>Entity</code>와 상호작용하는 것을 담당하는 개념이다. </p>
<h3 id="service">service</h3>
<p>db와 접근하는 파일이다.</p>
<h3 id="db와의-상호작용">DB와의 상호작용</h3>
<ul>
<li>Active Record
패턴은 모델 내에서 데이터베이스에 액세스하는 접근 방식. 소규모 앱에서 단순하게 사용하는데 좋다.</li>
<li>Data Mapper
모델 대신 리포지토리 내의 데이터베이스에 액세스하는 접근 방식. <code>repository</code>라는 별도의 클래스에서 모든 쿼리 메서드를 정의한다. 대규모 앱에서 유용하다.</li>
</ul>
<h3 id="recap-nest--graphql--mvc-ecosystem">Recap (Nest + GraphQL + MVC EcoSystem)</h3>
<ol>
<li><p><code>app.module.ts</code> 의 <code>entity</code> 설정에 <code>Store</code>가 있다. 이것을 통해 Store에서 생성한 <code>@Entity</code> 가 DB에 생성된다. </p>
<pre><code class="language-ts">{ //... 중략
entities: [Store],
}</code></pre>
</li>
<li><p><code>store.module.ts</code>파일, imports에서 <code>TypeOrmModule.forFeature([Store])</code>를 넣었다. 여기서 <code>Store</code>는 <code>Entity</code> 파일에 있는 그것이다. <code>forFeature</code>는 <code>TypeOrmModule</code>가 특정 feature를 import 할 수 있게 해준다. 이 경우 feature는 Store entity이다. </p>
</li>
<li><p>resolver에서는 새로 생성한 <code>StoreService</code>를 construtor에 넣었다(inject). 이것이 제대로 작동하려면, <code>store.module.ts</code>에서 <code>Provicer</code>에 넣어야 한다. 그래야 class construtor에 inject할 수 있다. 그렇게 하고나면, resolver에서 service에 접근 할 수 있다. <code>service</code>에 접근한다는 것은 비즈니스 로직은 service에서 처리하고 resolver에서는 service에서 생성한 로직함수만 return 하겠다는 말이다.</p>
<pre><code class="language-ts">@Resolver() // Resolver란 마치 로직이다.
export class StoreResolver {
 constructor(private readonly storeService: StoreService) {}

 @Query(() =&gt; [Store])
 getStore(): Promise&lt;Store[]&gt; {
   return this.storeService.getAll(); // service와 연결됨
 }

 @Mutation(() =&gt; Boolean)
 createStore(@Args() newStoreInfo: CreateStoreDto): boolean {
   return true;
 }
}</code></pre>
</li>
<li><p>service에서는 여러 비즈니스 로직 함수들이 있다. 그 가운데 <code>getAll(){ return this.store.find() }</code>라는 함수가 있다. 여기서 store란 무엇일까? <code>store</code>란 store entity의 <code>repository</code> 이다. service에서 <code>@InjectRepository(Store)</code> 함으로써 Repository가 된다. 이때 인자는 <code>@Entity</code> 이어야만 한다.</p>
<pre><code class="language-ts">@Injectable()
export class StoreService {
 construtor(
   @InjectRepository(Store)
    private readonly store: Repository&lt;Store&gt;,
 ){}

 getAll(): Promise&lt;Store[]&gt;{
   return this.store.find();
 }
}</code></pre>
<p>이 설정을 하고나면, repository에서 쓸 수 있는 모든 옵션을 사용 할 수 있다. 옵션이란, DB에서 정보를 가져오는 함수(명령)를 의미한다. </p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Apollo Client Part  - 1]]></title>
            <link>https://velog.io/@with-key/Apollo-Client-Part-1</link>
            <guid>https://velog.io/@with-key/Apollo-Client-Part-1</guid>
            <pubDate>Tue, 14 Jun 2022 17:39:32 GMT</pubDate>
            <description><![CDATA[<p>Apllo client에 대해서 알아보자. Apllo client는 전역 상태관리 뿐만 아니라 data fetching에 대한 여러 기능도 가지고 있다. 마치 redux와 react-query를 같이 합쳐 놓은 듯한 상태관리 라이브러리이다. </p>
<ul>
<li>초기설정</li>
<li>기본적인 query 작성</li>
<li>useQuery로 data fetching 하기</li>
<li>변수를 넣어서 useQuery data fetching 하기</li>
<li>그 밖의 내용 <ul>
<li>Apollo cache </li>
</ul>
</li>
</ul>
<p>초기 설정하기</p>
<pre><code class="language-jsx">const root = ReactDOM.createRoot(document.getElementById(&quot;root&quot;));
root.render(
  &lt;React.StrictMode&gt;
    // Provider로 감싸준다.
    &lt;ApolloProvider client={client}&gt;
      &lt;App /&gt;
    &lt;/ApolloProvider&gt;
  &lt;/React.StrictMode&gt;</code></pre>
<p>query 작성하기</p>
<pre><code class="language-jsx">// 컴포넌트

const client = useApolloClient();

 useEffect(() =&gt; {
   // query 작성하기
    client.query({
      query: gql`
        {
          allTweets {
            text
          }
        }
      `,
    })
   // setState 
   .then((res) =&gt; setData(res.data.allTweets));
  }, [client]);</code></pre>
<p>useQuery로 data fetching 하기</p>
<pre><code class="language-jsx">const GET_MOVIES = gql`
  // 원하는 명칭을 써줄 수 있다.
  query getMovies{
    allTweets {
      text
    }
  }    
`;

// 인자에는 gql을 넣어준다.
const { data, called, loading } = useQuery(ALL_MOVIES);</code></pre>
<p>변수 넣어서 Query 작성하기</p>
<pre><code class="language-tsx">const GET_MOVIE = gql`
  query getMovie($movieId:String!) {
    movie(id: $movieId){
      id
      title
    }
  }
`;

const { data, loading, error} = useQuery(GET_MOVIE, {
  variables: {
    movieId: id,
  }
})</code></pre>
<p>Apollo cache</p>
<ul>
<li>한번 서버에서 가져온 값은 cache에 저장되기 때문에 다시 서버에게 요청하지 않는다. 즉 loading이 <code>false</code> 이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js Deep dive : Custom document component & Script Component]]></title>
            <link>https://velog.io/@with-key/Next.js-Deep-dive-Custom-document-component-Script-Component</link>
            <guid>https://velog.io/@with-key/Next.js-Deep-dive-Custom-document-component-Script-Component</guid>
            <pubDate>Tue, 07 Jun 2022 16:14:08 GMT</pubDate>
            <description><![CDATA[<h3 id="custom-document-component">custom document component</h3>
<p>_document.tsx로 작성된다. </p>
<pre><code class="language-ts">// 필수 컴포넌트들
class CustomDocument extends Document{
  render(): JSX.Element {
    return (
      &lt;Html lang=&#39;ko&#39;&gt;
          &lt;Head&gt;
          &lt;body&gt;
          // Main에서 App 컴포넌트를 렌더링 해준다.
            &lt;Main /&gt;
          &lt;NextScript /&gt;
          &lt;/body&gt;
          &lt;/Head&gt;
      &lt;/Html&gt;
    )
  }
}

export default CustomComponent;</code></pre>
<p>_app과 _documnent의 차이 _app는 유저가 페이지를 불러올 때마다 브라우저에서 실행된다. _document는 서버에서 한 번만 실행된다. </p>
<p><strong>폰트 최적화 하기</strong>
구글 폰트에서 제공하는 폰트를 사용해서 최적화가 가능하다. Next.js가 구글 폰트에서 제공하는 폰트를 기반으로 하기 때문이다. </p>
<p>Next.js가 빌드될 때, 폰트를 다운받는다. 그래서 웹을 사용하는 유저는 별도로 폰트를 다운로드 받지 않기 때문에 로딩시간을 단축 할 수 있다. </p>
<h3 id="script-component">script component</h3>
<p>구글 에널리틱스, 또는 카카오 SDK 등, 다른 웹과 커뮤니케이션을 해야하는 경우가 있다. 이때 프로젝트 내에서 <code>script</code>를 통해 진행하는데, Next.js에서는 이러한 script도 최적화를 시켜준다.</p>
<p><code>strategy</code>는 3가지가 있다. <code>beforeInteractive</code>, <code>afterInterative</code>, <code>lazyOnLoad</code></p>
<ul>
<li><p><code>beforeInteractive</code>: 페이지를 다 불러와서 상호작용 하기 전에 스크립트를 불러오는 전략. 유저가 페이지와 상호작용하기 전에 꼭 스크립트를 불러와야 한다면 사용. 하지만 대부분의 스크립트는 페이지를 불러오기 전에 불러올 필요가 없을 것.
예를 들어, 채널톡과 같은 챗봇 서비스가 있다면 그건 페이지를 다 불러오고 나서 불러와도 상관 없을 것 이다. </p>
</li>
<li><p><code>afterInterative</code>: 기본 전략이다. 앱 전체를 모두 불러오고 나서 다른 스크립트를 불러오는 전략이다.</p>
</li>
<li><p><code>lazyOnLoad</code>: 스크립트를 불러오긴 하는데, 스크립트를 불러오는게 최우선이 아니다. 모든 데이터나 소스를 모두 불러오고 나서야 스크립트를 실행하겠다는 전략이다.</p>
</li>
</ul>
<pre><code class="language-jsx">&lt;Script strategy=&#39;afterInterative&#39; onLoad={()=&gt;{
    // 실행할 코드
  }} /&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js Deep dive 
: Response and Redirections]]></title>
            <link>https://velog.io/@with-key/Next.js-Deep-dive-Response-and-Redirections</link>
            <guid>https://velog.io/@with-key/Next.js-Deep-dive-Response-and-Redirections</guid>
            <pubDate>Tue, 07 Jun 2022 15:31:54 GMT</pubDate>
            <description><![CDATA[<p><code>middleware</code>를 사용해서 router에 대한 Response를 설정 할 수 있다. 그리고 <code>NextResponse.redirect()</code>를 통해서 redirect 할 수 있다. 클라이언트에서 커스텀 훅을 통해서 페이지를 가드하는 방법도 있지만, 그 방식은 가드가 적용되기전까지 의도치 않게 사용자에게 화면이 보여지는 현상이 있다. 미들웨어를 통한 방법은 클라이언트 코드가 실행되기전에 먼저 실행되기 때문에 더 매끄럽게 처리 할 수 있다. </p>
<p>API call에 대한 응답을 통해 router를 조작하던 방식과 달리, 인증되지 않은 유저는 처음부터 API call을 보내지 않기 때문에 불필요한 요청을 줄이는 효과도 있다.</p>
<pre><code class="language-ts">export default function useUser(){
  const { data, error } = useSWR&lt;ProfileResponse&gt;(&quot;/api/user/me&quot;);
  const router = useRouter();

  // API call을 해서 data가 있으면, `/enter`로 이동시킨다.
  useEffect(()=&gt;{
    if(data &amp;&amp; !data.ok){
      router.replace(&quot;/enter&quot;);
    }
  },[data, router]);

  return { user: data?.profile, isLoading: !data &amp;&amp; !error };
}</code></pre>
<p>미들웨어 코드</p>
<pre><code class="language-typescript">// _middleware.ts
export function middleware(request, event){
  // req.url: 유저가 요청한 url
  // req.cookies: 브라우저에 저장된 쿠키

  if(){

  }
}</code></pre>
<p><code>NextResponse</code>는 클라이언트로 json을 보내거나 rewrite, redirect 등을 할 수 있다. <code>return NextResponse....</code> 처럼 반드시 <code>return</code>을 같이 붙여줘야 정상적으로 기능이 작동한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-qeury + Next.js :: Prefetching (SSR)]]></title>
            <link>https://velog.io/@with-key/React-qeury-Next.js-Prefetching-SSR</link>
            <guid>https://velog.io/@with-key/React-qeury-Next.js-Prefetching-SSR</guid>
            <pubDate>Tue, 05 Apr 2022 02:39:12 GMT</pubDate>
            <description><![CDATA[<p><code>getServerSideProps()</code>를 이용해서 클라이언트 서버단에서 data를 fetching하고, 그 데이터를 prefetching 하는 방법에 대해 알아본다. 이 방법을 사용하면, 최초 화면이 렌더링 될 때는 클라이언트 서버에서 data를 fetching 하기 때문에 API 서버와 통신하는 이력을 볼 수 없을 것이다. 이후 같은 query key의 데이터를 다시 fetching 하면 클라이언트에서 data를 fetching 한다. (해당 query가 stale 상태일 때)</p>
<ol>
<li>react-query 설정
SSR을 위한 설정에서 반드시 필요한 것은 <code>Hydrate</code> 이다. 공식문서에서 제시하는 방법대로 설정한다.<pre><code class="language-ts"></code></pre>
</li>
</ol>
<p>import &quot;../styles/globals.css&quot;;
import type { AppProps } from &quot;next/app&quot;;
import { Hydrate, QueryClient, QueryClientProvider } from &quot;react-query&quot;;
import { ReactQueryDevtools } from &quot;react-query/devtools&quot;;</p>
<p>const queryClient = new QueryClient();</p>
<p>function MyApp({ Component, pageProps }: AppProps) {
  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}> // &lt;&lt; 이 부분
        &lt;Component {...pageProps} /&gt;
        <ReactQueryDevtools />
      </Hydrate>
    </QueryClientProvider>
  );
}</p>
<p>export default MyApp;</p>
<pre><code>
2. component
`Next.js`에서 SSR을 하기 위해서는 컴포넌트 파일 내에서 `getServerSideProps()`를 사용한다. 해당 함수는 `Next.js 클라이언트 서버` 단에서 실행된다. 프로세스는 다음과 같다. 
&gt;[queryClient 생성] -&gt; [queryClient에서 prefetchQeury()를 이용해서 data fetching] -&gt; [props에서 `dehydratedState: dehydrate(qc)`로 return] -&gt; [컴포넌트 단에서 같은 query key로 useQuery()] -&gt; [{data}를 활용해서 rendering]




```ts
export const getServerSideProps: GetServerSideProps = async (context) =&gt; {
  const { id } = context.query;
  const qc = new QueryClient();
  await qc.prefetchQuery(
    [&quot;person&quot;],
    async () =&gt; {
      const { data } = await axios.get(`http://localhost:3001/person/${id}`);
      return data;
    }
  );

  return {
    props: {
      name: &quot;with&quot;,
      id,
      dehydratedState: dehydrate(qc), // 반드시 dehydratedState 이어야 함
    },
  };
};
</code></pre><ol start="3">
<li>적용 확인</li>
</ol>
<p>적용 후 컴포넌트를 Refresh 하면, 최초에 데이터 fetching이  network에 보이지 않는 것을 확인 할 수 있다. 클라이언트 서버단에서 데이터  fetching이 이루어 졌기 때문이다. </p>
<p><img src="https://velog.velcdn.com/cloudflare/with-key/a89a098b-1e5f-46f3-a95b-f0699141f916/image.png" alt=""></p>
<ol start="4">
<li>전체 코드<pre><code class="language-ts">import axios from &quot;axios&quot;;
import { GetServerSideProps } from &quot;next&quot;;
import React from &quot;react&quot;;
import { dehydrate, QueryClient, useQuery } from &quot;react-query&quot;;
</code></pre>
</li>
</ol>
<p>const Item = ({ id }: any) =&gt; {
  const { data } = useQuery(
    [&quot;person&quot;],
    async () =&gt; {
      const { data } = await axios.get(<code>http://localhost:3001/person/${id}</code>);
      return data;
    },
    {
      staleTime: 3000,
    }
  );</p>
<p>  return (
    <div>
      <div>{data?.name}</div>
      <div>{data?.age}</div>
    </div>
  );
};</p>
<p>export const getServerSideProps: GetServerSideProps = async (context) =&gt; {
  const { id } = context.query;
  const qc = new QueryClient();
  await qc.prefetchQuery(
    [&quot;pers1on&quot;],
    async () =&gt; {
      const { data } = await axios.get(<code>http://localhost:3001/person/${id}</code>);
      return data;
    },
    {
      staleTime: 10000,
    }
  );</p>
<p>  return {
    props: {
      name: &quot;with&quot;,
      id,
      dehydratedState: dehydrate(qc),
    },
  };
};</p>
<p>export default Item;</p>
<pre><code>

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[11.9 Unbound Mutations]]></title>
            <link>https://velog.io/@with-key/11.9-Unbound-Mutations</link>
            <guid>https://velog.io/@with-key/11.9-Unbound-Mutations</guid>
            <pubDate>Sun, 03 Apr 2022 14:03:58 GMT</pubDate>
            <description><![CDATA[<p>unbound mutations 이란, 내가 원하는 곳에서 원하는 것을 mutate 하는 것이라고 말할 수 있다. 예를 들어 내가 상품 페이지에서 user 정보에 대한 cache값을 mutate 할 수 있다는 뜻이다.
코드</p>
<pre><code class="language-ts">// unbound mutate는 `useSWRConfig()`에서 가져온다.

const { mutate: unboundMutate } = useSWRConfig();

unboundMutate(key, data, revalidateBoolean)
// data: 변경하고자 하는 새로운 데이터</code></pre>
<pre><code class="language-ts">mutate(&#39;/api/users/me&#39;)
// 만약 인자가 없으면, 단순히 refetch만 한다.</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[11.8 Bound Mutations]]></title>
            <link>https://velog.io/@with-key/11.8-Bound-Mutations</link>
            <guid>https://velog.io/@with-key/11.8-Bound-Mutations</guid>
            <pubDate>Sun, 03 Apr 2022 13:39:08 GMT</pubDate>
            <description><![CDATA[<h2 id="bound-mutation-구현하기">bound mutation 구현하기</h2>
<p>SWR 캐시를 mutate 해야 한다. mutate란, 돌연변이, 즉 <code>변형</code>을 의미한다. SWR 캐시를 변경하고자 하는 목적은 현재 view 상에서 보여지는 data는 swr의 캐시 data이기 떄문이다. 캐시된 data를 mutate 함으로써 view를 업데이트 하는 것이다.</p>
<pre><code class="language-ts">const {data, mutate} = useSWR();

const onFavClick = () =&gt; {
    mutate({}, true) // 두번쨰 파라미터에는 revalidation 여부다. 
}
</code></pre>
<p>bound mutate에 첫번째 자리에 들어간는 파라미터 <code>{ }</code>는 변경될 새로운 데이터이다. 그리고 두번째 자리는 revalidation 여부이다. revalidation란? 해당 요청을 다시 server로  request 하는 것이다. </p>
<p>bound mutation 이란, 제한된 변형을 뜻한다. 다시 말해 하나의 <code>useSWR</code>에 대한 mutate이다. 다른 개념으로 unbound mutation도 있다. <code>unbound mutation</code>란, 직접 요청한 useSWR 뿐만 아니라, 다른 화면에 있는 다른 요청들의 데이터도 변경하는 것이다.</p>
<pre><code class="language-ts">const onFavClick = () =&gt; {
    if (!data) return;
    mutate({ ...data, isLiked: !data?.isLiked }, false); // cache만 변경하여 화면의 UI를 변경
    toggleFav({}); // backend server로 데이터 fetching을 요청
  };</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js Deep dive :: _document and Fonts]]></title>
            <link>https://velog.io/@with-key/Next.js-Deep-dive-document-and-Fonts</link>
            <guid>https://velog.io/@with-key/Next.js-Deep-dive-document-and-Fonts</guid>
            <pubDate>Sat, 02 Apr 2022 12:40:22 GMT</pubDate>
            <description><![CDATA[<p>loading..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-qeury + TS :: useQuery()]]></title>
            <link>https://velog.io/@with-key/React-qeury-TS-useQuery</link>
            <guid>https://velog.io/@with-key/React-qeury-TS-useQuery</guid>
            <pubDate>Sun, 27 Mar 2022 15:26:58 GMT</pubDate>
            <description><![CDATA[<p>useQuery를 custom hook 으로 만들어 사용할 때 typing 하는 방법에 대해 알아본다. custom hook 쪽에서 typing이 되지 않으면, 사용하는 컴포넌트 쪽에서 받아온 데이터의 타입이 지정되지 않는다.</p>
<p><img src="https://images.velog.io/images/with-key/post/63504638-be47-4b6e-85bf-7fc49f858076/image.png" alt=""></p>
<pre><code class="language-ts">// useTodos
import axios, { AxiosResponse } from &quot;axios&quot;;
import { useQuery } from &quot;react-query&quot;;

// useQuery :: queryKey, queryFuntion, options
type TodosResponseType = { id: number; title: string; desc: string }[];

// qeuryfn
async function qeuryFn() {
  const { data }: AxiosResponse&lt;TodosResponseType&gt; = await axios.get(
    &quot;http://localhost:3001/todos&quot;
  );
  return data;
}

// axios에 받은 값도 타입을 지정해주기 위해, AxiosResponse을 잡아주고 제너릭으로
// data의 타입을 넣어준다.


export default function useTodos() {
  return useQuery&lt;TodosResponseType, Error&gt;([&quot;todos&quot;], qeuryFn, {});
}

// useQuery에 data에 대한 타입을 제너릭으로 넣어주면, UI쪽에서의 [] 안 항목에 대해서도
// 타입이 지정된다.</code></pre>
<p>타입이 지정된 코드 👇
<img src="https://images.velog.io/images/with-key/post/9a790c69-ecd6-4149-8ce2-fcc207e0d8ac/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[11.7 Favorite Products part two]]></title>
            <link>https://velog.io/@with-key/11.7-Favorite-Products-part-two</link>
            <guid>https://velog.io/@with-key/11.7-Favorite-Products-part-two</guid>
            <pubDate>Sun, 20 Mar 2022 16:12:36 GMT</pubDate>
            <description><![CDATA[<h2 id="좋아요-기능-구현하기-2">좋아요 기능 구현하기 2</h2>
<h3 id="1-optimistic-ui-구현">1. optimistic UI 구현</h3>
<p><strong>정의</strong>
optimistic UI란, 서버의 응답 없이 UI가 변경되는 것을 의미한다. 즉 유저가 좋아요 버튼을 눌렀을 때, 서버로부터 toggle에 대한 결과값을 응답 받기전에 유저의 의도대로 UI를 변경한다. 클라이언트의 요청이 높은 확률로 성공할 것임을 예측하고 유저의 액션을 수행한다. 장점으로는 유저 입장에서는 아주 빠른 응답성을 보여주고, 단점으로는 자칫 클라이언트의 요청이 실패했을 때 유저의 의도와 전혀 다르게 화면이 보여질 수 있다. 그래서 optimistic UI를 구현하고자 한다면, req 실패에 대한 대응도 세워야 한다. </p>
<pre><code class="language-ts">//post 요청이므로, useMutation()
const [toggleFav, { data, error, loading }] = useMutation(`/api/products/${router.query.id}/fav`);

const onFavClick = () =&gt; {
  toggleFav({});
}

return (
  //...

  &lt;button onClick={onFavClick}&gt;  {/* 좋아요 버튼 */}
)</code></pre>
<p><strong>현재 유저가 해당 게시물에 대하여 &#39;좋아요&#39;를 했는지 확인하기</strong>
Fav Record는 Product, User 정보를 모두 가지고 있다. 이것을 활용해서 현재 유저의 정보와 유저가 머무르고 있는 Product의 값을 모두 가지고 있는 Fav Record를 찾아낸다. 만약 두 조건을 모두 충족하는 Fav Record가 있다면, 해당 Product에 대해 User가 &#39;Like&#39;를 한 것이고, Record가 없다면 &#39;Unlike&#39; 한 것이다. db에서는 record가 없다면 null을 return 하고 있다면, { } 를 return 한다.</p>
<pre><code class="language-ts">// record를 조회하고, 그 여부를 boolean으로 변환해서, response에 담는다.
const isLiked = Boolean(
  await client.fav.findFirst({
  where: {
    userId: user?.id, // req.session에서 가져옴
    productId: product?.id, // req.query에서 가져옴 
  }, 
  select: {
    id: true, // fav record의 모든 정보를 거져올 필요는 없으므로.
  }
  })
);

res.json({
  ok: true, product, isLiked, relatedProducts  
})</code></pre>
<p><strong>프론트 작업</strong>
client에서는 fetching 한 data에서 isLike가 true, false일 때 각각 스타일을 넣어준다.</p>
<pre><code class="language-ts">const { data } = useSWR&lt;ProductDetailResponse&gt;(
    router.query.id ? `/api/products/${router.query.id}` : null
  );

// data === {ok: true, product: {…}, isLiked: true, relatedProducts: Array(0)}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[11.6 Favorite Products]]></title>
            <link>https://velog.io/@with-key/11.6-Favorite-Products</link>
            <guid>https://velog.io/@with-key/11.6-Favorite-Products</guid>
            <pubDate>Sun, 20 Mar 2022 15:14:09 GMT</pubDate>
            <description><![CDATA[<h2 id="좋아요-기능-구현하기">좋아요. 기능 구현하기</h2>
<h3 id="1-fav-model-추가">1. Fav model 추가</h3>
<p>favorite는 user에 의해서 생성되고, product을 가리킨다</p>
<pre><code class="language-ts">model Fav {
  id        Int      @id @default(autoincrement())
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId    Int
  product   Product  @relation(fields: [productId], references: [id], onDelete: Cascade)
  productId Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model User {
  ...
  fav Fav[]
}

model Product {
  ...
  favs Fav[]
}</code></pre>
<p>그리고, <code>npx prisma db push</code>, 프론트 서버 재실행을 후에 prisma studio를 확인한다.</p>
<h3 id="2-server-handler-구현">2. server handler 구현</h3>
<p>fav가 product에 존재한다면, 삭제하고 존재하지 않는다면 생성한다. 먼저 fav의 여부를 확인 할 product을 조회해야 한다. 그리고 그 product에서 fav가 존재하지는지 아닌지 확인한다.</p>
<pre><code class="language-ts">export default handler (){

  // 조회하고자 하는 product을 조회하는 쿼리문
  const alreadyExists = await client.fav.findFirtst({
    where: {
      productId, // req.query를 통해 확인
      userId, // req.session에서 확인
    }
  });

  if(alreadyExists){
    // delete
    await client.fav.delete({
      where: {
        id: alreadyExists.id,
      }
    })
  } else {
    // create fav
    await client.fav.crete({
      data: {
        userId: {
          user: {
            connect: {
              id: user?.id,
            }
          }
        },
        productId: {},
      }
    })
  }  
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>