<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hunter_joe99.log</title>
        <link>https://velog.io/</link>
        <description>Improvise, Adapt, Overcome</description>
        <lastBuildDate>Thu, 13 Aug 2026 14:20:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hunter_joe99.log</title>
            <url>https://velog.velcdn.com/images/hunter_joe99/profile/b00b749c-9006-4e25-b65f-c9499e3e392a/image.jfif</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hunter_joe99.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hunter_joe99" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[깃 잼따. ]]></title>
            <link>https://velog.io/@hunter_joe99/%EA%B9%83-%EC%9E%BC%EB%94%B0</link>
            <guid>https://velog.io/@hunter_joe99/%EA%B9%83-%EC%9E%BC%EB%94%B0</guid>
            <pubDate>Thu, 13 Aug 2026 14:20:23 GMT</pubDate>
            <description><![CDATA[<h1 id="체리-픽">체리 픽</h1>
<p><code>cherry-pick &lt;commit hash&gt;</code> </p>
<p>사용 경우: 현재 브랜치가 아닌 다른 브랜치에 있는 커밋 해시를 현재 워킹 트리에 Merge함
핵심은 해당 커밋해시를 Merge한다는 거임 </p>
<p>머지하지 않고 staged 상태로만 불러오고 싶다면? 
<code>-n</code> 옵션을 주어서 </p>
<p><code>cherry-pick -n &lt;commit hash&gt;</code> 갱장히 유용하니 까먹지 말쟈</p>
<p>만약에 <code>-n</code>을 까먹고 체리픽을 해버렸다? -&gt; <code>git cherry-pick --abort</code> 로 취소하자</p>
<p>혹은 <code>git reset --soft HEAD~1</code> or <code>--hard</code> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[비지터]]></title>
            <link>https://velog.io/@hunter_joe99/%EB%B9%84%EC%A7%80%ED%84%B0</link>
            <guid>https://velog.io/@hunter_joe99/%EB%B9%84%EC%A7%80%ED%84%B0</guid>
            <pubDate>Fri, 10 Apr 2026 11:55:15 GMT</pubDate>
            <description><![CDATA[<h2 id="정의">정의</h2>
<p>방문자 패턴은 알고리즘들을 그들이 작동하는 객체들로부터 분리할 수 있도록 하는 행동 디자인 패턴입니다.</p>
<p>알고리즘들을 그들이 작동하는 객체들로부터 분리한다라..</p>
<h2 id="문제">문제</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/321171a1-ccbe-4ac3-8125-24eee3ad894f/image.png" alt=""></p>
<ol>
<li>기존 클래스 변경 안돼</li>
<li>XML 내보기내기 메서드가 해당 클래스에 있는게 맞아? &lt;-SRP</li>
<li>다른 형식으로 내보내기 기능이 추가될꺼 같은데? &lt;- OCP</li>
</ol>
<h2 id="해결">해결</h2>
<p>새로운 메서드를 기존 클래스에 통합하는 대신에 <code>visitor</code>라는 별도의 클래스에 배치하자 </p>
<pre><code class="language-java">class ExportVisitor implements Visitor is
    method doForCity(City c) { ... }
    method doForIndustry(Industry f) { ... }
    method doForSightSeeing(SightSeeing ss) { ... }
    // …</code></pre>
<p>해당 메서드를 정확히 어떻게 호출할 수 있을까? 특히 전체 그래프(foreach)를 다룰 때,
이 메서드들은 시그니처들이 다르므로 다형성을 사용할 수 없습니다.</p>
<pre><code class="language-java">for Each {
    if (node instance of City) 
        exportVistor.doForCity((City) node)
    if (node instance of Industry)
        exportVisitor.doForIndustry((Industry) node)
}</code></pre>
<p>이 방법은 번거로운 조건문 없이 객체에 적절한 메서드를 실행하는 것을 목표로합니다.
객체들은 자신의 클래스들을 알고 있으므로 비지터가 올바른 메서드를 선택할 수 있도록 할 수 있습니다. </p>
<pre><code class="language-java">foreach (Node node in graph)
    node.accept(exportVisitor)

// City
class City is
    method accept(Visitor v) is
        v.doForCity(this)
    // …

// Industry
class Industry is
    method accept(Visitor v) is
        v.doForIndustry(this)
    // …</code></pre>
<blockquote>
<p>노드(Element) 안바꾼다면서 바꿨.... accept 추가했네..</p>
</blockquote>
<h2 id="다이어그램">다이어그램</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/b9264945-a3b8-4845-be29-3d5b89788b94/image.png" alt=""></p>
<h2 id="의사코드">의사코드</h2>
<pre><code class="language-java">// interface - element
interface Shape is 
    method move(x,y)
    method draw()
    method accept(v: Vistor)

// implement - concrete element
class Dot implements Shape is
    method accept(v: Visitor) is
        v.visitDot(this)

class Circle implements Shape is
    // …
    method accept(v: Visitor) is
        v.visitCircle(this)

class Rectangle implements Shape is
    // …
    method accept(v: Visitor) is
        v.visitRectangle(this)

class CompoundShape implements Shape is
    // …
    method accept(v: Visitor) is
        v.visitCompoundShape(this)

// interface - visitor
interface Visitor is 
    method visitDot(d: Dot)
    method visitCircle(c: Circle)
    method visitRectangle(r: Rectangle)
    method visitCompoundShape(cs: CompoundShape)

// implements - concrete visitor 
class XMLExportVisitor implements Visitor is
    method visitDot(d: Dot) is
        // 점의 아이디와 중심 좌표를 내보냅니다.

    method visitCircle(c: Circle) is
        // 원의 아이디, 중심 좌표 및 반지름을 내보냅니다.

    method visitRectangle(r: Rectangle) is
        // 사각형의 아이디, 왼쪽 상단 좌표, 너비 및 높이를 내보냅니다.

    method visitCompoundShape(cs: CompoundShape) is
        // 모양의 아이디와 그 자식들의 아이디 리스트를 내보냅니다.

// 클라이언트 코드 
class Application is
    field allShapes: array of Shapes[]

    method export() is
        exportVisitor = new XMLExportVisitor()

        foreach (shape in allShapes) do
            shape.accept(exportVisitor)

</code></pre>
<h2 id="더블-디스패치">더블 디스패치</h2>
<p><code>accept</code> 이 필요한 이유 더블 디스패치 아래서 한번 더 보자 </p>
<pre><code class="language-java">foreach (shape in allShapes) do
    shape.accept(exportVisitor)
</code></pre>
<p>allShapes에 Dot, Circle 등이 섞여 있지만 루프에서는 전부 Shape 타입으로 잡힘 
컴파일러는 구체적으로 뭔지 모름 </p>
<h3 id="1단계-디스패치--shape-타입-결정하기">1단계 디스패치 : Shape 타입 결정하기</h3>
<p><code>shape.accept(exportVisitor)</code>를 호출하면 런타임에 shape의 실제 타입에 따라 해당 클래스의 accept가 호출, 
예를 들어 실제로 Dot 객체라면</p>
<pre><code class="language-java">class Dot implements Shape is
    method accept(v: Visitor) is
        v.visitDot(this)  </code></pre>
<p>이 시점에서 Dot이 확정 이게 첫 번째 디스패치 </p>
<h3 id="2단계-디스패치-visitor-타입-결정">2단계 디스패치 :Visitor 타입 결정</h3>
<p><code>accept</code> 안에서<code>v.visitDot(this)</code>를 호출하면, <code>v</code>의 실제 타입에 따라 어떤 <code>visitDot</code>이 실행될지 결정
<code>v</code>가 <code>XMLExportVisitor</code>라면</p>
<pre><code class="language-java">class XMLExportVisitor implements Visitor is
    method visitDot(d: Dot) is
        // 점의 아이디와 중심 좌표를 내보냅니다.  </code></pre>
<p>한번의 호출로는 shape, visitor 두 축을 동시에 결정할 수 없어서 두번의 호출로 각각 다형성을 적용
그래서 더블 디스패치..</p>
<p>그러니깐 accept 쪽에서 자기 자신을 this로 넘겨줘야 
visitor에서 해당 구현된 shape의 데이터에 접근된다는건데 .. </p>
<h2 id="적용은">적용은</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/99e8dfe7-7966-43a0-979a-76f4a44535dd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SRP 원칙]]></title>
            <link>https://velog.io/@hunter_joe99/SRP-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@hunter_joe99/SRP-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Mon, 30 Mar 2026 05:01:18 GMT</pubDate>
            <description><![CDATA[<p>클래스나 모듈을 변경할 이유가 하나, 단 하나뿐이어야 한다는 원칙 </p>
<p>Class는 해당 클래스만이 가지고 있는 기능 또는 목적이 있을 것임 </p>
<p>쉽게 말하면 모든 기능을 하나의 클래스에 다 때려박지 말라는 말 </p>
<p>그럼 그 기준은 어떻게 정하냐고 물을 수 있다. &lt;&lt; 이건 개발자 영역 </p>
<blockquote>
<p>기준이 모호하기 때문에 <em>변경을</em> 책임의 기준으로 삼으면 설계에 용이할 수 있다.</p>
</blockquote>
<p>객체를 쪼갤 때 기능으로 쪼개든 목적으로 쪼개든 다 좋은데 그 근본 목표가 유지보수를 향해있어야한다. 
즉 유지보수성을 극대화해라! S/W의 특징(유연함)
<img src="https://velog.velcdn.com/images/hunter_joe99/post/f7682022-db46-4a00-aba1-3f425fa1513f/image.png" alt=""></p>
<p>결국 처음에 언급한 대로 기준은 변경 가능성이며 그 판단은 개발자의 몫
SRP는 &quot;무조건 기능 하나당 클래스 하나&quot;가 아니라 &quot;변경 이유가 섞이지 않게 해라&quot;는 원칙</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 에러 정리 ]]></title>
            <link>https://velog.io/@hunter_joe99/%EB%8F%84%EC%BB%A4-%EC%97%90%EB%9F%AC-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@hunter_joe99/%EB%8F%84%EC%BB%A4-%EC%97%90%EB%9F%AC-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 20 Mar 2026 16:35:51 GMT</pubDate>
            <description><![CDATA[<p>Docker 컨테이너에서 Telegram Bot API 호출 시 ETIMEDOUT 발생 원인과 해결                                     </p>
<p>  증상                                                                                                        </p>
<ul>
<li><p>NestJS 서버에서 에러 발생 시 Telegram Bot API로 알림을 보내는 기능                                        </p>
</li>
<li><p>간헐적으로 알림이 오지 않는 현상 발생                                                                   </p>
</li>
<li><p>서버 로그에는 에러가 정상적으로 찍히지만, Telegram 메시지는 도착하지 않음                                 </p>
<p>디버깅 과정</p>
<p>1단계: 코드 문제 확인                                                                                       </p>
<p>TelegramTransport에서 axios 에러를 .catch(() =&gt; {})로 무시하고 있어서, 실패해도 아무 로그가 남지 않았음.<br>catch에 로그를 추가하여 원인 파악 시작.                                                                   </p>
<p>// 기존 — 에러를 삼킴<br>axios.post(url, data).catch(() =&gt; {});                                                                      </p>
<p>// 디버깅용 — 에러 내용 출력<br>axios.post(url, data).catch((err) =&gt; {<br>console.error(&#39;[TelegramTransport]&#39;, err.code, err.message);<br>});                                                                                                         </p>
<p>결과: ETIMEDOUT 확인                                                                                        </p>
<p>2단계: 네트워크 문제 확인                                                                                   </p>
<h1 id="docker-컨테이너-내부에서-테스트">Docker 컨테이너 내부에서 테스트</h1>
<h1 id="wget-→-성공">wget → 성공</h1>
<p>docker exec server wget -q -O- <a href="https://api.telegram.org">https://api.telegram.org</a>                                                     </p>
<h1 id="nodejs-https-모듈-→-etimedout">Node.js https 모듈 → ETIMEDOUT</h1>
<p>docker exec server node -e &quot;require(&#39;https&#39;).get(&#39;<a href="https://api.telegram.org&#39;">https://api.telegram.org&#39;</a>, ...)&quot;                          </p>
<h1 id="google은-정상">Google은 정상</h1>
<p>docker exec server node -e &quot;require(&#39;https&#39;).get(&#39;<a href="https://google.com&#39;">https://google.com&#39;</a>, ...)&quot;                                </p>
<h1 id="→-status-301-성공">→ status: 301 (성공)</h1>
<p>wget은 되고 Node.js는 안 되는 상황.                                                                         </p>
<p>3단계: DNS 조회                                                                                             </p>
<p>docker exec server node -e &quot;<br>require(&#39;dns&#39;).resolve4(&#39;api.telegram.org&#39;, (e, a) =&gt; console.log(&#39;IPv4:&#39;, a));<br>require(&#39;dns&#39;).resolve6(&#39;api.telegram.org&#39;, (e, a) =&gt; console.log(&#39;IPv6:&#39;, a));<br>&quot;                                                                                                           </p>
<p>IPv4: [ &#39;149.154.166.110&#39; ]<br>IPv6: [ &#39;2001:67c:4e8:f004::9&#39; ]                                                                            </p>
<p>api.telegram.org는 IPv4, IPv6 둘 다 지원.                                                                   </p>
<p>4단계: IPv4 직접 연결 테스트                                                                                </p>
<p>IPv4 IP로 직접 연결 → 성공. IPv6 경로가 문제라는 것을 확인.                                                 </p>
<p>원인                                                                                                        </p>
</li>
<li><p>api.telegram.org는 IPv4/IPv6 듀얼 스택                                                                    </p>
</li>
<li><p>Node.js는 기본적으로 IPv6를 우선 사용 (happy eyeballs 알고리즘)</p>
</li>
<li><p>Docker 기본 네트워크(bridge)는 IPv6 외부 라우팅을 지원하지 않는 경우가 많음                               </p>
</li>
<li><p>IPv6로 연결 시도 → 라우팅 불가 → ETIMEDOUT                                                                </p>
</li>
<li><p>wget은 IPv4를 우선 사용하기 때문에 성공                                                                   </p>
</li>
<li><p>간헐적으로 알림이 온 이유: Node.js가 IPv4로 먼저 연결되는 경우도 있었기 때문                              </p>
<p>해결                                                                                                        </p>
<p>axios 인스턴스에 family: 4 옵션을 추가하여 IPv4 연결을 강제.                                                </p>
<p>// 변경 전<br>axios.post(this.apiUrl, { chat_id: this.chatId, text }).catch(() =&gt; {});                                  </p>
<p>// 변경 후
this.httpClient = axios.create({<br>timeout: 10000,<br>family: 4, // IPv4 강제<br>});<br>this.httpClient.post(this.apiUrl, { chat_id: this.chatId, text }).catch(() =&gt; {});                          </p>
<p>핵심 정리                                                                                                   </p>
<p>┌──────┬───────────────────────────────────────────────┐<br>│ 항목 │                     내용                      │<br>├──────┼───────────────────────────────────────────────┤<br>│ 환경 │ Docker (bridge network) + Node.js             │
├──────┼───────────────────────────────────────────────┤<br>│ 증상 │ 외부 API 호출 간헐적 ETIMEDOUT                │<br>├──────┼───────────────────────────────────────────────┤<br>│ 원인 │ Node.js IPv6 우선 + Docker IPv6 라우팅 미지원 │<br>├──────┼───────────────────────────────────────────────┤<br>│ 해결 │ family: 4로 IPv4 강제                         │<br>├──────┼───────────────────────────────────────────────┤<br>│ 교훈 │ .catch(() =&gt; {})로 에러를 삼키지 말 것        │<br>└──────┴───────────────────────────────────────────────┘  </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[사앙태 패터언 (상태패턴)]]></title>
            <link>https://velog.io/@hunter_joe99/%EC%82%AC%EC%95%99%ED%83%9C-%ED%8C%A8%ED%84%B0%EC%96%B8-%EC%83%81%ED%83%9C%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@hunter_joe99/%EC%82%AC%EC%95%99%ED%83%9C-%ED%8C%A8%ED%84%B0%EC%96%B8-%EC%83%81%ED%83%9C%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 19 Mar 2026 07:58:48 GMT</pubDate>
            <description><![CDATA[<h1 id="상태-패턴">상태 패턴</h1>
<p>객체의 내부 상태가 변경될 때 해당 객체가 ctx의 행동을 변경할 수 있도록 하는 패턴 
객체가 행동을 변경할 때 객체가 클래스를 변경한 것처럼 보일 수 있다. </p>
<ol>
<li>행동을 변경?? </li>
<li>클래스를 변경한 것처럼?? -&gt; 실제로는 변경이 일어나지 않았다는건가?<blockquote>
<p>궁금증은 아래서 </p>
</blockquote>
</li>
</ol>
<p>상태 패턴은 객체의 내부상태 변화에 따라 객체의 동작을 변경할 수 있는 패턴 
이 패턴은 유한 상태 기계의 개념과 유사 
상태 패턴은 패턴 인터페이스에 정의된 메서드 호출을 통해 전략을 전환할 수 있는 전략 패턴으로 해석 가능 </p>
<h1 id="fsm-유한-상태-기계">FSM 유한 상태 기계?</h1>
<p><strong>정해진 상태들 사이를 규칙에 따라 이동하는 기계(하드웨어)</strong>
신호등(초록, 노랑, 빨강), 자판기(돈 -&gt; 음료선택(유한)), 개찰구(open-close) 등 -&gt; </p>
<p><em>상태 패턴은 FSM이라는 이론적 개념을 소프트웨어에서 OOP로 구현한 패턴</em></p>
<h1 id="주요-개념">주요 개념</h1>
<ol>
<li>모든 주어진 순간에 프로그램이 속해 있을 수 있는 상태들의 수는 유한하다는 것. </li>
<li>현재 상태에 따라 프로그램은 특정 상태로 전환 or 전환되지 않을 수 있음 </li>
</ol>
<p>-&gt; 이러한 전환 규칙들을 천이(transition)이라고 하며 이 규칙들 또한 유한하며 결정적</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/f065fc8e-896e-4963-b2c1-549de9a3a44d/image.png" alt=""></p>
<h1 id="문제점-feat절차지향">문제점 feat.절차지향</h1>
<pre><code class="language-js">class Document is
    field state: string
    // …
    method publish() is
        switch (state)
            &quot;draft&quot;:
                state = &quot;moderation&quot;
                break
            &quot;moderation&quot;:
                if (currentUser.role == &quot;admin&quot;)
                    state = &quot;published&quot;
                break
            &quot;published&quot;:
                // Do nothing.
                break
    // …</code></pre>
<blockquote>
<p><strong><em>아아.. 이런 코드는 OOP에서 OCP원칙을 위반한다고~ 쓰지말라고!</em></strong> 
아래서 한번 절차지향적/객체지향적 장단점에 대해 얘기해봄 </p>
</blockquote>
<p>조건문들에 기반한 상태 머신의 가장 큰 약점은 상태들과 상태에 의존하는 행동들을 추가할수록 분명해지겠죠?</p>
<ul>
<li>코드베이스 이빠이 커짐..(설계 단계에서 모든 상태 + 천이를 예측하기란 어려워)</li>
<li>유지 보수 빡셈 </li>
</ul>
<h1 id="해결how">해결(HOW)</h1>
<ol>
<li>상태 패턴은 모든 가능한 상태들에 대해 새로운 클래스를 만든다.</li>
<li>모든 상태별 행동들을 클래스들로 추출한다.</li>
</ol>
<p>조금 더 구체적으로는? 
3. context(ctx)라는 원래 객체는 모든 행동을 구현하는 대신 <strong>&quot;현재 상태&quot;</strong>를 나타내는 상태 객체 중 하나에 대한 참조를 저장 + 모든 상태와 관련된 작업을 해당 클래스에 위임</p>
<p>ㅇㅎ 상태를 만들겠군 <code>State</code>합성 합성 영어로는 ? 콤포지숀(Composition)<del>~</del> 
그리고 <code>State</code>는 합성이니깐 고수준 모듈(interface)이겠고 이거슬 구현한 ConcreteState가 있겠군!</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/f68339bb-5e69-42f5-ba93-5b43e6b888c5/image.png" alt=""></p>
<h1 id="구체적인-해결-with-코드">구체적인 해결 With 코드</h1>
<h2 id="sudo">SUDO</h2>
<pre><code>Context = 리모컨 
State&lt;&lt;interface&gt;&gt; = 현재 TV 상태 인터페이스
ConcreteState = 실제 상태들 </code></pre><h3 id="ts">TS</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/8e8826d9-647f-4fcb-8cc5-dd4ecc7a2993/image.png" alt=""></p>
<p>일단은 간단하게 켜기/끄기만 구현</p>
<pre><code class="language-ts">interface TVState {
 pressButton(tv: TV): void;
}

// TV off 상태 
class TVOffState implements TVState {
    pressButton(tv: TV): void {
        console.log(&#39;티비가 켜짐!&#39;);
          tv.setState(new TVOnState());
    }
}

class TVOnState implements TVState {
    pressButton(tv: TV): void {
        console.log(&#39;티비가 꺼짐!&#39;);
          tv.setState(new TVOffState());
    }
}

class TV {
    private state: TVState = new TVOffState();

      setState(state: TVState) {
        this.state = state; 
    }

      public pressPower() {
        this.state.pressButton(this);
          // this를 호출한 객체.
    }
}

// -------CLIENT-------
const tv = new TV();

tv.pressPower(); // off -&gt; on 
tv.pressPower(); // on -&gt; off</code></pre>
<p>여기서 음량 조절 채널 버튼을 추가하면 어떻게 될까?
<em>*<em>상태패턴은 유한한 상태지만 많은 상태가 있어야 유의미한거잖아요! 
*</em></em></p>
<pre><code class="language-ts">interface TVState {
 pressButton(tv: TV): void;
 // 음량, 채널 
 pressChannel(direction: &#39;up&#39;| &#39;down&#39;): void
 pressVolume(direction: &#39;up&#39;|&#39;down&#39;): void
 // 그외 음소거 버튼 등등등 .. 
}</code></pre>
<h1 id="전략패턴-vs-상태패턴">전략패턴 vs 상태패턴</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/4516002d-2072-4360-868c-f9c9bfe0c51f/image.png" alt=""></p>
<p>전략 패턴과 비슷해 보이지만 한 가지 중요한 차이점이 있습니다. 상태 패턴에서의 특정 상태들은 서로를 인식하고, 한 상태에서 다른 상태로 천이(on -&gt; off)를 시작할 수 있지만 전략패턴들은 대부분 서로에 대해 알지 못한다는 것입니다.</p>
<p>전략 패턴은 사용자가 필요에따라 런타임에 바꿔!! 바꿔!! 바꿔!! 가능 (사용자가 갑)
상태 패턴은 사용자가 X &quot;응~ 니가 누르는거에 따라서 알아서 바뀔거야~&quot; (상황이 갑) </p>
<blockquote>
<p>솔직히 잘 모르겠음.. </p>
</blockquote>
<h1 id="궁금증-해결하기">궁금증 해결하기</h1>
<h2 id="1-행동을-변경">1. 행동을 변경??</h2>
<pre><code class="language-ts">const tv = new TV();  // TV 객체는 하나

tv.pressPower();  // &quot;티비가 켜짐!&quot; → ON 상태의 행동
tv.pressPower();  // &quot;티비가 꺼짐!&quot; → OFF 상태의 행동</code></pre>
<h2 id="2-클래스를-변경한-것처럼---실제로는-변경이-일어나지-않았다는건가">2. 클래스를 변경한 것처럼?? -&gt; 실제로는 변경이 일어나지 않았다는건가?</h2>
<p>상태 패턴을 사용하면 다음과 같은상태를 가진 클래스로 바꾼것처럼 행동한다는 뜻 
실제로는 변경없음, 상태만 변경됨</p>
<pre><code class="language-ts">tv.state = new TVOnState()   
tv.state = new TVOffState()  </code></pre>
<h1 id="절차지향-vs-객체지향">절차지향 vs 객체지향</h1>
<p>근데 꼭 절차지향이 나쁜건 아니거덩요. 
언어라는 도구를 잘 활용하면 좋거덩요 
꼭 oop에 갇혀 있을 필욘 없거덩요 
상황에 맞춰서 쓰면 되거덩요
그러하거덩요</p>
<h2 id="절차지향-c">절차지향 C</h2>
<pre><code class="language-c">#include &lt;stdio.h&gt;

// 상태 ENUM
typedef enum {
    TV_OFF,
    TV_ON
} TVState;

// 데이터 구조체 (행동 없음, 데이터만)
typedef struct {
    TVState state;
} TV;

// 함수가 구조체 밖에 존재
void press_power(TV* tv) {
    switch (tv-&gt;state) {
        case TV_OFF:
            printf(&quot;티비가 켜짐!\n&quot;);
            tv-&gt;state = TV_ON;
            break;
        case TV_ON:
            printf(&quot;티비가 꺼짐!\n&quot;);
            tv-&gt;state = TV_OFF;
            break;
    }
}

int main() {
    TV tv = { TV_OFF };  // 초기화
    press_power(&amp;tv);    // 포인터로 넘김
    press_power(&amp;tv);
    return 0;
}</code></pre>
<p>또한 상태가 많지 않다면 가독성 + 유지보수성은 절차지향이 편리함 (등가교환)</p>
<p>&quot;뭐가 더 자주 바뀌냐&quot; 에 따라 선택이 달라짐</p>
<p>기능(함수)이 자주 추가 -&gt; 절차지향
타입(클래스)이 자주 추가 -&gt; OOP</p>
<p>절차지향은 자료구조는 바꾸기 어렵지만 함수 추가는 EZPZ
OOP는 자료구조는 바꾸기 쉽지만 함수 추가는 HARD</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[axios/ forEach vs for of]]></title>
            <link>https://velog.io/@hunter_joe99/axios</link>
            <guid>https://velog.io/@hunter_joe99/axios</guid>
            <pubDate>Thu, 19 Mar 2026 04:04:21 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-ts">import { ENV } from &#39;@/configs/env&#39;;
import useAuthStore from &#39;@/store/use-auth-store&#39;;
import axios from &#39;axios&#39;;

type FailedQueue = Array&lt;{
  resolve: (token: string | null) =&gt; void;
  reject: (error: unknown) =&gt; void;
}&gt;;

export const apiCall = axios.create({
  baseURL: ENV.NEXT_PUBLIC_SERVER_URL,
  withCredentials: true,
});

apiCall.interceptors.request.use((config) =&gt; {
  const { accessToken } = useAuthStore.getState();
  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
});

let isRefreshing = false;
let failedQueue: FailedQueue = [];

const processQueue = (error: unknown, token: string | null = null) =&gt; {
  for (const { resolve, reject } of failedQueue) {
    if (error) {
      reject(error);
    } else {
      resolve(token);
    }
  }
  failedQueue = [];
};

apiCall.interceptors.response.use(
  (response) =&gt; response,
  async (error) =&gt; {
    const originalRequest = error.config;

    if (error.response?.status !== 401 || originalRequest._retry || originalRequest.url === &#39;/auth/refresh&#39;) {
      throw error;
    }

    if (isRefreshing) {
      return new Promise((resolve, reject) =&gt; {
        failedQueue.push({ resolve, reject });
      }).then((token) =&gt; {
        originalRequest.headers.Authorization = `Bearer ${token}`;
        return apiCall(originalRequest);
      });
    }

    originalRequest._retry = true;
    isRefreshing = true;

    try {
      const newToken = await useAuthStore.getState().refreshAccessToken();
      if (!newToken) {
        processQueue(new Error(&#39;Refresh failed&#39;));
        throw error;
      }

      processQueue(null, newToken);
      originalRequest.headers.Authorization = `Bearer ${newToken}`;
      return apiCall(originalRequest);
    } catch (refreshError) {
      processQueue(refreshError);
      throw refreshError;
    } finally {
      isRefreshing = false;
    }
  },
);
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ws]]></title>
            <link>https://velog.io/@hunter_joe99/ws</link>
            <guid>https://velog.io/@hunter_joe99/ws</guid>
            <pubDate>Wed, 18 Mar 2026 09:28:48 GMT</pubDate>
            <description><![CDATA[<p>⏺ allowRequest는 Socket.IO가 모든 연결 요청에 대해 가장 먼저 실행하는 검증 함수입니다.</p>
<p>  흐름                                                                                                                                       </p>
<p>  클라이언트가 WebSocket 연결 시도<br>    ↓<br>  allowRequest(req, cb) 실행<br>    ↓<br>  req.headers.origin 확인 (요청을 보낸 출처)<br>    ↓<br>  ├─ origin이 없음 → cb(null, true) → 연결 허용
  │   (Postman, curl, 서버 간 통신 등 브라우저가 아닌 경우)<br>  │<br>  ├─ origin이 corsOrigin 목록에 있음 → cb(null, true) → 연결 허용<br>  │   (예: &#39;<a href="http://localhost:3010&#39;%EC%9D%B4">http://localhost:3010&#39;이</a> 목록에 포함)<br>  │<br>  └─ origin이 목록에 없음 → cb(&#39;CORS_NOT_ALLOWED&#39;, false) → 연결 거부<br>      (예: &#39;<a href="http://evil.com&#39;%EC%97%90%EC%84%9C">http://evil.com&#39;에서</a> 접근 시도)                                                                                                  </p>
<p>  cb(err, success)의 의미                                                                                                                    </p>
<ul>
<li><p>cb(null, true) — 에러 없음, 연결 승인                                                                                                    </p>
</li>
<li><p>cb(&#39;CORS_NOT_ALLOWED&#39;, false) — 에러 메시지와 함께 연결 거부                                                                           </p>
<p>왜 필요한가                                                                                                                                </p>
<p>cors 옵션만으로는 HTTP의 preflight(OPTIONS) 요청에만 적용됩니다. WebSocket upgrade 요청은 preflight가 없어서 브라우저가 CORS를 강제하지<br>않습니다. 그래서 allowRequest로 서버 측에서 직접 Origin을 검사해야 합니다.                                                               </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ts, DTO ]]></title>
            <link>https://velog.io/@hunter_joe99/ts-DTO</link>
            <guid>https://velog.io/@hunter_joe99/ts-DTO</guid>
            <pubDate>Fri, 13 Mar 2026 19:58:44 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하면서 
기존 코드에 DTO가 적힌 부분을봤는데 의문점이 들어서 간단하게 정리용으로 작성함 후에 더 깊게 파야함 </p>
<h1 id="발단">발단</h1>
<pre><code class="language-ts">class UserDto {
  address: string; 
  signature: string;
}

@Post(&#39;login/verify&#39;)
async login(@Body() dto: UserDto, @Res() res:Response ): any {
      const result: { accessToken: string; refreshToken: string } = await this.authService.login(
      dto.address,
      dto.signature,
    );
}</code></pre>
<ul>
<li>사용 스택: nestjs</li>
</ul>
<p>일단 사건의 발단은 저 코드에서 UserDto가 실제로 컴파일타임, 런타임에 무슨 역할을 하는지 궁금했음 </p>
<p>내가 알고 있는 TS에대한 지식으로는 저렇게 클래스를 선언하면 변수를 할당하지 않는 이상 해당 클래스는 아무런 프로퍼티가 없는 상태다 
<code>class MetaMaskVerifyDto {}</code> &lt;- 컴파일 시점에는 빈 객체
이게 과연 런타임에 무슨 의미가 있을까, 
그리고 어떻게 <code>dto.[property]</code>에 접근할 수 있었을까?</p>
<p>조금 더 질문을 좁혀보자면 다음과 같다.</p>
<ol>
<li><p>interface/type과 비교해 class로 작성하는 이유?</p>
</li>
<li><p>컴파일시점과 런타임에는 어떻게 동작하는가?</p>
</li>
<li><p>dto를 js/ts에는 어떻게 다뤄야하는가? </p>
</li>
</ol>
<h1 id="1">1</h1>
<p>일단 ts 공식문서에서는 찾지 못했지만 내가 사용하는 프레임워크인 nestjs공식문서에서 다음과같은 글을 찾았따.
<a href="https://docs.nestjs.com/controllers">https://docs.nestjs.com/controllers</a>
Before we proceed (if you&#39;re using TypeScript), we need to define the DTO (Data Transfer Object) schema. A DTO is an object that specifies how data should be sent over the network. We could define the DTO schema using TypeScript interfaces or simple classes. <em><strong>However, we recommend using classes here. Why? Classes are part of the JavaScript ES6 standard, so they remain intact as real entities in the compiled JavaScript</strong></em>. In contrast, TypeScript interfaces are removed during transpilation, meaning Nest can&#39;t reference them at runtime. This is important because features like Pipes rely on having access to the metatype of variables at runtime, which is only possible with classes.</p>
<p>Let&#39;s create the CreateCatDto class:</p>
<pre><code>create-cat.dto.tsJS

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}</code></pre><p>DTO를정의할 때 interface/type을 상용하게 되면 트랜스파일링할때 지워져서 해당 entity에 접근할 수 없다. 
그렇기 때문에 class로 작성하는 것을 권장한다. </p>
<p>그리고후에 pipe를 활용할 만들어 놓은 dto 객체가 사용된다고 명시되어 있다.(like Pipes rely on having access to the metatype of variables at runtime, which is only possible with classes)</p>
<p>즉, interface/type을 사용해도 상관은 없지만 nest에서는 다른 feature들을 사용하기 위해서는 class로 dto를 정의한다고 보면된다.</p>
<h1 id="2">2</h1>
<p>1번에서 많은 의문점이 해소되었지만
컴파일시점 js로 변환됐을 때는 해당 class의 껍데기만 남게된다.</p>
<pre><code>class UserDto {}</code></pre><p>그리고 런타임 시점에서는 아무런 역할도 하지 않는다. 그저 객체가 body로 들어오면 아. 들어왔구나 통과~ 다음 로직에서 처리하게 된다.</p>
<p>즉 </p>
<pre><code>this.authService.login(
      dto.address,
      dto.signature,
    );</code></pre><p>이렇게 dto.address -&gt; json에 address property가 없다? 그냥 undifined로 할당 -&gt; 다음 로직 실행 </p>
<h1 id="3">3</h1>
<p>DATA TRANSFER OBJECTS</p>
<p>데이터를 전달하는 객체...</p>
<p>아마 zod를 몇번 더 써보면 정확하게 답이 나올거같아서 
현재는 더 길게는 안적을꺼같음</p>
<p>처음에 작성할 때 의문은 1,2,3 이렇게 3개였는데
1번에서 꽤 많은 의문점이 해소되었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[tcp ]]></title>
            <link>https://velog.io/@hunter_joe99/tcp</link>
            <guid>https://velog.io/@hunter_joe99/tcp</guid>
            <pubDate>Thu, 05 Mar 2026 08:03:17 GMT</pubDate>
            <description><![CDATA[<p>Transmission control protocol
데이터를 목적지까지 운송하는 역할 </p>
<p>http는 데이터를 달라고 요청만 함 그 중간에 데이터가 유실되거나 순서가 섞였는지는 확인하지 않음 이러한 복잡하고 중요한 일은 tcp가 해결함 </p>
<ul>
<li>데이터 분할 : 큰 데이터를 작은 단위의 페킷 형태로 전송 </li>
<li>순서 보장 : 페킷들을 도착지에서 순서가 바뀌어도 다시 올바른 순서로 조립 </li>
<li>재전송 : 가는 길에 데이터가 유실되면 다시 보내줘라고 요청해서 누락 없이 전달. </li>
</ul>
<p>그럼 과연 재전송하면 새로 오나? 아니면 빠진 부분만 가져오나?</p>
<p>TCP의 동작 방식 3-way-handshake
TCP는 데이터를 보내기전에 상대방과 연결이 가능한 상태인지 먼저 확인하는 절차를 거침 
이를 3-way-handshake라고 함 </p>
<ol>
<li>클라이언트 : 나 데이터 보내도 돼?</li>
<li>서버 : 나도 보내도 돼?</li>
<li>클라이언트 : 응 그럼 보낼께~ </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[면접 후기 ]]></title>
            <link>https://velog.io/@hunter_joe99/%EB%A9%B4%EC%A0%91-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@hunter_joe99/%EB%A9%B4%EC%A0%91-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 05 Mar 2026 04:46:45 GMT</pubDate>
            <description><![CDATA[<p>이번 면접 후기 
생각보다 기초지식이 부족하다는 것을 느낌 </p>
<ul>
<li><p>기술스택에만 집중했던것을 후회함 
오히려 그 스택중 기본이 되는 것을 더욱 파고 드는게 중요 </p>
</li>
<li><p>왜? 라는 의문점을 항시 가져야 함 
그냥 좋아서 썼어요 &lt;- 실제로 이런 경우는 잘 없겠지만 
그 부분이 왜 좋았는지 기존의 문제는 무엇이였는지, 다른 것들과 비교해볼 때 어떤게 좋았는지, 새로운 문제를 발생시키지 않을지 
등 다양한 요소들에 대해서도 생각해보는게 좋음 </p>
</li>
<li><p>기초 결국 FE 개발자는 브라우저 , 네트워크, 이런것에 기초가 단단하고 사용하는 Js 코어, React 코어 
그리고 그 배경까지 빠삭하게 이해하는게 중요함 </p>
</li>
<li><p>개발자라면 논리적인 추론 흐름을 꼭 연습하는게 중요함</p>
</li>
<li><p>전체 프로젝트를 진행하는 것도 중요하지만, 모듈식으로 부분 부분 내가 필요한 부분을 만들어서 공부하는 것도 중요한 방식임 </p>
</li>
</ul>
<p>일단은 여기까지 작성하고 후에 이런 부분들을 고려해서 글을 작성할 때 참고해야겠음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트리쉐이킹]]></title>
            <link>https://velog.io/@hunter_joe99/%ED%8A%B8%EB%A6%AC%EC%89%90%EC%9D%B4%ED%82%B9</link>
            <guid>https://velog.io/@hunter_joe99/%ED%8A%B8%EB%A6%AC%EC%89%90%EC%9D%B4%ED%82%B9</guid>
            <pubDate>Sat, 21 Feb 2026 18:55:53 GMT</pubDate>
            <description><![CDATA[<p>전 
<img src="https://velog.velcdn.com/images/hunter_joe99/post/700b8033-cc29-4596-9df2-ccf8d497662d/image.png" alt="">
후 
<img src="https://velog.velcdn.com/images/hunter_joe99/post/0704584d-8c60-4c4a-a921-ae6ada548b3a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] - CI/CD 자동화, (웹 서버 기준)]]></title>
            <link>https://velog.io/@hunter_joe99/AWS-CICD-%EC%9E%90%EB%8F%99%ED%99%94-%EC%9B%B9-%EC%84%9C%EB%B2%84-%EA%B8%B0%EC%A4%80</link>
            <guid>https://velog.io/@hunter_joe99/AWS-CICD-%EC%9E%90%EB%8F%99%ED%99%94-%EC%9B%B9-%EC%84%9C%EB%B2%84-%EA%B8%B0%EC%A4%80</guid>
            <pubDate>Mon, 19 Jan 2026 18:59:35 GMT</pubDate>
            <description><![CDATA[<p>글이 중구난방일거고 읽으시는 분들한테도 그닥 도움 안될 수 있지만 
이 글의 전반적인 흐름은 다음과 같습니다. </p>
<ol>
<li>Docker Hub 가입 및 설정 + Docker Hub 필요 이유</li>
<li>git actions 작성 </li>
<li>git runners(셀프 호스팅) 설정</li>
<li>nginx + Let&#39;s Encrypt (SSL 인증서)</li>
</ol>
<h1 id="docker-hub">Docker Hub</h1>
<p>완전 자동화 CI/CD 구축을 위해서는 Docker Hub의 repository를 이용하면 개굿임 진짜 편함! </p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/651fd0ef-fee1-471b-85dd-d4c91b753581/image.png" alt="">
사진처럼 도커 이미지 생성하는 법을 알아보자 </p>
<ol>
<li>ID + 비밀번호 or Access Token 필요 
Access Token의 권한은 Read &amp; Write 정도는 필수 </li>
</ol>
<h2 id="docker-hub-무료-플랜-참고용-그닥-쓸모-x">Docker Hub 무료 플랜 (참고용 그닥 쓸모 X)</h2>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/991d3d56-3eb4-416f-9132-1aaf673a684c/image.png" alt=""></p>
<blockquote>
<p>여담인데 배포해보니깐 그냥 대머리 될거같음 공짜 공짜 공짜.. ㄹㅇ 공짜만 찾아 댕기는 듯 ㅠ....</p>
</blockquote>
<h2 id="다시-본론-">다시 본론 ...</h2>
<p>무튼 Docker ID, PW or Access Token 이 필요한 이유는 </p>
<p>이따가 GitHub Actions를 이용한 CI/CD를 구축하기 위해 보안 정보가 필요 함 ( 아래 사진 참고 )</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/a28a2ee2-8930-4722-9440-42b596e6a994/image.png" alt=""></p>
<h2 id="docker-hub-회원가입-했지">Docker Hub 회원가입 했지?</h2>
<p>만약 보안을 위해 Private으로 유지하고 싶다면 EC2 터미널에 접속해서 아래 명령어를 딱 한 번만 실행 ㄱㄱ </p>
<pre><code># EC2 터미널에서 입력
docker login -u [도커_허브_아이디]
# Password 입력창이 뜨면 &#39;비밀번호&#39;가 아닌 발급받은 &#39;Access Token&#39;을 입력하세요.</code></pre><p>이렇게 한 번 로그인해두면 그 이후부터는 GitHub Actions가 도커 이미지를 올리고 EC2가 내려받는 과정이 막힘없이 진행됨 </p>
<h1 id="git-action">git action</h1>
<p>&quot;배포 스크립트&quot;,&quot;CI/CD 파이프라인&quot;, &quot;깃헙 Actions 설정&quot; 이 중에 뭐라고 부르는지는 잘 모르겠지만 
일단 이 코드는 잘 자기 프로젝트에 맞게 작성할 것</p>
<pre><code class="language-yml"># .github/workflows/deploy.yml

name: My server CI/CD

on:
  push:
    branches: [ main ] # 메인 브랜치에 푸시될 때 실행 </code></pre>
<blockquote>
<p>현재 <code>branches: [main]</code>으로 되어 있는데 
이건 CI/CD를 구축하기에 안전한건 아님 
왜냐하면 git Ubuntu가 내 SSH에 들어가서 직접적으로 수정하는거라서  SSH 22번 포트를 내 ip 외에도 열어줘야함 </p>
<blockquote>
<p>난 일단 초짜라 요금 폭탄 맞기 싫어서 누구한테도 안열어 줄겅미</p>
</blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/8b8d64ba-3d42-46e1-bf15-a70aee49f44b/image.png" alt=""></p>
<h1 id="git-self-hosted-runner">Git Self-Hosted Runner</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/6fe82198-a572-4d77-99c5-425fb4f497b7/image.png" alt=""></p>
<p>5분은 무슨 한 30분 걸린듯. ㅠ </p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/0948571f-eb3e-4eff-98d1-7683ba1e9afe/image.png" alt=""></p>
<p>이대로 실행하면 되긴하는데 약간의 디테일이 있음 
Download부분은 저 대로 EC2 터미널에 하나씩 입력하면되고 </p>
<p>Configure에 
<code>./config.sh --url https://~~~</code>입력한 뒤에 
./run.sh 대신에 아래 명령어 하면 좋음 </p>
<pre><code># 서비스를 설치합니다. (관리자 권한 필요)
sudo ./svc.sh install

# 서비스를 시작합니다.
sudo ./svc.sh start

# 서비스 상태를 확인합니다.
sudo ./svc.sh status</code></pre><p>이렇게하면 다음과 같다고 함 
<img src="https://velog.velcdn.com/images/hunter_joe99/post/d2587441-7633-45be-bc69-a813fd21a218/image.png" alt=""></p>
<h2 id="dependency-에러-생길경우">Dependency 에러 생길경우</h2>
<pre><code># Amazon Linux 2023 기준 수동 설치
sudo dnf install libicu -y</code></pre><h1 id="nginx--lets-encrypt">nginx + Let&#39;s Encrypt</h1>
<p><code>docker-compose.prod.yml</code>에 nginx에 대한 필요한 코드 작성해주고~ 
<code>nginx.conf</code>도 작성해주고~ </p>
<h2 id="내-폴더-구조는-이래용-nestjs">내 폴더 구조는 이래용 (NestJS)</h2>
<p>나는 nginx + certbot 폴더 생성해줬음 </p>
<pre><code>server
├── src
├── nginx/
│   └── default.conf
└── certbot/
    ├── conf/
    │   └── .gitkeep  
    └── www/
        └── .gitkeep  </code></pre><p>폴더구조는 자기 환경에 맞게끔 잘 조절해주면 GOOD!</p>
<pre><code class="language-shell"># 1. 먼저 전체 서비스를 실행합니다. (이때 Nginx는 인증서가 없어 에러가 날 수 있지만 일단 띄웁니다)
docker compose -f docker-compose.prod.yml up -d

# 2. Certbot을 이용해 인증서를 발급받습니다.
docker compose -f docker-compose.prod.yml run --rm certbot certonly --webroot --webroot-path /var/www/certbot -d &lt;url&gt;</code></pre>
<p>발급 성공하믄 
근데 아마 여기서 발급받을 때 소스코드 부분 몇번 변경을 할거임</p>
<ul>
<li>포트 열어주고 -&gt; 다시 닫아주고 이 과정 한번 거쳐야함</li>
</ul>
<p>SSL 인증서까지 받았따면!? <code>docker compose restart nginx</code> ㄱㄱ 리스타트 </p>
<h3 id="참고로-인증서는-다음과-같음">참고로 인증서는 다음과 같음</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/d84765e1-d84a-4eca-99a4-d333643eaf14/image.png" alt=""></p>
<p>여기까지 했다면 배포 굿 잘 된것임!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] - Elastic IP 설정]]></title>
            <link>https://velog.io/@hunter_joe99/AWS-Elastic-IP-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hunter_joe99/AWS-Elastic-IP-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 19 Jan 2026 18:03:29 GMT</pubDate>
            <description><![CDATA[<h1 id="elastic-ip">Elastic IP</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/4fe47913-cc41-44a8-b299-3a51a8d3bbc8/image.png" alt="">
AWS EC2의 <strong>&#39;퍼블릭 IPv4 주소&#39;</strong>는 인스턴스를 중지(Stop)했다가 다시 시작(Start)할 때마다 변경될 가능성이 있음 </p>
<ul>
<li><p>문제점: IP가 바뀌면 Route 53에 등록한 설정값도 매번 수동으로 수정해야 함 + 그동안 서비스 접속이 끊김</p>
</li>
<li><p>해결책: AWS 콘솔의 &#39;탄력적 IP&#39; 메뉴에서 고정 IP를 할당받아 인스턴스에 연결한 후, 그 고정된 IP 주소를 Route 53 값에 입력 권장</p>
</li>
</ul>
<blockquote>
<p>Elastic IP는 결국 돈나감 ㅠ </p>
</blockquote>
<blockquote>
<p>편의상 Route 53으로 설명했는데 필자는 Route 53 대신 
가비아(국내) DNS 관리 툴에서 설정 했음</p>
</blockquote>
<ul>
<li>Route53은 무료 플랜에서 사용 불가 항목 <img src="https://velog.velcdn.com/images/hunter_joe99/post/7c5955b1-0bce-4bb0-915a-9b09e1bcccfe/image.png" alt=""></li>
</ul>
<table>
<thead>
<tr>
<th align="center">타입</th>
<th align="center">호스트</th>
<th align="center">IP주소</th>
<th align="center">TTL</th>
</tr>
</thead>
<tbody><tr>
<td align="center">A</td>
<td align="center">api</td>
<td align="center">x.xx.xx.xxx</td>
<td align="center">300or 3600</td>
</tr>
</tbody></table>
<p>호스트(api) : <code>api.your-domain.com</code> &lt;- 에서 앞 <code>api.</code> 부분 설정임 </p>
<p>암튼 핵심은 도메인 연결은 기본 퍼블릭 IP는 바뀔 수 있으니 고정 IPdㅣㄴ Elastic IP(탄력적 IP)를 받아서 설정 해놓으면 수고스러움을 덜 수 있다.</p>
<p>도메인 연결은 검색하면 자료가 많아서 굳이 더 다루지 않겠음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] - EC2 설정 + RDS 연결]]></title>
            <link>https://velog.io/@hunter_joe99/AWS-EC2-%EC%84%A4%EC%A0%95-RDS-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@hunter_joe99/AWS-EC2-%EC%84%A4%EC%A0%95-RDS-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Sun, 18 Jan 2026 19:24:36 GMT</pubDate>
            <description><![CDATA[<p>혹시라도 이거 따라하시는 분 막히시면 알려주세요 
<a href="mailto:hunterjoe9999@gmail.com">hunterjoe9999@gmail.com</a> or 댓글 환영입니다</p>
<h1 id="1-ec2-인스턴스-생성">1. EC2 인스턴스 생성</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/75c1f931-b61a-45b5-8c34-2532a25ff0fc/image.png" alt=""></p>
<ul>
<li>amzaon linux - 2023 사용 </li>
<li>t3.micro</li>
</ul>
<p>무료 플랜은 이거 사용하면 됨 
우분투는 시간당 0.016이라 패스 (극강의 효율 가즈아) </p>
<ul>
<li>키를 생성하면 <code>&lt;key pair&gt;.pem</code>이런걸 다운로드 하는데 
AWS 키페어는 EC2 인스턴스에 처음 접속하기 위한 마스터 키</li>
<li><blockquote>
<p>나중에 git action에 ssh 연결할 떄 쓰임</p>
</blockquote>
</li>
</ul>
<h1 id="2-접속">2. 접속</h1>
<pre><code class="language-shell"># .pem 파일 드래그 앤 드랍해서 .pem 파일 위치 넣기
chmod 400 &lt;keypair-경로.pem&gt;

# 퍼블릭 DNS을(를) 사용하여 인스턴스에 연결
ssh -i &lt;keypair-경로.pem&gt; ec2-user@내-퍼블릭-IP주소&gt;</code></pre>
<blockquote>
<p>*<em>chmod 400 *</em>
chmod 400은 파일 소유자에게만 읽기(read) 권한을 부여하고, 그룹 및 다른 사용자에게는 어떠한 권한도 주지 않는(권한 없음) 설정</p>
</blockquote>
<h1 id="ec2-linux-shell">ec2-linux-shell</h1>
<p>일단 여기까지 왔으면 아래 사진처럼 접속이 됨
<img src="https://velog.velcdn.com/images/hunter_joe99/post/7fc1748c-b1af-439c-baa7-5e94a15a5286/image.png" alt=""></p>
<p>docker, docker-compose, git이 필요함 
설정하는건 다음과 같음 </p>
<pre><code class="language-shell">
sudo dnf update -y # 보안 패치 및 버그 수정의 이유로 패키지 업데이트 
sudo dnf install -y docker # Docker 엔진을 설치
sudo systemctl start docker # Docker 서비스 시작 
sudo systemctl enable docker # EC2 재부팅해도 Docker가 자동으로 시작되게 설정
sudo usermod -aG docker ec2-user # ec2-user를 docker 그룹에 추가
sudo dnf install -y docker-compose-plugin #  Docker Compose 플러그인 설치
docker compose version # docker-compose 버전 확인
sudo dnf install -y git # 깃 설치
git --version # 깃 버전 확인 </code></pre>
<blockquote>
<p><code>sudo usermod -aG docker ec2-user</code>
역할: ec2-user를 docker 그룹에 추가
왜 필요? Docker 명령어는 기본적으로 root 권한 필요
효과: sudo 없이도 docker 명령어 사용 가능</p>
</blockquote>
<pre><code class="language-shell"># 이 명령 전
sudo docker ps  # sudo 필요
&gt;
# 이 명령 후 (재로그인 필요)
docker ps </code></pre>
<p>⚠️ 주의: 적용하려면 로그아웃(<code>exit</code>) 후 재로그인 필요w
재로그인: <code>ssh -i &lt;keypair.pem&gt; ec-user@퍼블릭 ip주소</code></p>
<pre><code>
# git 설정 (레포지토리가 private 일때만)
```shell
# ec2 터미널에서 
ssh-keygen -t ed25519 -C &quot;내이메일@example.com&quot;</code></pre><p>엔터 3번 -&gt; <code>cat ~/.ssh/id_ed25519.pub</code>(공개 키 확인 및 복사)</p>
<pre><code class="language-shell"># 공개키는 다음과같이 생김 
`ssh-ed25519 AAAA... 내이메일@example.com`</code></pre>
<h4 id="github-저장소에-키-등록">GitHub 저장소에 키 등록</h4>
<ol>
<li><p>본인의 GitHub Private 저장소 페이지로 이동</p>
</li>
<li><p>Settings 탭 클릭 → 왼쪽 메뉴에서 Deploy keys</p>
</li>
<li><p>Add deploy key 버튼을 클릭</p>
</li>
<li><p><code>Title: EC2-Server</code> (본인이 알아보기 쉬운 이름)</p>
</li>
<li><p>Key: 방금 복사한 문자열을 붙여넣기</p>
</li>
<li><p>Add key를 눌러 저장 (Write 권한은 필요 없으므로 체크하지 않아도 됨)</p>
</li>
</ol>
<p>이렇게 설정하면 SSH로 이제 <code>git clone</code> 가능</p>
<h1 id="git-clone-레포-복사">git clone (레포 복사)</h1>
<h4 id="1-ssh-연결-테스트-가장-먼저-확인">1. SSH 연결 테스트 (가장 먼저 확인!)</h4>
<pre><code>ssh -T git@github.com</code></pre><ul>
<li>Hi 계정명/저장소명! You&#39;ve successfully authenticated... 라고 나오면 성공</li>
</ul>
<p>ec2의 루트 경로 <code>[ec-user@...]</code>에서 </p>
<ul>
<li>현재 위치한 경로 알고 싶으면 : <code>pwd</code></li>
</ul>
<pre><code class="language-shell"># SSH 주소!!!
git clone git clone git@github.com:계정명/저장소명.git</code></pre>
<h1 id="env-설정">.env 설정</h1>
<pre><code class="language-shell"># 1. env 파일 만듦 
vi .env

# 2. `i` 를 눌러 -- INSERT -- 모드로 바꾼 뒤
.env파일 복붙 

# 파일 저장 &amp; 나가기
Esc #키를 누름 (입력 모드 해제).
:wq # 입력 (Write and Quit).
Enter # 터미널로 돌아옵니다.</code></pre>
<h1 id="compose-build-requires-buildx-017-or-later">compose build requires buildx 0.17 or later</h1>
<p>이런 경고가 뜨면 
버전 업글해주면됨</p>
<pre><code class="language-shell">mkdir -p ~/.docker/cli-plugins

curl -L https://github.com/docker/buildx/releases/download/v0.19.1/buildx-v0.19.1.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx

chmod +x ~/.docker/cli-plugins/docker-buildx

docker buildx version</code></pre>
<h1 id="rds-연결까지">RDS 연결까지</h1>
<pre><code># nc(netcat) 명령어로 해당 IP와 포트 통신 확인
sudo dnf install -y nc # 설치
nc -zv &lt;프라이빗IP&gt; &lt;dbport&gt; # 확인</code></pre><p><img src="https://velog.velcdn.com/images/hunter_joe99/post/e776ab09-d47f-40e4-8792-1aff4ec68c9d/image.png" alt="">
데이터베이스(RDS)는 일종의 &#39;성&#39;이고, 보안 그룹은 그 성문을 지키는 &#39;수문장&#39;입니다. 현재 EC2가 성 안으로 들어오려고 하지만, RDS의 수문장(default)이 막고 있는 상태입니다. 따라서 default 보안 그룹의 인바운드 규칙을 수정하여 EC2(launch-wizard-3)를 통과시켜줘야 합니다.</p>
<blockquote>
<p>나는 다음과같음 
default가 rds 설정할 떄 썼던거고
launch-wizard-3가 내 ec2 인스턴스에 추가한거 </p>
</blockquote>
<h4 id="보안-그룹-규칙-추가-순서">보안 그룹 규칙 추가 순서</h4>
<ol>
<li><p><code>default</code></p>
</li>
<li><p>인바운드 규칙 편집: 하단 탭에서 <strong>인바운드 규칙(Inbound rules)</strong>을 선택하고, 오른쪽의 인바운드 규칙 편집(Edit inbound rules) 버튼을 누릅니다.</p>
</li>
<li><p>규칙 추가: 아래와 같이 새로운 규칙을 입력합니다.</p>
</li>
</ol>
<ul>
<li><p>유형: PostgreSQL (선택하면 자동으로 포트 범위가 5432로 설정됩니다.)</p>
</li>
<li><p>프로토콜: TCP (자동 설정)</p>
</li>
<li><p>포트 범위: 5432 (자동 설정)</p>
</li>
</ul>
<ol start="4">
<li><p>소스(Source): &#39;사용자 지정&#39;을 선택한 뒤, 옆의 입력창에 launch-wizard-3의 보안 그룹 ID를 입력합니다. (입력창을 클릭하면 목록에 sg-... (launch-wizard-3)가 나타날 텐데, 그걸 선택하는 것이 가장 정확하고 안전합니다.)</p>
</li>
<li><p>저장: 하단의 규칙 저장(Save rules) 버튼을 누릅니다.</p>
</li>
</ol>
<p>자 이제 다시 네트워크 확인해보자 </p>
<pre><code class="language-shell">nc -zv &lt;프라이빗IP&gt; &lt;dbport&gt; # 확인
# &gt;connected 뜨면 정상</code></pre>
<h1 id="aws-ec2-보안-그룹-확인">AWS EC2 보안 그룹 확인</h1>
<p>아까 확인하신 launch-wizard-3 보안 그룹은 현재 EC2 인스턴스의 &#39;성벽&#39; 역할을 합니다. 여기에 3000번 포트를 열어주는 규칙이 있는지 확인해야 합니다.</p>
<ol>
<li><p>AWS 콘솔 → EC2 인스턴스 목록에서 현재 실행 중인 인스턴스를 클릭합니다.</p>
</li>
<li><p>하단의 보안(Security) 탭을 누르고, 보안 그룹(Security groups) 아래에 있는 launch-wizard-3 링크를 클릭합니다.</p>
</li>
<li><p>인바운드 규칙(Inbound rules) 탭에서 <strong>인바운드 규칙 편집(Edit inbound rules)</strong>을 누릅니다.</p>
</li>
<li><p>아래 규칙을 추가하고 저장하세요.</p>
</li>
</ol>
<ul>
<li>유형: 사용자 지정 TCP</li>
<li>포트 범위: 3000</li>
<li>소스: Anywhere-IPv4 (또는 주소창에 0.0.0.0/0 입력)</li>
</ul>
<ol start="5">
<li>저장 </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] - RDS postgreSQL 설정]]></title>
            <link>https://velog.io/@hunter_joe99/AWS-RDS</link>
            <guid>https://velog.io/@hunter_joe99/AWS-RDS</guid>
            <pubDate>Sat, 17 Jan 2026 09:03:07 GMT</pubDate>
            <description><![CDATA[<p>*<em>AWS RDS DB *</em>만들고 난 뒤에 local개발 환경과 동기화 시키는 방법 작성</p>
<h1 id="1퍼블릭-액세스-설정-변경하기">1.퍼블릭 액세스 설정 변경하기</h1>
<p>RDS 콘솔로 가서 해당 → 데이터베이스를 클릭합니다.</p>
<p>상단의 → [수정(Modify)] 버튼을 누릅니다.</p>
<p>화면을 아래로 쭉 내려서 [연결(Connectivity)] 섹션을 찾습니다.</p>
<p><strong>[추가 구성(Additional configuration)]</strong>을 클릭하여 메뉴를 펼칩니다.</p>
<p>퍼블릭 액세스(Public access) 항목을 <strong>[예(Yes)]</strong>로 변경합니다.</p>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/caf55e49-51c2-433d-ab0c-06c541da5397/image.png" alt="">
맨 아래로 내려가 <strong>[계속(Continue)]</strong>을 누릅니다.</p>
<p>수정 예약에서 반드시 <strong>[즉시 적용(Apply immediately)]</strong>을 선택하고 <strong>[DB 인스턴스 수정]</strong>을 누르세요.</p>
<p>주의: &#39;다음 예약된 유지 관리 기간에 적용&#39;을 선택하면 당장 바뀌지 않습니다.</p>
<hr>
<h1 id="2보안-그룹security-group에서-내-ip-허용하기">2.보안 그룹(Security Group)에서 내 IP 허용하기</h1>
<p>퍼블릭 액세스를 켰더라도, 보안을 위해 &quot;특정 IP(사용자님의 컴퓨터)만 접속을 허용하겠다&quot;는 설정을 반드시 해줘야 합니다.</p>
<p>RDS 콘솔의 DB 인스턴스 상세 페이지에서 [연결 및 보안] 탭을 클릭합니다.</p>
<p>오른쪽의 [VPC 보안 그룹] 아래에 있는 파란색 링크(예: sg-0abc...)를 클릭합니다.</p>
<p>화면 하단의 [인바운드 규칙] 탭을 선택하고 [인바운드 규칙 편집] 버튼을 누릅니다.</p>
<p><strong>[규칙 추가]</strong>를 누르고 다음과 같이 설정합니다.</p>
<p>유형: PostgreSQL (자동으로 포트 5432가 입력됩니다.)</p>
<p>소스: <strong>&#39;내 IP&#39;</strong>를 선택합니다. (현재 사용자님의 인터넷 IP 주소가 자동으로 들어갑니다.)</p>
<p>설명(선택 사항): My PC Access라고 적어두면 나중에 알아보기 편합니다.</p>
<p><strong>[규칙 저장]</strong>을 누릅니다</p>
<hr>
<h1 id="3-접속-주소엔드포인트-복사하기">3. 접속 주소(엔드포인트) 복사하기</h1>
<p>실제 DB 주소를 알아야 도구에서 접속할 수 있습니다.</p>
<p>다시 RDS 상세 페이지의 [연결 및 보안] 탭으로 가서 엔드포인트(Endpoint) 주소를 복사해 두세요.</p>
<p>예: database-1.xxxx.ap-northeast-2.rds.amazonaws.com</p>
<p>3단계: DB 관리 도구(DBeaver)로 접속 테스트
이제 사용자님의 컴퓨터에 설치된 DB 클라이언트 도구(DBeaver 등)를 켭니다.</p>
<p><strong>새 연결(New Connection)</strong>을 만들고 PostgreSQL을 선택합니다.</p>
<p>Host: 복사한 &#39;엔드포인트&#39; 주소를 붙여넣습니다.</p>
<p>Port: 5432를 확인합니다.</p>
<p>Database: 기본값인 postgres를 입력합니다. (별도로 생성한 DB가 없다면 기본 DB명입니다.)</p>
<p>Username: 설정하셨던 마스터 이름(예: postgres)을 입력합니다.</p>
<p>Password: 설정하신 마스터 암호를 입력합니다.</p>
<p>[Test Connection] 버튼을 눌러 연결되는지 확인합니다.</p>
<hr>
<h1 id="4-cli">4. CLI</h1>
<pre><code>psql -h &lt;엔드포인트&gt; -p 5432 -U &lt;마스터사용자이름&gt; -d postgres</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[websocket data 테스팅]]></title>
            <link>https://velog.io/@hunter_joe99/websocket-data-%ED%85%8C%EC%8A%A4%ED%8C%85</link>
            <guid>https://velog.io/@hunter_joe99/websocket-data-%ED%85%8C%EC%8A%A4%ED%8C%85</guid>
            <pubDate>Mon, 12 Jan 2026 18:35:20 GMT</pubDate>
            <description><![CDATA[<h3 id="og">OG</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/41965663-5896-4809-92f4-85a507107321/image.png" alt="">
<code>ls -lh docs/sample/original-comparison.json</code> -&gt; 81KB</p>
<hr>
<h3 id="필드명-축약">필드명 축약</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/724a997c-4f53-40e9-9df0-abecc11300eb/image.png" alt="">
<code>ls -lh docs/sample/compressed-comparison.json</code> -&gt; 58KB</p>
<hr>
<h3 id="zlib-압축--필드명-축약">zlib 압축 + 필드명 축약</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/b9a4a250-d38a-473d-b44d-2f8187f44895/image.png" alt=""></p>
<hr>
<h3 id="zlib-압축--og">zlib 압축 + OG</h3>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/d6a69b11-398e-4d24-a452-aa546cda7992/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] - Basic (Docker & Docker Compose)]]></title>
            <link>https://velog.io/@hunter_joe99/Docker-Basic</link>
            <guid>https://velog.io/@hunter_joe99/Docker-Basic</guid>
            <pubDate>Sun, 11 Jan 2026 05:47:07 GMT</pubDate>
            <description><![CDATA[<h1 id="1-docker">1. Docker</h1>
<p>도커는 애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼입니다. 소프트웨어를 <strong>&#39;컨테이너&#39;</strong>라는 표준화된 유닛으로 패키징하여, 개발 환경과 운영 환경의 차이 없이 어디서든 동일하게 실행되도록 돕습니다. 가상 머신보다 가볍고 빠르며 자원을 효율적으로 사용하는 것이 특징입니다.</p>
<h1 id="2-docker-image">2. Docker Image</h1>
<p>도커 이미지는 컨테이너를 생성하기 위한 <strong>읽기 전용 설계도(템플릿)</strong>입니다. 코드, 라이브러리, 설정 파일 등 애플리케이션 실행에 필요한 모든 것을 담고 있는 스냅샷과 같습니다. 한 번 만들어진 이미지는 변하지 않으며, 이 이미지를 공유하면 누구나 똑같은 환경을 복제할 수 있습니다.</p>
<h1 id="3-docker-container">3. Docker Container</h1>
<p>컨테이너는 도커 이미지를 실행한 상태로, 애플리케이션이 실제 구동되는 격리된 공간입니다. 이미지라는 설계도로 만든 &#39;실제 제품&#39;에 비유할 수 있으며, 독립된 자원을 할당받아 프로세스가 실행됩니다. 하나의 이미지로 여러 개의 컨테이너를 동시에 띄울 수 있고, 삭제와 생성이 매우 자유롭습니다.</p>
<h1 id="4-dockerfile">4. Dockerfile</h1>
<p>도커파일은 <strong>이미지를 만들기 위한 명령어들을 모아놓은 텍스트 파일</strong>입니다. 어떤 베이스 이미지를 쓸지, 어떤 파일을 복사할지, 어떤 명령어를 실행할지 적어둔 &#39;레시피&#39;와 같습니다. 이 파일을 실행(build)하면 자동으로 도커 이미지가 생성되어 환경 구축을 자동화할 수 있습니다.</p>
<h1 id="차이점-요약">차이점 요약</h1>
<p><img src="https://velog.velcdn.com/images/hunter_joe99/post/f7654819-a8b0-4ca8-af27-7e468538182f/image.png" alt=""></p>
<h1 id="1-docker-compose">1. Docker Compose</h1>
<p>Docker Compose는 여러 개의 컨테이너를 하나의 서비스로 정의하고 묶어서 관리할 수 있게 도와주는 도구입니다.</p>
<p>별도의 <code>run.sh</code> 스크립트 파일을 작성하지 않아도, 하나의 <code>docker-compose.yml</code> 파일만으로 여러 컨테이너의 실행 환경과 설정을 선언적으로 정의할 수 있습니다.</p>
<pre><code class="language-bash">####### run.sh #######
# 1. 네트워크 생성
docker network create my-project-network

# 2. 볼륨 생성
docker volume create postgres_data

# 3. Redis 실행
docker run -d --name my-project-redis \
  --network my-project-network \
  redis:7-alpine

# 4. Postgres 실행
docker run -d --name my-project-postgres \
  --network my-project-network \
  -e POSTGRES_USER=myuser -e POSTGRES_PASSWORD=mypass \
  -v postgres_data:/var/lib/postgresql/data \
  postgres:15-alpine

# 5. API 서버 빌드 및 실행 (매우 복잡해짐)
docker build -t myproject-api -f Dockerfile.dev .
docker run -d --name my-project-server \
  --network crypto-express-network \
  -p 3000:3000 \
  -v $(pwd):/app -v /app/node_modules -v /app/.pnpm-store \
  -e DATABASE_URL=... (기타 환경변수 생략) \
  crypto-api sh -c &quot;pnpm install &amp;&amp; pnpm run start:dev&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NestJS] - Drizzle ORM을 사용한 DB 설정]]></title>
            <link>https://velog.io/@hunter_joe99/NestJS-Drizzle-ORM%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-DB-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hunter_joe99/NestJS-Drizzle-ORM%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-DB-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Fri, 09 Jan 2026 15:28:39 GMT</pubDate>
            <description><![CDATA[<p> NestJS에서 Drizzle ORM으로 PostgreSQL DB 설정하는 방법 
 <img src="https://velog.velcdn.com/images/hunter_joe99/post/f4ceb96e-a68d-4118-aca8-a4e423929378/image.png" alt=""></p>
<ul>
<li><a href="https://tsx.is/">https://tsx.is/</a></li>
<li><a href="https://www.npmjs.com/package/dotenv">https://www.npmjs.com/package/dotenv</a></li>
<li><a href="https://node-postgres.com/">https://node-postgres.com/</a></li>
</ul>
<h1 id="db-만들기---docker">DB 만들기 - Docker</h1>
<p><a href="https://orm.drizzle.team/docs/guides/postgresql-local-setup">https://orm.drizzle.team/docs/guides/postgresql-local-setup</a>
해당 문서 참고 </p>
<h1 id="drizzleconfigts">drizzle.config.ts</h1>
<pre><code class="language-ts">import { defineConfig } from &#39;drizzle-kit&#39;;

export default defineConfig({
  schema: &#39;./src/db/schema.ts&#39;,
  out: &#39;./drizzle&#39;,
  dialect: &#39;postgresql&#39;,
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});</code></pre>
<h1 id="database-모듈-설정">Database 모듈 설정</h1>
<pre><code class="language-ts">// db.module.ts
import &#39;dotenv/config&#39;;
import { drizzle } from &#39;drizzle-orm/node-postgres&#39;;
import { Module, Global } from &#39;@nestjs/common&#39;;
import { Pool } from &#39;pg&#39;;

import { NodePgDatabase } from &#39;drizzle-orm/node-postgres&#39;;
import * as schema from &#39;./schema&#39;; // Your Drizzle schema files

// The type for your fully configured Drizzle DB instance
export type DB = NodePgDatabase&lt;typeof schema&gt;;

const dbProvider = {
  provide: &#39;DATABASE&#39;,
  useFactory: () =&gt;
    drizzle({
      connection: {
        connectionString: process.env.DATABASE_URL,
        ssl: false, // 로컬 Docker에서는 SSL 지원 X 
      },
    }),
};

@Global()
@Module({
  providers: [dbProvider],
  exports: [&#39;DATABASE&#39;],
})
export class DbModule {}</code></pre>
<h1 id="schema---user">schema - user</h1>
<pre><code class="language-ts">import { integer, pgTable, varchar } from &#39;drizzle-orm/pg-core&#39;;

export const usersTable = pgTable(&#39;users&#39;, {
  id: integer().primaryKey().generatedAlwaysAsIdentity(),
  name: varchar({ length: 255 }).notNull(),
  age: integer().notNull(),
  email: varchar({ length: 255 }).notNull().unique(),
});</code></pre>
<h1 id="뽀나스">뽀나스</h1>
<pre><code class="language-ts">import { drizzle } from &quot;drizzle-orm/node-postgres&quot;
const db = drizzle(process.env.DATABASE_URL);
const pool = db.$client;

// above is equivalent to
import { drizzle } from &quot;drizzle-orm/node-postgres&quot;;
import { Pool } from &quot;pg&quot;;
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});
const db = drizzle({ client: pool });</code></pre>
<p>첫번째 방식은 기본설정을 그대로 쓰는 것 튜닝 X </p>
<p>두번째는 Pool을 만들어서 세부 설정(튜닝)함 
해당 튜닝 config 정보들은 <a href="https://node-postgres.com/apis/client">링크</a> 참고</p>
<ul>
<li><a href="https://node-postgres.com/apis/client">https://node-postgres.com/apis/client</a></li>
</ul>
<pre><code class="language-ts">type Config = {
  user?: string, // default process.env.PGUSER || process.env.USER
  password?: string or function, //default process.env.PGPASSWORD
  host?: string, // default process.env.PGHOST
  port?: number, // default process.env.PGPORT
  database?: string, // default process.env.PGDATABASE || user
  connectionString?: string, // e.g. postgres://user:password@host:5432/database
  ssl?: any, // passed directly to node.TLSSocket, supports all tls.connect options
  types?: any, // custom type parsers
  statement_timeout?: number, // number of milliseconds before a statement in query will time out, default is no timeout
  query_timeout?: number, // number of milliseconds before a query call will timeout, default is no timeout
  lock_timeout?: number, // number of milliseconds a query is allowed to be en lock state before it&#39;s cancelled due to lock timeout
  application_name?: string, // The name of the application that created this Client instance
  connectionTimeoutMillis?: number, // number of milliseconds to wait for connection, default is no timeout
  keepAliveInitialDelayMillis?: number, // set the initial delay before the first keepalive probe is sent on an idle socket
  idle_in_transaction_session_timeout?: number, // number of milliseconds before terminating any session with an open idle transaction, default is no timeout
  client_encoding?: string, // specifies the character set encoding that the database uses for sending data to the client
  fallback_application_name?: string, // provide an application name to use if application_name is not set
  options?: string // command-line options to be sent to the server
}</code></pre>
<h1 id="참고자료">참고자료</h1>
<ul>
<li><p><a href="https://docs.nestjs.com/fundamentals/custom-providers">https://docs.nestjs.com/fundamentals/custom-providers</a></p>
</li>
<li><p><a href="https://orm.drizzle.team/docs/get-started/postgresql-new">https://orm.drizzle.team/docs/get-started/postgresql-new</a></p>
</li>
<li><p><a href="https://orm.drizzle.team/docs/get-started-postgresql#node-postgres">https://orm.drizzle.team/docs/get-started-postgresql#node-postgres</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] - 컴파일 에러를 피하고 싶어 with archive]]></title>
            <link>https://velog.io/@hunter_joe99/TypeScript-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%97%90%EB%9F%AC%EB%A5%BC-%ED%94%BC%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%96%B4-with-archive</link>
            <guid>https://velog.io/@hunter_joe99/TypeScript-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%97%90%EB%9F%AC%EB%A5%BC-%ED%94%BC%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%96%B4-with-archive</guid>
            <pubDate>Fri, 26 Dec 2025 21:57:36 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하다보면 구조를 완전히 바꿔야 할때가 생긴다. 이때 개발 방향성을 잃지 않기 위해서 <code>archive</code> 폴더를 주로 사용한다. </p>
<p>하지만 이때 발생되는 에러는 TS가 모든 폴더 및 파일을 컴파일 하다보니 dev 환경에서 서버를 시작하면 컴파일 에러가 나와 바로 테스트하기 어려운 경우도 생긴다. </p>
<p>이때 <code>tsconfig</code>를 다음과 같이 수정해서 컴파일 문제를 해결한다. </p>
<pre><code class="language-ts">// tsconfig
{
  &quot;compilerOptions&quot;: {
    // ... 기존 설정
  },
  &quot;exclude&quot;: [
    &quot;node_modules&quot;,
    &quot;src/archive&quot; // archive 폴더를 컴파일 대상에서 제외
  ]
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] - keyof, typeof 그리고 union]]></title>
            <link>https://velog.io/@hunter_joe99/TypeScript-keyof-typeof-%EA%B7%B8%EB%A6%AC%EA%B3%A0-union</link>
            <guid>https://velog.io/@hunter_joe99/TypeScript-keyof-typeof-%EA%B7%B8%EB%A6%AC%EA%B3%A0-union</guid>
            <pubDate>Sun, 30 Nov 2025 22:43:04 GMT</pubDate>
            <description><![CDATA[<p>미리 Union타입을 만들어서 사용하는 방법과 자료구조에서 Union 타입을 추출해는 방법이 있다. </p>
<h1 id="기본적인-union-타입-선언">기본적인 Union 타입 선언</h1>
<pre><code class="language-ts">type Fruit = &quot;apple&quot; | &quot;orange&quot; | &quot;lemon&quot;| &quot;melon&quot;

const myFruit: Fruit  = &quot;kiwi&quot; // Type &#39;&quot;kiwi&quot;&#39; is not assignable to type &#39;Fruit&#39;

const yourFruit: Fruit = &quot;apple&quot;</code></pre>
<h1 id="배열에서-union-타입-추출하기">배열에서 Union 타입 추출하기</h1>
<pre><code class="language-ts">const fruits = [&quot;apple&quot;, &quot;orange&quot;, &quot;lemon&quot;] as const

type Fruits = typeof fruits[number]</code></pre>
<h1 id="객체에서-union-타입-추출하기-key-value">객체에서 Union 타입 추출하기 (key, value)</h1>
<pre><code class="language-ts">const countryCurrency = {
    KOREA : &quot;KRW&quot;,
      USA : &quot;USD&quot;,
      CANADA : &quot;CAD&quot;,
} as const;

type Country = keyof typeof countryCurrency
type Currency = (typeof countryCurrency)[keyof typeof countryCurrency]</code></pre>
]]></description>
        </item>
    </channel>
</rss>