<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Wooncloud Blog</title>
    <link>https://wooncloud.tistory.com/</link>
    <description>프로그래밍, 디자인 및 각종 이야기와 리뷰를 담는 블로그</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 16:36:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>운클라우드</managingEditor>
    <image>
      <title>Wooncloud Blog</title>
      <url>https://tistory1.daumcdn.net/tistory/3734618/attach/7fd1a32c972647e19174ecf1fdca9f0e</url>
      <link>https://wooncloud.tistory.com</link>
    </image>
    <item>
      <title>React-hook-form</title>
      <link>https://wooncloud.tistory.com/171</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXMMKN/dJMcadm1GPx/DlSslaAfM46lT0t0iRhx5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXMMKN/dJMcadm1GPx/DlSslaAfM46lT0t0iRhx5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXMMKN/dJMcadm1GPx/DlSslaAfM46lT0t0iRhx5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXMMKN%2FdJMcadm1GPx%2FDlSslaAfM46lT0t0iRhx5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;144&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Hook Form은 React에서 폼(Form)을 쉽고 효율적으로 관리하기 위한 라이브러리라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 리액트에서 input을 만들때 useState를 사용해 '제어 컴포넌트' 방식으로 구현하고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 input이 많아지면 코드가 복잡해지고 성능이 떨어지는 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴때 React-hook-form를 쓰면 편하다고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 React Hook Form을 쓸까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 최적화 (불필요한 리렌더링 방지)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 가장 큰 장점이라고 한다. 일반적인 &lt;code&gt;useState&lt;/code&gt; 방식은 글자 하나를 칠 때마다 전체 컴포넌트가 다시 그려지는데, React-hook-form는 &lt;b&gt;비제어 컴포넌트(Uncontrolled Components)&lt;/b&gt; 방식을 기반으로 하여 사용자가 입력을 마칠 때까지 리렌더링을 최소화한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가볍고 의존성이 없음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 폼 라이브러리(예: Formik)에 비해 크기가 매우 작고, 다른 외부 라이브러리에 의존하지 않아 프로젝트에 부담이 적다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;간결한 코드 (유효성 검사)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일 형식 확인, 필수 입력 체크 등을 아주 적은 코드로 구현할 수 있다. &lt;code&gt;register&lt;/code&gt; 함수 하나로 입력창과 폼 상태를 연결할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 동작 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React-hook-form는 리액트의 &lt;code&gt;ref&lt;/code&gt;를 사용하여 DOM 요소에 직접 접근합니다. 이를 통해 상태(State)가 바뀔 때마다 화면을 계속 다시 그리는 비용을 아껴준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;간단한 코드 예시&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useForm } from &quot;react-hook-form&quot;;

function App() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  // 데이터 제출 시 실행될 함수
  const onSubmit = (data) =&amp;gt; console.log(data);

  return (
    &amp;lt;form onSubmit={handleSubmit(onSubmit)}&amp;gt;
      {/* 1. register를 통해 input 등록 */}
      &amp;lt;input {...register(&quot;username&quot;, { required: &quot;아이디를 입력해주세요&quot; })} /&amp;gt;

      {/* 2. 에러 메시지 출력 */}
      {errors.username &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{errors.username.message}&amp;lt;/p&amp;gt;}

      &amp;lt;button type=&quot;submit&quot;&amp;gt;제출&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 사용하면 좋을까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입력 필드가 많은 폼&lt;/b&gt;을 만들 때 (회원가입, 설문조사 등)&lt;/li&gt;
&lt;li&gt;폼 입력 시 발생하는 &lt;b&gt;버벅임(성능 저하)&lt;/b&gt;을 줄이고 싶을 때&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유효성 검사(Validation)&lt;/b&gt; 로직을 깔끔하게 관리하고 싶을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Timefit이라는 프로젝트를 사이드로 만들고 있는데, 여기서 입력하는 폼이 많은 부분을 React-hook-form으로 마이그레이션을 해볼까 생각하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-hook-form.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-hook-form.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766554234876&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;React Hook Form - performant, flexible and extensible form library&quot; data-og-description=&quot;Performant, flexible and extensible forms with easy-to-use validation.&quot; data-og-host=&quot;react-hook-form.com&quot; data-og-source-url=&quot;https://react-hook-form.com/&quot; data-og-url=&quot;https://react-hook-form.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://react-hook-form.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react-hook-form.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React Hook Form - performant, flexible and extensible form library&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Performant, flexible and extensible forms with easy-to-use validation.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-hook-form.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/React</category>
      <category>React</category>
      <category>react-hook-form</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/171</guid>
      <comments>https://wooncloud.tistory.com/171#entry171comment</comments>
      <pubDate>Wed, 24 Dec 2025 14:31:17 +0900</pubDate>
    </item>
    <item>
      <title>Enum의 ordinal() 메서드</title>
      <link>https://wooncloud.tistory.com/170</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ORdyZ/dJMcaaw7E1L/2ke6G42LmEkuyehUwBelQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ORdyZ/dJMcaaw7E1L/2ke6G42LmEkuyehUwBelQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ORdyZ/dJMcaaw7E1L/2ke6G42LmEkuyehUwBelQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FORdyZ%2FdJMcaaw7E1L%2F2ke6G42LmEkuyehUwBelQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;240&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enum에 정규식과 마스킹 메소드를 정의했는데, 정규식은 패턴에 따라 부분집합인 정규식도 간혹 존재한다. 나는 Enum에 작은 부분집합을 오름차순으로 정렬하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패턴 검사할때도 Enum에 정의된 순서대로 검사가 되었으면 좋겠다고 생각했다. 각 Enum 요소에 Order 값을 추가해야하나 고민을 했는데, ordinal이라는 값이 있다는 것을 알게되어 활용했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Enum의 &lt;code&gt;ordinal()&lt;/code&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Enum 정의 순서 = ordinal 값&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Enum은 &lt;b&gt;정의된 순서대로 자동으로 0부터 시작하는 인덱스&lt;/b&gt;가 부여된다. 그 인덱스가 바로 ordinal이다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;public enum BlackKeywordPattern {
    PHONE(...),     // ordinal() = 0
    EMAIL(...),     // ordinal() = 1
    SSN(...),       // ordinal() = 2
    PASSPORT(...),  // ordinal() = 3
    ACCOUNT(...),   // ordinal() = 4
    CARD(...);      // ordinal() = 5
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. ordinal() 메서드&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public final int ordinal()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Java의 모든 Enum이 가지는 메서드&lt;/b&gt; (java.lang.Enum 클래스에서 상속)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정의된 순서를 정수로 반환&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;final 메서드&lt;/b&gt; - 오버라이드 불가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동으로 할당&lt;/b&gt; - 개발자가 직접 설정하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 실제 동작 예시&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Enum 상수의 ordinal 값 확인
System.out.println(BlackKeywordPattern.PHONE.ordinal());    // 0
System.out.println(BlackKeywordPattern.EMAIL.ordinal());    // 1
System.out.println(BlackKeywordPattern.SSN.ordinal());      // 2
System.out.println(BlackKeywordPattern.PASSPORT.ordinal()); // 3
System.out.println(BlackKeywordPattern.ACCOUNT.ordinal());  // 4
System.out.println(BlackKeywordPattern.CARD.ordinal());     // 5&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 정렬 원리&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;Collections.sort(sortedPatterns, new Comparator&amp;lt;BlackKeywordPattern&amp;gt;() {
    @Override
    public int compare(BlackKeywordPattern p1, BlackKeywordPattern p2) {
        return Integer.compare(p1.ordinal(), p2.ordinal());
        // PHONE(0) &amp;lt; EMAIL(1) &amp;lt; SSN(2) &amp;lt; ... 순서로 정렬됨
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// 입력: [CARD(5), PHONE(0), EMAIL(1)]
// 정렬 과정:
// - CARD(5) vs PHONE(0) &amp;rarr; PHONE이 작음 &amp;rarr; PHONE 먼저
// - CARD(5) vs EMAIL(1) &amp;rarr; EMAIL이 작음 &amp;rarr; EMAIL 먼저
// 결과: [PHONE(0), EMAIL(1), CARD(5)]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Java 내부 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Enum의 실제 컴파일 결과:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;// 우리가 작성한 코드:
public enum BlackKeywordPattern {
    PHONE(...),
    EMAIL(...),
    SSN(...)
}

// 컴파일러가 생성하는 코드 (개념적):
public final class BlackKeywordPattern extends Enum&amp;lt;BlackKeywordPattern&amp;gt; {
    public static final BlackKeywordPattern PHONE = new BlackKeywordPattern(&quot;PHONE&quot;, 0, ...);
    public static final BlackKeywordPattern EMAIL = new BlackKeywordPattern(&quot;EMAIL&quot;, 1, ...);
    public static final BlackKeywordPattern SSN = new BlackKeywordPattern(&quot;SSN&quot;, 2, ...);

    private BlackKeywordPattern(String name, int ordinal, ...) {
        super(name, ordinal); // ordinal 값이 여기서 설정됨
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8. 대안 비교&lt;/b&gt;&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방법&lt;/th&gt;
&lt;th&gt;장점&lt;/th&gt;
&lt;th&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ordinal() 사용&lt;/b&gt; ✅&lt;/td&gt;
&lt;td&gt;자동, 안전, 간단&lt;/td&gt;
&lt;td&gt;Enum 순서 변경 시 영향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;priority 필드 추가&lt;/td&gt;
&lt;td&gt;순서와 독립적&lt;/td&gt;
&lt;td&gt;수동 관리, 실수 가능성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;하드코딩&lt;/td&gt;
&lt;td&gt;명시적&lt;/td&gt;
&lt;td&gt;중복 코드, 유지보수 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Enum 정의 순서 = ordinal 값 = 정렬 순서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ordinal을 이용하면 아래와 같은 순기능을 얻을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컴파일 타임에 결정&lt;/b&gt; - 런타임에 변경 불가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동 할당&lt;/b&gt; - 개발자 실수 방지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순서 보장&lt;/b&gt; - Enum 정의 순서 = ordinal 순서&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불변성&lt;/b&gt; - final 메서드로 오버라이드 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;Enum 순서를 바꾸면 ordinal도 변경되니 조심&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// 변경 전
PHONE,   // ordinal = 0
EMAIL,   // ordinal = 1
SSN      // ordinal = 2

// Enum 순서 변경 후
EMAIL,   // ordinal = 0 (변경됨!)
PHONE,   // ordinal = 1 (변경됨!)
SSN      // ordinal = 2 (유지)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 이 코드는:
return Integer.compare(p1.ordinal(), p2.ordinal());

// 이렇게 동작한다:
return Integer.compare(0, 1);  // PHONE vs EMAIL &amp;rarr; PHONE 먼저
return Integer.compare(0, 5);  // PHONE vs CARD &amp;rarr; PHONE 먼저
return Integer.compare(4, 5);  // ACCOUNT vs CARD &amp;rarr; ACCOUNT 먼저&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발 아카이브/JAVA</category>
      <category>Enum</category>
      <category>ordinal</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/170</guid>
      <comments>https://wooncloud.tistory.com/170#entry170comment</comments>
      <pubDate>Tue, 23 Dec 2025 12:07:10 +0900</pubDate>
    </item>
    <item>
      <title>CSS-Loaders.com - 600+ 순수 CSS 로더 컬렉션</title>
      <link>https://wooncloud.tistory.com/169</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://css-loaders.com/&quot;&gt;https://css-loaders.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1761108315818&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CSS Loaders: A collection of more than 600 loading animations&quot; data-og-description=&quot;The biggest collection of CSS-only loaders. More than 600 loading animations made by Temani Afif using a single element.&quot; data-og-host=&quot;css-loaders.com&quot; data-og-source-url=&quot;https://css-loaders.com/&quot; data-og-url=&quot;https://css-loaders.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sfACn/hyZL00oEUB/BPBDfQfK3LbY6ZZlSq5Sk1/img.jpg?width=1600&amp;amp;height=800&amp;amp;face=1180_717_1240_782&quot;&gt;&lt;a href=&quot;https://css-loaders.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://css-loaders.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sfACn/hyZL00oEUB/BPBDfQfK3LbY6ZZlSq5Sk1/img.jpg?width=1600&amp;amp;height=800&amp;amp;face=1180_717_1240_782');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CSS Loaders: A collection of more than 600 loading animations&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The biggest collection of CSS-only loaders. More than 600 loading animations made by Temani Afif using a single element.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;css-loaders.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css-loaders.com은 단일 HTML 요소(보통)와 순수 CSS만으로 구현한 600개 이상의 로딩 애니메이션을 모아 보여주는 사이트입니다. 사용자는 클릭으로 CSS 코드를 복사해 즉시 프로젝트에 붙여넣을 수 있어 빠른 시제품 제작과 UI 개선에 유용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Oct-22-2025 13-46-00.gif&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;1234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nJj80/dJMb9O1M2Mi/kbc7FSk9nb4BYjg7rwJdek/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nJj80/dJMb9O1M2Mi/kbc7FSk9nb4BYjg7rwJdek/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nJj80/dJMb9O1M2Mi/kbc7FSk9nb4BYjg7rwJdek/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/nJj80/dJMb9O1M2Mi/kbc7FSk9nb4BYjg7rwJdek/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2340&quot; height=&quot;1234&quot; data-filename=&quot;Oct-22-2025 13-46-00.gif&quot; data-origin-width=&quot;2340&quot; data-origin-height=&quot;1234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  핵심 특징&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방대한 컬렉션: 600개 이상(꾸준히 추가).&lt;/li&gt;
&lt;li&gt;단일 엘리먼트 방식: 각 로더는 최소한의 HTML(대개 한 개 div)로 동작하여 통합이 쉽습니다.&lt;/li&gt;
&lt;li&gt;카테고리 분류: Spinner, Dots, Bars, Shapes 등 테마별로 탐색 가능하며 섹션별 샘플 페이지 제공됩니다.&lt;/li&gt;
&lt;li&gt;즉시 복사 기능: 로더 클릭으로 CSS 복사 및 재사용 가능.&lt;/li&gt;
&lt;li&gt;랜덤 추천 기능: 무작위 로더 선택 페이지로 영감 얻기 용이합니다.&lt;/li&gt;
&lt;li&gt;튜토리얼/아티클: 단일 요소로 만드는 기법, 단일 엘리먼트 디자인 원리 등 제작자 글과 가이드 제공됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  장점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가벼움: JS 없이 CSS만으로 동작해 로드 비용이 낮습니다.&lt;/li&gt;
&lt;li&gt;빠른 적용성: 복사 &amp;rarr; 붙여넣기만으로 즉시 사용 가능.&lt;/li&gt;
&lt;li&gt;디자인 다양성: 단순 스피너부터 3D&amp;middot;복잡한 패턴까지 폭넓은 선택지 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 고려사항 / 제약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 호환성: 일부 고급 CSS(예: :has(), offset-path 등)는 브라우저별 지원 차이가 있어 특히 오래된 브라우저나 일부 모바일 Safari에서 동작하지 않을 수 있습니다.&lt;/li&gt;
&lt;li&gt;접근성: 시각적 애니메이션은 감각에 민감한 사용자에겐 불편을 줄 수 있으므로 prefers-reduced-motion 대응 등 접근성 처리가 필요합니다.&lt;/li&gt;
&lt;li&gt;성능: 개별 애니메이션은 가벼워도 과도한 수의 애니메이션 동시 실행은 렌더링 비용을 높일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 적용 팁&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prefers-reduced-motion 미디어 쿼리를 넣어 애니메이션을 줄이거나 비활성화하세요.&lt;/li&gt;
&lt;li&gt;색상&amp;middot;크기 변수화를 통해 테마와 일관성 있게 재사용하세요; 대부분 로더는 하나의 색상 값만 바꿔도 전체 색상을 변경할 수 있습니다.&lt;/li&gt;
&lt;li&gt;로더는 '진짜' 로딩 시간 단축 수단이 아니므로, 가능한 실제 로딩을 줄이는 최적화(지연 로딩, 캐싱 등)와 병행하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;text-align: start;&quot; href=&quot;https://dev.to/afif/css-loaderscom-the-biggest-collection-of-loading-animations-more-than-500--23jg&quot;&gt;dev.to 글 요약&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1761108461440&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;css-loaders.com: The Biggest Collection of Loading Animations (more than 500  )&quot; data-og-description=&quot;It's been a while but I finally did it. I collected all my CSS-only loaders into one unique...&quot; data-og-host=&quot;dev.to&quot; data-og-source-url=&quot;https://dev.to/afif/css-loaderscom-the-biggest-collection-of-loading-animations-more-than-500--23jg&quot; data-og-url=&quot;https://dev.to/afif/css-loaderscom-the-biggest-collection-of-loading-animations-more-than-500--23jg&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bVNzps/hyZLeqzA03/1PK03IDWYKXBUGN8WFjFTk/img.jpg?width=1000&amp;amp;height=500&amp;amp;face=0_0_1000_500&quot;&gt;&lt;a href=&quot;https://dev.to/afif/css-loaderscom-the-biggest-collection-of-loading-animations-more-than-500--23jg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.to/afif/css-loaderscom-the-biggest-collection-of-loading-animations-more-than-500--23jg&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bVNzps/hyZLeqzA03/1PK03IDWYKXBUGN8WFjFTk/img.jpg?width=1000&amp;amp;height=500&amp;amp;face=0_0_1000_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;css-loaders.com: The Biggest Collection of Loading Animations (more than 500  )&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;It's been a while but I finally did it. I collected all my CSS-only loaders into one unique...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.to&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;text-align: start;&quot; href=&quot;https://www.sliderrevolution.com/resources/css-loaders/&quot;&gt;접근성 권장 사항 참고, &lt;/a&gt;&lt;a style=&quot;text-align: start;&quot; href=&quot;https://www.sliderrevolution.com/resources/css-loaders/&quot;&gt;성능 팁 참고&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1761108471886&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;The Best 100 CSS Loaders to Choose from For Your Website&quot; data-og-description=&quot;This article is a latest collection of the best 100 CSS loaders. Each demo includes the source code used to create the loader.&quot; data-og-host=&quot;www.sliderrevolution.com&quot; data-og-source-url=&quot;https://www.sliderrevolution.com/resources/css-loaders/&quot; data-og-url=&quot;https://www.sliderrevolution.com/resources/css-loaders/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CHIvG/hyZL3pgsDw/z1fIoyFbNk6Uhx5CLqdT9k/img.jpg?width=1180&amp;amp;height=664&amp;amp;face=0_0_1180_664&quot;&gt;&lt;a href=&quot;https://www.sliderrevolution.com/resources/css-loaders/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.sliderrevolution.com/resources/css-loaders/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CHIvG/hyZL3pgsDw/z1fIoyFbNk6Uhx5CLqdT9k/img.jpg?width=1180&amp;amp;height=664&amp;amp;face=0_0_1180_664');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The Best 100 CSS Loaders to Choose from For Your Website&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This article is a latest collection of the best 100 CSS loaders. Each demo includes the source code used to create the loader.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.sliderrevolution.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>정보/유용한 사이트</category>
      <category>CSS</category>
      <category>loading</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/169</guid>
      <comments>https://wooncloud.tistory.com/169#entry169comment</comments>
      <pubDate>Wed, 22 Oct 2025 13:48:11 +0900</pubDate>
    </item>
    <item>
      <title>Turborepo 빌드 의존성 문제 - Turborepo Package and Task Graphs</title>
      <link>https://wooncloud.tistory.com/168</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;1008&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vbGBH/btsQ7sE58En/PGtbs0e9KCGWMPXlNYnNtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vbGBH/btsQ7sE58En/PGtbs0e9KCGWMPXlNYnNtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vbGBH/btsQ7sE58En/PGtbs0e9KCGWMPXlNYnNtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvbGBH%2FbtsQ7sE58En%2FPGtbs0e9KCGWMPXlNYnNtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;135&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;1008&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 팀에서 다양한 플로우 서비스에 업무, 간트차트 기능을 라이브러리로 적용하기 위해 리포지토리를 분리하고 모노레포를 적용중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모노레포를 구현하기 위해 pnpm을 사용하여 인스톨 및 빌드를 하는데 Turborepo를 이용해 빌드하다보면 이런 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1661&quot; data-origin-height=&quot;503&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9yxxy/btsQ9jtjPQB/WIKjnTkbrLZwCfVfLxxKK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9yxxy/btsQ9jtjPQB/WIKjnTkbrLZwCfVfLxxKK0/img.png&quot; data-alt=&quot;빌드 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9yxxy/btsQ9jtjPQB/WIKjnTkbrLZwCfVfLxxKK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9yxxy%2FbtsQ9jtjPQB%2FWIKjnTkbrLZwCfVfLxxKK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;빌드 실패&quot; loading=&quot;lazy&quot; width=&quot;1661&quot; height=&quot;503&quot; data-origin-width=&quot;1661&quot; data-origin-height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빌드 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드하는데 다음과 같이 빌드가 안되는 문제가 있었는데, 이는 빌드 의존성이 있음에도 의존성을 무시한채 빌드되어서 생긴 문제다.&lt;br /&gt;다시 말하면 Turborepo는 빌드 순서가 있고, 각 패키지들이 순서대로 빌드가 되어야 의존하고 있는 다른 패키지들이 빌드가 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;각 패키지의 package.json에 dependencies 추가.&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpS89c/btsQ8UAzHye/oOwNRg1fI3pRhwCdU1LLsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpS89c/btsQ8UAzHye/oOwNRg1fI3pRhwCdU1LLsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpS89c/btsQ8UAzHye/oOwNRg1fI3pRhwCdU1LLsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpS89c%2FbtsQ8UAzHye%2FoOwNRg1fI3pRhwCdU1LLsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;packagejson1&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;321&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이미 peerDependancies에 의존하고 있는 패키지들이 있는 상태인데 빌드가 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그러냐면 Turbo는 패키지 그래프(dependencies/devDependencies/optionalDependencies)로 빌드 순서를 결정하는데, peerDependencies로만 선언하면 의존 에지가 없어 동시에 빌드된다고 함. 빌드 순서를 보장하려면 내부 패키지를 devDependencies(또는 dependencies)에도 추가해야한다고 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어짜피 빌드할때만 쓰는거 devDependencies에 넣으면 된다고 생각해서 다음과 같이 넣었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BPPLH/btsQ9yRqD2h/1XDq3kQmCkfTWkwIVmoRl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BPPLH/btsQ9yRqD2h/1XDq3kQmCkfTWkwIVmoRl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BPPLH/btsQ9yRqD2h/1XDq3kQmCkfTWkwIVmoRl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBPPLH%2FbtsQ9yRqD2h%2F1XDq3kQmCkfTWkwIVmoRl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;packagejson2&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;367&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 workspace:*와 workspace:^ 가 뭔지 궁금해서 검색후 정리함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;workspace:*&lt;/code&gt;는 워크스페이스 안에서는 로컬 패키지를 심볼릭 링크로 바로 사용하고, 패키지를 배포할 때는 해당 의존성의 정확한 버전 값으로 치환. 즉 &amp;ldquo;현재 이 패키지와 동일한 버전만 쓴다&amp;rdquo;는 의미.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;workspace:^&lt;/code&gt; 역시 개발 중에는 로컬 패키지를 직접 링크하지만, 배포 시에는 ^1.2.3 같은 캐럿 범위(호환 가능한 마이너&amp;middot;패치)를 가진 의존성으로 바뀜. 최신 마이너/패치 업데이트를 허용하려면 &lt;code&gt;workspace:^&lt;/code&gt;를, 정확히 같은 버전만 고정하려면 &lt;code&gt;workspace:*&lt;/code&gt;를 쓰면 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;turbo.json 수정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모든 패키지에 package.json로 찾아 들어가서 devDependencies를 수정하는게 너무 비효율적이라고 생각함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤것들이 서로 의존하고 있는지 한눈에 보기 힘들것 같아서 다른 방법을 찾아봤음. 이때 turbo.json을 발견하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json에 각각 devDependencies에 명시하지 않고, Turborepo의 &lt;code&gt;turbo.json&lt;/code&gt;에서 명시적으로 의존성을 지정할 수 있다고 해서 수정함.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;&quot;@flow-library/grid-popup#build&quot;: {  
  &quot;dependsOn&quot;: [&quot;@flow-library/grid-core#build&quot;],  
  &quot;outputs&quot;: [&quot;dist/**&quot;]  
},  
&quot;@flow-library/grid-gantt#build&quot;: {  
  &quot;dependsOn&quot;: [&quot;@flow-library/grid-core#build&quot;, &quot;@flow-library/grid-popup#build&quot;],  
  &quot;outputs&quot;: [&quot;dist/**&quot;]  
},  
&quot;@flow-library/grid-header#build&quot;: {  
  &quot;dependsOn&quot;: [&quot;@flow-library/grid-core#build&quot;, &quot;@flow-library/grid-popup#build&quot;],  
  &quot;outputs&quot;: [&quot;dist/**&quot;]  
},  
&quot;@flow-library/grid-toolbar#build&quot;: {  
  &quot;dependsOn&quot;: [&quot;@flow-library/grid-core#build&quot;, &quot;@flow-library/grid-popup#build&quot;, &quot;@flow-library/grid-gantt#build&quot;],  
  &quot;outputs&quot;: [&quot;dist/**&quot;]  
},  
&quot;@flow-library/grid-tutorial#build&quot;: {  
  &quot;dependsOn&quot;: [&quot;@flow-library/grid-core#build&quot;],  
  &quot;outputs&quot;: [&quot;dist/**&quot;]  
},  
&quot;@flow-library/grid-list-kit#build&quot;: {  
  &quot;dependsOn&quot;: [  
    &quot;@flow-library/grid-core#build&quot;,  
    &quot;@flow-library/grid-popup#build&quot;,  
    &quot;@flow-library/grid-gantt#build&quot;,  
    &quot;@flow-library/grid-header#build&quot;,  
    &quot;@flow-library/grid-toolbar#build&quot;,  
    &quot;@flow-library/grid-tutorial#build&quot;  
  ],  
  &quot;outputs&quot;: [&quot;dist/**&quot;]  
},  
&quot;@flow-library/flow-grid#build&quot;: {  
  &quot;dependsOn&quot;: [  
    &quot;@flow-library/grid-core#build&quot;,  
    &quot;@flow-library/grid-popup#build&quot;,  
    &quot;@flow-library/grid-gantt#build&quot;,  
    &quot;@flow-library/grid-header#build&quot;,  
    &quot;@flow-library/grid-toolbar#build&quot;,  
    &quot;@flow-library/grid-tutorial#build&quot;,  
    &quot;@flow-library/grid-list-kit#build&quot;  
  ],  
  &quot;outputs&quot;: [&quot;dist/**&quot;]  
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 turbo.json을 수정하고 devDependencies를 모두 제거해도 정상적으로 빌드가 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;빌드 순서&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;grid-core (의존성 없음)&lt;/li&gt;
&lt;li&gt;grid-popup, grid-tutorial (grid-core 의존)&lt;/li&gt;
&lt;li&gt;grid-gantt, grid-header (grid-core + grid-popup 의존)&lt;/li&gt;
&lt;li&gt;grid-toolbar (grid-core + grid-popup + grid-gantt 의존)&lt;/li&gt;
&lt;li&gt;grid-list-kit (모든 컴포넌트 의존)&lt;/li&gt;
&lt;li&gt;flow-grid (모든 패키지 의존)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;code&gt;#build&lt;/code&gt; 문법과 예시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법: &lt;code&gt;&amp;lt;패키지명&amp;gt;#&amp;lt;태스크명&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;&quot;@flow-library/grid-popup#build&quot;: {
  &quot;dependsOn&quot;: [&quot;@flow-library/grid-core#build&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의미: grid-popup 패키지의 build 태스크는 grid-core 패키지의 build 태스크 이후에 실행됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json 예시:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@flow-library/grid-popup&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;rollup -c&quot;,
    &quot;test&quot;: &quot;vitest&quot;,
    &quot;lint&quot;: &quot;eslint .&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;turbo.json에서의 태스크 지정:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;@flow-library/grid-popup#build&quot;: { },
  &quot;@flow-library/grid-popup#test&quot;: { },
  &quot;@flow-library/grid-popup#lint&quot;: { }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 태스크 의존성 예시:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;@flow-library/grid-popup#build&quot;: {
    &quot;dependsOn&quot;: [&quot;@flow-library/grid-core#build&quot;]
  },
  &quot;@flow-library/grid-popup#test&quot;: {
    &quot;dependsOn&quot;: [&quot;@flow-library/grid-core#test&quot;]
  },
  &quot;@flow-library/grid-popup#lint&quot;: {
    &quot;dependsOn&quot;: []
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 체인 예시:&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;&quot;@flow-library/grid-gantt#build&quot;: {
  &quot;dependsOn&quot;: [
    &quot;@flow-library/grid-core#build&quot;,
    &quot;@flow-library/grid-popup#build&quot;
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;#&lt;/code&gt;는 &amp;ldquo;패키지의 특정 태스크&amp;rdquo; 지정자&lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;@flow-library/grid-popup#build&lt;/code&gt; = grid-popup의 build 태스크&lt;/li&gt;
&lt;li&gt;패키지별 태스크 의존성을 Turborepo에 명확히 전달할 때 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;참고 링크&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://turborepo.com/docs/core-concepts/package-and-task-graph&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://turborepo.com/docs/core-concepts/package-and-task-graph&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1760370777797&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Package and Task Graphs | Turborepo&quot; data-og-description=&quot;Turborepo builds a Task Graph based on your configuration and repository structure.&quot; data-og-host=&quot;turborepo.com&quot; data-og-source-url=&quot;https://turborepo.com/docs/core-concepts/package-and-task-graph&quot; data-og-url=&quot;https://turborepo.com/docs/core-concepts/package-and-task-graph&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VThuL/hyZLBk0a0Z/BIKXip0g9UuhepXfKB0gS1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ZmiYH/hyZLk4u00p/KHNVk2kWeI2smsvbJOnk31/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://turborepo.com/docs/core-concepts/package-and-task-graph&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://turborepo.com/docs/core-concepts/package-and-task-graph&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VThuL/hyZLBk0a0Z/BIKXip0g9UuhepXfKB0gS1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ZmiYH/hyZLk4u00p/KHNVk2kWeI2smsvbJOnk31/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Package and Task Graphs | Turborepo&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Turborepo builds a Task Graph based on your configuration and repository structure.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;turborepo.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/개발 관련 지식</category>
      <category>pnpm</category>
      <category>turborepo</category>
      <category>모노레포</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/168</guid>
      <comments>https://wooncloud.tistory.com/168#entry168comment</comments>
      <pubDate>Tue, 14 Oct 2025 00:53:09 +0900</pubDate>
    </item>
    <item>
      <title>Spring 기본 - JWT 인증 시스템</title>
      <link>https://wooncloud.tistory.com/167</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/161&quot;&gt;https://wooncloud.tistory.com/161&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758460759981&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 시작 - 세팅부터 CRUD API 까지&quot; data-og-description=&quot;1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream API 등 중요한 기능이 도입된 버전장기적으로는 점차 마이그레이션하는 추&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/161&quot; data-og-url=&quot;https://wooncloud.tistory.com/161&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VpXVk/hyZJGUmCNi/RcqugEKk2LCfzrkR0mw4rk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cObxJS/hyZJtPjkj2/nlgwwAGdcwDV3L7deuz8M1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/banP1m/hyZI8EVt3M/cKQQEdK6zdqJDrPXyQ7HMK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/161&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/161&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VpXVk/hyZJGUmCNi/RcqugEKk2LCfzrkR0mw4rk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cObxJS/hyZJtPjkj2/nlgwwAGdcwDV3L7deuz8M1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/banP1m/hyZI8EVt3M/cKQQEdK6zdqJDrPXyQ7HMK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 시작 - 세팅부터 CRUD API 까지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream API 등 중요한 기능이 도입된 버전장기적으로는 점차 마이그레이션하는 추&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/162&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wooncloud.tistory.com/162&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758460845857&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 - Service 계층&quot; data-og-description=&quot;이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream &quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/162&quot; data-og-url=&quot;https://wooncloud.tistory.com/162&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SrI4K/hyZJMtwTpu/s6f9qmAhpeN0KCNQ6vJv5k/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/bmrmcq/hyZJm3H0ib/Y6DyfmuRKrO3MUVDajK8J0/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/162&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/162&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SrI4K/hyZJMtwTpu/s6f9qmAhpeN0KCNQ6vJv5k/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/bmrmcq/hyZJm3H0ib/Y6DyfmuRKrO3MUVDajK8J0/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 - Service 계층&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/163&quot;&gt;https://wooncloud.tistory.com/163&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758460770051&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 - DTO (Data Transfer Object) 패턴&quot; data-og-description=&quot;https://wooncloud.tistory.com/161 Spring 기본 - Service 계층이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/163&quot; data-og-url=&quot;https://wooncloud.tistory.com/163&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kGJ1z/hyZJhaOgAZ/JoMWvvuS9slA0BUfNlU9f1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cZzVEJ/hyZJtuYT7L/KVs6HsgPCXwjv2qAcJ6Oak/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/163&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/163&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kGJ1z/hyZJhaOgAZ/JoMWvvuS9slA0BUfNlU9f1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cZzVEJ/hyZJtuYT7L/KVs6HsgPCXwjv2qAcJ6Oak/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 - DTO (Data Transfer Object) 패턴&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://wooncloud.tistory.com/161 Spring 기본 - Service 계층이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/165&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wooncloud.tistory.com/165&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758460782863&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 - 게시판 만들기&quot; data-og-description=&quot;https://wooncloud.tistory.com/161 Spring 기본 - DTO (Data Transfer Object) 패턴https://wooncloud.tistory.com/161 Spring 기본 - Service 계층이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 &quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/165&quot; data-og-url=&quot;https://wooncloud.tistory.com/165&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k3uXB/hyZJG09CDT/042ok3hkZydfo3kHsCRcnk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/E3DQk/hyZJotFPeJ/N6n9jTsJkBPlayOA8NMvSK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/KvNjf/hyZJD4qbIr/BysAVSMPOpgDiNvdkC9fy0/img.png?width=400&amp;amp;height=816&amp;amp;face=0_0_400_816&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/165&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/165&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k3uXB/hyZJG09CDT/042ok3hkZydfo3kHsCRcnk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/E3DQk/hyZJotFPeJ/N6n9jTsJkBPlayOA8NMvSK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/KvNjf/hyZJD4qbIr/BysAVSMPOpgDiNvdkC9fy0/img.png?width=400&amp;amp;height=816&amp;amp;face=0_0_400_816');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 - 게시판 만들기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://wooncloud.tistory.com/161 Spring 기본 - DTO (Data Transfer Object) 패턴https://wooncloud.tistory.com/161 Spring 기본 - Service 계층이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I4w6T/btsQHRyaYfB/k6UfELGSo0MKlu2Tz0LM81/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I4w6T/btsQHRyaYfB/k6UfELGSo0MKlu2Tz0LM81/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I4w6T/btsQHRyaYfB/k6UfELGSo0MKlu2Tz0LM81/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI4w6T%2FbtsQHRyaYfB%2Fk6UfELGSo0MKlu2Tz0LM81%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;120&quot; height=&quot;120&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 인증 시스템 만들기&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT가 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/166&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wooncloud.tistory.com/166&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758465213921&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;JWT (JSON Web Token) 설명&quot; data-og-description=&quot;JWT 기본 구조JWT는 점(.)으로 구분된 3개 부분으로 이루어져 있다.xxxxx.yyyyy.zzzzz각 부분은 Base64URL로 인코딩되어 있다.1. Header (헤더)역할: 토큰의 타입과 서명 알고리즘 정보{ &amp;quot;alg&amp;quot;: &amp;quot;HS256&amp;quot;, &amp;quot;typ&amp;quot;: &amp;quot;JWT&amp;quot;}a&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/166&quot; data-og-url=&quot;https://wooncloud.tistory.com/166&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/blGQb1/hyZJRuhdvp/uwf5wM8uqoQ8sE7tsK6ae0/img.jpg?width=800&amp;amp;height=391&amp;amp;face=0_0_800_391,https://scrap.kakaocdn.net/dn/bMfDTj/hyZJ4NURx0/W4qNcglQU1cNf4Nq2k7pK1/img.jpg?width=800&amp;amp;height=391&amp;amp;face=0_0_800_391,https://scrap.kakaocdn.net/dn/dDnP4I/hyZJi750j4/dBYDGu6shdGUVBbxKpWrP1/img.png?width=1331&amp;amp;height=916&amp;amp;face=0_0_1331_916&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/166&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/166&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/blGQb1/hyZJRuhdvp/uwf5wM8uqoQ8sE7tsK6ae0/img.jpg?width=800&amp;amp;height=391&amp;amp;face=0_0_800_391,https://scrap.kakaocdn.net/dn/bMfDTj/hyZJ4NURx0/W4qNcglQU1cNf4Nq2k7pK1/img.jpg?width=800&amp;amp;height=391&amp;amp;face=0_0_800_391,https://scrap.kakaocdn.net/dn/dDnP4I/hyZJi750j4/dBYDGu6shdGUVBbxKpWrP1/img.png?width=1331&amp;amp;height=916&amp;amp;face=0_0_1331_916');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JWT (JSON Web Token) 설명&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JWT 기본 구조JWT는 점(.)으로 구분된 3개 부분으로 이루어져 있다.xxxxx.yyyyy.zzzzz각 부분은 Base64URL로 인코딩되어 있다.1. Header (헤더)역할: 토큰의 타입과 서명 알고리즘 정보{ &quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot;}a&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT에 대한 정보는 여기에 적어놨다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 의존성 추가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JWT 관련&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. io.jsonwebtoken:jjwt-api:0.11.5&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JWT(JSON Web Token) 처리를 위한 핵심 API&lt;/li&gt;
&lt;li&gt;JWT 토큰 생성, 파싱, 검증을 위한 인터페이스와 클래스들을 제공&lt;/li&gt;
&lt;li&gt;실제 구현체는 포함하지 않고 API만 제공하는 추상화 레이어&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. io.jsonwebtoken:jjwt-impl:0.11.5 (runtimeOnly)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JJWT API의 실제 구현체&lt;/li&gt;
&lt;li&gt;JWT 토큰의 실제 생성, 파싱, 서명 검증 로직이 포함&lt;/li&gt;
&lt;li&gt;runtimeOnly로 설정되어 컴파일 시점에는 필요하지 않고 런타임에만 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. io.jsonwebtoken:jjwt-jackson:0.11.5 (runtimeOnly)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JWT 페이로드의 JSON 직렬화/역직렬화를 위한 Jackson 바인딩&lt;/li&gt;
&lt;li&gt;JWT 클레임을 JSON으로 변환하거나 JSON에서 객체로 변환할 때 사용&lt;/li&gt;
&lt;li&gt;마찬가지로 런타임에만 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Jwts (JJWT 라이브러리)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JSON Web Token을 생성/파싱하는 Java 라이브러리입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공식 GitHub:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/jwtk/jjwt&quot;&gt;https://github.com/jwtk/jjwt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;API 문서:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://javadoc.io/doc/io.jsonwebtoken/jjwt-api/latest/index.html&quot;&gt;https://javadoc.io/doc/io.jsonwebtoken/jjwt-api/latest/index.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Security 관련&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. org.springframework.boot:spring-boot-starter-security&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot의 보안 기능을 위한 스타터 패키지&lt;/li&gt;
&lt;li&gt;인증(Authentication), 인가(Authorization), CSRF 보호 등 웹 보안 기능 제공&lt;/li&gt;
&lt;li&gt;기본적인 로그인 폼, 세션 관리, 보안 필터 체인 등이 자동 구성됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. org.springframework.security:spring-security-crypto&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;암호화 및 해싱 기능을 제공하는 Spring Security 모듈&lt;/li&gt;
&lt;li&gt;비밀번호 해싱(BCrypt, SCrypt, Pbkdf2 등), 암호화, 키 생성 등의 기능&lt;/li&gt;
&lt;li&gt;주로 사용자 비밀번호를 안전하게 저장하기 위한 해싱에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758460964945&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    // 기존 의존성들...
    
    // JWT 관련
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
    
    // Spring Security
    implementation 'org.springframework.boot:spring-boot-starter-security'
    
    // 비밀번호 암호화
    implementation 'org.springframework.security:spring-security-crypto'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 지금은 이렇게 의존성이 구성되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1758461001437&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    // Spring Boot Core - 웹 애플리케이션 개발 기본 설정
	implementation 'org.springframework.boot:spring-boot-starter-web'          // REST API, MVC
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'   // 템플릿 엔진
	developmentOnly 'org.springframework.boot:spring-boot-devtools'           // 개발 도구 (Hot Reload)

    // Database - 데이터베이스 연동 및 ORM
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'    // JPA, Hibernate ORM
    runtimeOnly 'org.postgresql:postgresql'                                  // PostgreSQL 드라이버

    // Object Mapping - 객체 변환
    implementation 'org.modelmapper:modelmapper:3.1.1'                       // DTO &amp;harr; Entity 변환

    // Security - 인증 및 보안
    implementation 'org.springframework.boot:spring-boot-starter-security'   // Spring Security
    implementation 'org.springframework.security:spring-security-crypto'     // 비밀번호 암호화

    // JWT - JSON Web Token 인증
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'                         // JWT API
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'                          // JWT 구현체
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'                       // JWT JSON 처리

    // Testing - 테스트 프레임워크
	testImplementation 'org.springframework.boot:spring-boot-starter-test'    // Spring Boot 테스트
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'             // JUnit 플랫폼
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;User Entity 수정&lt;/h2&gt;
&lt;pre id=&quot;code_1758461317174&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.users.entity;

import jakarta.persistence.*;

import java.time.LocalDateTime;

@Entity
@Table(name = &quot;users&quot;)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false, unique = true)
    private String userId; // 추가

    @Column(nullable = false)
    private String password; // 추가

    @Enumerated(EnumType.STRING)
    private UserRole role; // 추가

    @Column(name = &quot;created_at&quot;)
    private LocalDateTime createdAt;

    @Column(name = &quot;updated_at&quot;)
    private LocalDateTime updatedAt;

    public User() {
    }

    public User(String name, String email, String userId, String password) {
        this.name = name;
        this.email = email;
        this.userId = userId;
        this.password = password;
        this.role = UserRole.USER;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    // getter, setter...
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1758461392958&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.entity;

public enum Role {
    USER, ADMIN
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 관련 DTO&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LoginRequestDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758461432924&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class LoginRequestDto {
    private String userId;
    private String password;
    
    // 기본 생성자
    public LoginRequestDto() {}
    
    // 생성자
    public LoginRequestDto(String userId, String password) {
        this.userId = userId;
        this.password = password;
    }
    
    // getter, setter
    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RegisterRequestDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758461439833&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class RegisterRequestDto {
    private String name;
    private String email;
    private String userId;
    private String password;
    
    // 기본 생성자
    public RegisterRequestDto() {}
    
    // 생성자, getter, setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AuthResponseDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758461447423&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class AuthResponseDto {
    private String token;
    private String userId;
    private String name;
    private String message;
    
    // 생성자
    public AuthResponseDto(String token, String userId, String name, String message) {
        this.token = token;
        this.userId = userId;
        this.name = name;
        this.message = message;
    }
    
    // getter, setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 유틸리티 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JwtUtil.java&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;util&amp;nbsp;패키지를&amp;nbsp;만들고&amp;nbsp;JwtUtil.java&amp;nbsp;생성&lt;/p&gt;
&lt;pre id=&quot;code_1758461494666&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class JwtUtil {
    
    @Value(&quot;${jwt.secret-key}&quot;)
    private String secretKey;
    
    @Value(&quot;${jwt.expiration-time}&quot;)
    private long expirationTime;
    
    private Key getSigningKey() {
        return Keys.hmacShaKeyFor(secretKey.getBytes());
    }
    
    // JWT 토큰 생성
    public String generateToken(String userId, String name) {
        return Jwts.builder()
                .setSubject(userId)
                .claim(&quot;name&quot;, name)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expirationTime))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }
    
    // JWT 토큰에서 사용자 ID 추출
    public String getUserIdFromToken(String token) {
        return getClaimsFromToken(token).getSubject();
    }
    
    // JWT 토큰에서 이름 추출
    public String getNameFromToken(String token) {
        return getClaimsFromToken(token).get(&quot;name&quot;, String.class);
    }
    
    // JWT 토큰 유효성 검증
    public boolean isTokenValid(String token) {
        try {
            getClaimsFromToken(token);
            return true;
        } catch (JwtException e) {
            return false;
        }
    }
    
    // JWT 토큰이 만료되었는지 확인
    public boolean isTokenExpired(String token) {
        return getClaimsFromToken(token).getExpiration().before(new Date());
    }
    
    private Claims getClaimsFromToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;application.properties에 JWT 관련 키 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 키는 제가 대충 아무글자 막 넣어서 만들었는데, 다른 키로 직접 만드시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 사이트에서 256bits로 만들면 적당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jwtsecrets.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jwtsecrets.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758464354496&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JWT Secret Free Key Generator | Secure JWT Tokens&quot; data-og-description=&quot;Generate secure JWT secret keys with our free online tool. Create strong, random keys for your JWT tokens with customizable length and security options.&quot; data-og-host=&quot;jwtsecrets.com&quot; data-og-source-url=&quot;https://jwtsecrets.com/&quot; data-og-url=&quot;https://jwtsecrets.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mjGGb/hyZJQoAMWq/P1xIj23yECGBGk8LTkVsQ1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b4ObIy/hyZJY70tUY/DdTO4fSt3b32bueWEE2MJ0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bDP83b/hyZJISerT7/CkyDMoowKeKLG93ubVUwrk/img.jpg?width=738&amp;amp;height=466&amp;amp;face=0_0_738_466&quot;&gt;&lt;a href=&quot;https://jwtsecrets.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jwtsecrets.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mjGGb/hyZJQoAMWq/P1xIj23yECGBGk8LTkVsQ1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b4ObIy/hyZJY70tUY/DdTO4fSt3b32bueWEE2MJ0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bDP83b/hyZJISerT7/CkyDMoowKeKLG93ubVUwrk/img.jpg?width=738&amp;amp;height=466&amp;amp;face=0_0_738_466');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JWT Secret Free Key Generator | Secure JWT Tokens&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Generate secure JWT secret keys with our free online tool. Create strong, random keys for your JWT tokens with customizable length and security options.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jwtsecrets.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1758461610498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# JWT 설정
jwt.secret-key=mySecretKeyForJWTTokenGenerationAndValidation1234567890
jwt.expiration-time=86400000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JwtUtil 주요 메소드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;generateToken(): 로그인 성공시 JWT 생성&lt;/li&gt;
&lt;li&gt;getUserIdFromToken(): 토큰에서 사용자 ID 추출&lt;/li&gt;
&lt;li&gt;isTokenValid(): 토큰 유효성 검증 (서명, 만료시간)&lt;/li&gt;
&lt;li&gt;getClaimsFromToken():&amp;nbsp;토큰&amp;nbsp;파싱해서&amp;nbsp;정보&amp;nbsp;추출&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;getClaimsFromToken 함수 동작&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private Claims getClaimsFromToken(String token) {
    return Jwts.parserBuilder()           // JWT 파서 빌더 생성
            .setSigningKey(getSigningKey()) // 서명 검증용 키 설정
            .build()                        // 파서 완성
            .parseClaimsJws(token)          // 토큰 파싱 및 서명 검증
            .getBody();                     // Claims 객체 반환
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰을 파싱하고 서명을 검증한 후 Claims 객체를 반환합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;만료시간 체크&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;getClaimsFromToken(token).getExpiration().before(new Date());
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getExpiration(): JWT의 exp 클레임 (만료시간) 가져오기&lt;/li&gt;
&lt;li&gt;before(new Date()): 만료시간이 현재시간보다 이전인지 확인&lt;/li&gt;
&lt;li&gt;true면 만료됨, false면 아직 유효함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;HMAC 키 생성&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Keys.hmacShaKeyFor(secretKey.getBytes());
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HMAC-SHA256 알고리즘용 암호화 키 생성&lt;/li&gt;
&lt;li&gt;문자열 시크릿키를 바이트 배열로 변환 후 Key 객체 생성&lt;/li&gt;
&lt;li&gt;JWT 서명 생성/검증에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;@Value 어노테이션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Value 어노테이션은 application.properties 파일의 설정값을 Java 변수에 주입하는 Spring의 기능.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;${jwt.secret-key}: application.properties의 jwt.secret-key 값을 secretKey 변수에 주입&lt;/li&gt;
&lt;li&gt;${jwt.expiration-time}: 토큰 만료시간을 expirationTime 변수에 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정값을 외부화하면 환경별로 다른 값을 사용할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AuthService&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AuthService.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758462193748&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.service;

import com.example.demo_api.dto.*;
import com.example.demo_api.entity.User;
import com.example.demo_api.entity.UserRole;
import com.example.demo_api.repository.UserRepository;
import com.example.demo_api.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
public class AuthService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Autowired
    private JwtUtil jwtUtil;
    
    // 회원가입
    public AuthResponseDto register(RegisterRequestDto registerRequest) {
        // userId 중복 체크
        if (userRepository.existsByUserId(registerRequest.getUserId())) {
            return new AuthResponseDto(null, null, null, &quot;이미 존재하는 사용자 ID입니다.&quot;);
        }
        
        // 이메일 중복 체크
        if (userRepository.existsByEmail(registerRequest.getEmail())) {
            return new AuthResponseDto(null, null, null, &quot;이미 존재하는 이메일입니다.&quot;);
        }
        
        // 비밀번호 암호화
        String encodedPassword = passwordEncoder.encode(registerRequest.getPassword());
        
        // 사용자 생성
        User user = new User(
            registerRequest.getName(),
            registerRequest.getEmail(),
            registerRequest.getUserId(),
            encodedPassword
        );
        
        User savedUser = userRepository.save(user);
        
        // JWT 토큰 생성
        String token = jwtUtil.generateToken(savedUser.getUserId(), savedUser.getName());
        
        return new AuthResponseDto(token, savedUser.getUserId(), savedUser.getName(), &quot;회원가입이 완료되었습니다.&quot;);
    }
    
    // 로그인
    public AuthResponseDto login(LoginRequestDto loginRequest) {
        // 사용자 조회
        User user = userRepository.findByUserId(loginRequest.getUserId()).orElse(null);
        if (user == null) {
            return new AuthResponseDto(null, null, null, &quot;존재하지 않는 사용자입니다.&quot;);
        }
        
        // 비밀번호 검증
        if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
            return new AuthResponseDto(null, null, null, &quot;비밀번호가 일치하지 않습니다.&quot;);
        }
        
        // JWT 토큰 생성
        String token = jwtUtil.generateToken(user.getUserId(), user.getName());
        
        return new AuthResponseDto(token, user.getUserId(), user.getName(), &quot;로그인 성공&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UserRepository 수정&lt;/h2&gt;
&lt;pre id=&quot;code_1758462218221&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// UserRepository.java에 추가할 메소드들

// userId로 사용자 조회
Optional&amp;lt;User&amp;gt; findByUserId(String userId);

// userId 중복 체크
boolean existsByUserId(String userId);

// 이메일 중복 체크  
boolean existsByEmail(String email);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AuthController&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AuthController.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758462252196&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.controller;

import com.example.demo_api.dto.*;
import com.example.demo_api.service.AuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(&quot;/api/auth&quot;)
public class AuthController {
    
    @Autowired
    private AuthService authService;
    
    // 회원가입
    @PostMapping(&quot;/register&quot;)
    public AuthResponseDto register(@RequestBody RegisterRequestDto registerRequest) {
        return authService.register(registerRequest);
    }
    
    // 로그인
    @PostMapping(&quot;/login&quot;)
    public AuthResponseDto login(@RequestBody LoginRequestDto loginRequest) {
        return authService.login(loginRequest);
    }
    
    // 토큰 유효성 검증 (테스트용)
    @GetMapping(&quot;/validate&quot;)
    public String validateToken(@RequestHeader(&quot;Authorization&quot;) String authHeader) {
        if (authHeader != null &amp;amp;&amp;amp; authHeader.startsWith(&quot;Bearer &quot;)) {
            String token = authHeader.substring(7);
            // JwtUtil로 토큰 검증 로직 추가 예정
            return &quot;Token validation endpoint&quot;;
        }
        return &quot;Invalid token format&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring&amp;nbsp;Security&amp;nbsp;설정&lt;/h2&gt;
&lt;pre id=&quot;code_1758465752831&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -&amp;gt; csrf.disable())
            .sessionManagement(session -&amp;gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -&amp;gt; auth
                .requestMatchers(&quot;/api/auth/**&quot;).permitAll()
                .requestMatchers(&quot;/api/users/**&quot;, &quot;/api/posts/**&quot;, &quot;/api/comments/**&quot;).permitAll()
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config&amp;nbsp;패키지를&amp;nbsp;만들고&amp;nbsp;SecurityConfig.java&amp;nbsp;생성&lt;/p&gt;
&lt;pre id=&quot;code_1758465710533&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http.csrf(csrf -&amp;gt; csrf.disable())&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CSRF 비활성화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT를 사용하는 Stateless 환경에서는 CSRF 공격에 대한 보호가 필요 없어 비활성화&lt;br /&gt;SessionCreationPolicy.STATELESS: 서버에서 세션을 생성하지 않고 모든 요청을 독립적으로 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;차후 프론트엔드와 연동 시 CORS 설정이 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1758465693047&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;http.cors(cors -&amp;gt; cors.configurationSource(corsConfigurationSource()))&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JwtAuthenticationFilter&lt;/h2&gt;
&lt;pre id=&quot;code_1758462340114&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.config;

import com.example.demo_api.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.ArrayList;

/**
 * JWT 토큰을 검증하여 사용자 인증을 처리하는 필터
 * 모든 HTTP 요청에 대해 한 번씩 실행됨
 */
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    /**
     * 요청마다 실행되는 필터 메소드
     * JWT 토큰을 검증하고 인증 정보를 SecurityContext에 설정
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
                                  FilterChain filterChain) throws ServletException, IOException {
        
        // HTTP 헤더에서 Authorization 값을 가져옴 (Bearer 토큰 형식)
        String authHeader = request.getHeader(&quot;Authorization&quot;);
        
        // Authorization 헤더가 존재하고 &quot;Bearer &quot;로 시작하는지 확인
        if (authHeader != null &amp;amp;&amp;amp; authHeader.startsWith(&quot;Bearer &quot;)) {
            // &quot;Bearer &quot; 부분을 제거하고 실제 JWT 토큰만 추출
            String token = authHeader.substring(7);
            
            // JWT 토큰이 유효하고 만료되지 않았는지 검증
            if (jwtUtil.isTokenValid(token) &amp;amp;&amp;amp; !jwtUtil.isTokenExpired(token)) {
                // 토큰에서 사용자 ID 추출
                String userId = jwtUtil.getUserIdFromToken(token);
                
                // Spring Security 인증 객체 생성 (권한은 빈 리스트로 설정)
                UsernamePasswordAuthenticationToken authentication = 
                    new UsernamePasswordAuthenticationToken(userId, null, new ArrayList&amp;lt;&amp;gt;());
                
                // SecurityContext에 인증 정보 저장 (이후 Controller에서 사용 가능)
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        
        // 다음 필터로 요청을 전달 (필터 체인 계속 진행)
        filterChain.doFilter(request, response);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 헤더에서 JWT 토큰 추출&lt;/li&gt;
&lt;li&gt;토큰 유효성 검증&lt;/li&gt;
&lt;li&gt;토큰에서 사용자 정보 추출&lt;/li&gt;
&lt;li&gt;Spring Security 인증 객체 생성&lt;/li&gt;
&lt;li&gt;SecurityContext에 인증 정보 저장&lt;/li&gt;
&lt;li&gt;다음 필터로 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Security 필터 체인에서의 동작 순서&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요청 들어옴 &amp;rarr; JwtAuthenticationFilter 실행&lt;/li&gt;
&lt;li&gt;JWT 토큰 검증 &amp;rarr; 유효하면 SecurityContext에 인증 정보 저장&lt;/li&gt;
&lt;li&gt;다음 필터로 진행 &amp;rarr; Spring Security의 다른 필터들 실행&lt;/li&gt;
&lt;li&gt;Controller 도달 &amp;rarr; Authentication 객체로 사용자 정보 접근 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 필터는 OncePerRequestFilter를 상속받아 요청당 한 번만 실행된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SecurityConfig에 JWT 필터를 추가&lt;/h2&gt;
&lt;pre id=&quot;code_1758462385758&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -&amp;gt; csrf.disable())
            .sessionManagement(session -&amp;gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -&amp;gt; auth
                .requestMatchers(&quot;/api/auth/**&quot;).permitAll()  // 인증 API는 누구나 접근
                .requestMatchers(&quot;GET&quot;, &quot;/api/posts/**&quot;).permitAll()  // 게시글 조회는 누구나
                .requestMatchers(&quot;GET&quot;, &quot;/api/comments/**&quot;).permitAll()  // 댓글 조회는 누구나
                .requestMatchers(&quot;/api/users/**&quot;).permitAll()  // 임시로 허용
                .anyRequest().authenticated()  // 나머지는 인증 필요
            )
            // JWT 필터를 UsernamePasswordAuthenticationFilter 앞에 추가
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JWT 필터를 필터 체인에 추가&lt;/li&gt;
&lt;li&gt;GET 요청은 인증 없이 허용 (조회만 가능)&lt;/li&gt;
&lt;li&gt;POST, PUT, DELETE는 인증 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Controller에서 인증된 사용자 정보 가져오기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PostController 수정&lt;/h3&gt;
&lt;pre id=&quot;code_1758462460284&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping
public PostDto createPost(@RequestBody PostCreateDto postCreateDto, 
                         Authentication authentication) {
    String userId = authentication.getName(); // JWT의 userId (문자열)
    return postService.createPost(postCreateDto, userId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1758462426059&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public PostDto createPost(PostCreateDto postCreateDto, String userId) {
    // Service에서 userId로 User 조회
    User author = userRepository.findByUserIdAndDeletedAtIsNull(userId).orElse(null);
    if (author == null) {
        return null; // 사용자가 존재하지 않음
    }
    
    Post post = new Post(postCreateDto.getTitle(), postCreateDto.getContent(), author);
    Post savedPost = postRepository.save(post);
    return convertToDto(savedPost);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CommentService도&amp;nbsp;동일하게&amp;nbsp;수정&lt;/h3&gt;
&lt;pre id=&quot;code_1758462437415&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public CommentDto createComment(CommentCreateDto commentCreateDto, String userId) {
    User author = userRepository.findByUserIdAndDeletedAtIsNull(userId).orElse(null);
    Post post = postRepository.findByIdAndNotDeleted(commentCreateDto.getPostId()).orElse(null);
    
    if (author == null || post == null) {
        return null;
    }
    
    Comment comment = new Comment(commentCreateDto.getContent(), author, post);
    Comment savedComment = commentRepository.save(comment);
    return convertToDto(savedComment);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 로그인해서 토큰 받기&lt;/h4&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8080/api/auth/login \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;userId&quot;: &quot;hong123&quot;, &quot;password&quot;: &quot;password123&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 받은 토큰으로 게시글 작성&lt;/h4&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8080/api/posts \
  -H &quot;Content-Type: application/json&quot; \
  -H &quot;Authorization: Bearer YOUR_JWT_TOKEN&quot; \
  -d '{&quot;title&quot;: &quot;인증된 사용자의 글&quot;, &quot;content&quot;: &quot;JWT 토큰으로 작성한 글입니다!&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 토큰 없이 게시글 작성 시도 (실패해야 함)&lt;/h4&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8080/api/posts \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;title&quot;: &quot;실패할 글&quot;, &quot;content&quot;: &quot;토큰이 없어서 실패&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 게시글 조회 (토큰 없어도 가능)&lt;/h4&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl http://localhost:8080/api/posts
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이정도면 게시판 완성인것 같습니다. 휴..&lt;/p&gt;</description>
      <category>Java</category>
      <category>JWT</category>
      <category>spring</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/167</guid>
      <comments>https://wooncloud.tistory.com/167#entry167comment</comments>
      <pubDate>Sun, 21 Sep 2025 23:47:02 +0900</pubDate>
    </item>
    <item>
      <title>JWT (JSON Web Token) 설명</title>
      <link>https://wooncloud.tistory.com/166</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOWKTR/btsQIyZoTww/bQUy9oXkuOKrtF42FMAAJK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOWKTR/btsQIyZoTww/bQUy9oXkuOKrtF42FMAAJK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOWKTR/btsQIyZoTww/bQUy9oXkuOKrtF42FMAAJK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOWKTR%2FbtsQIyZoTww%2FbQUy9oXkuOKrtF42FMAAJK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;626&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 기본 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는 &lt;b&gt;점(.)으로 구분된 3개 부분&lt;/b&gt;으로 이루어져 있다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;xxxxx.yyyyy.zzzzz
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 부분은 &lt;b&gt;Base64URL&lt;/b&gt;로 인코딩되어 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Header (헤더)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: 토큰의 타입과 서명 알고리즘 정보&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;alg&quot;: &quot;HS256&quot;,
  &quot;typ&quot;: &quot;JWT&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;alg: 서명에 사용된 알고리즘 (HS256, RS256, ES256 등)&lt;/li&gt;
&lt;li&gt;typ: 토큰 타입 (항상 &quot;JWT&quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Payload (페이로드)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: 실제 전달하고자 하는 정보(클레임 Claims)&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;sub&quot;: &quot;user123&quot;,
  &quot;name&quot;: &quot;김철수&quot;,
  &quot;role&quot;: &quot;admin&quot;,
  &quot;iat&quot;: 1516239022,
  &quot;exp&quot;: 1516242622
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;클레임 종류&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Registered Claims&lt;/b&gt;: 표준 클레임 (sub, exp, iat, iss 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Public Claims&lt;/b&gt;: 공개적으로 정의된 클레임&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Private Claims&lt;/b&gt;: 사용자 정의 클레임 (role, permissions 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Signature (서명)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;: 토큰의 무결성 검증&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;HMACSHA256(
  base64UrlEncode(header) + &quot;.&quot; +
  base64UrlEncode(payload),
  secret
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서명은 헤더와 페이로드를 합친 후 비밀키로 암호화한 값입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 JWT 예시&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.으로 구분되어 있는거 분해하면 아래로 나누어짐.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Header&lt;/b&gt;: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Payload&lt;/b&gt;: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Signature&lt;/b&gt;: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT의 특징&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Stateless: 서버에 세션 정보를 저장하지 않음&lt;/li&gt;
&lt;li&gt;Self-contained: 토큰 자체에 모든 필요한 정보가 포함&lt;/li&gt;
&lt;li&gt;URL-safe: URL, HTTP 헤더에서 안전하게 사용 가능&lt;/li&gt;
&lt;li&gt;투명성: Base64로 인코딩되어 있어 내용을 쉽게 확인 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;보안 고려사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;페이로드는 암호화되지 않음&lt;/b&gt; &amp;rarr; 민감한 정보 저장 금지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서명으로만 무결성 검증&lt;/b&gt; &amp;rarr; 변조는 방지하지만 내용은 노출됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;만료시간 설정 필수&lt;/b&gt; &amp;rarr; 토큰 탈취 시 피해 최소화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTPS 사용 권장&lt;/b&gt; &amp;rarr; 토큰 전송 시 암호화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT.io&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT.io는 JWT(JSON Web Token)를 위한 공식 웹사이트. Auth0에서 운영하는 무료 온라인 도구다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jwt.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.jwt.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758464955720&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JSON Web Tokens - jwt.io&quot; data-og-description=&quot;JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).&quot; data-og-host=&quot;www.jwt.io&quot; data-og-source-url=&quot;https://www.jwt.io/&quot; data-og-url=&quot;https://jwt.io&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qqmgx/hyZJsbMlvX/Ma11RUX5pxX1aSxIMtFn0k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/r0E7k/hyZJnVRqSM/poEilVYyuZg7KTR7Q3gp40/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://www.jwt.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jwt.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qqmgx/hyZJsbMlvX/Ma11RUX5pxX1aSxIMtFn0k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/r0E7k/hyZJnVRqSM/poEilVYyuZg7KTR7Q3gp40/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JSON Web Tokens - jwt.io&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jwt.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사이트에서 JWT Debugger랑 라이브러리와 JWT 표준 문서 등이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c18GRz/btsQGQ05QuN/30OMTdsYwDdYlKk5kkcQp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c18GRz/btsQGQ05QuN/30OMTdsYwDdYlKk5kkcQp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c18GRz/btsQGQ05QuN/30OMTdsYwDdYlKk5kkcQp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc18GRz%2FbtsQGQ05QuN%2F30OMTdsYwDdYlKk5kkcQp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1380&quot; height=&quot;866&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 JWT를 넣으면 Decoder를 통해 분해하거나 Encoder를 통해 인코딩을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bblpWl/btsQH6B2JYp/JK5mT5hqZFUK0ic6k77hj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bblpWl/btsQH6B2JYp/JK5mT5hqZFUK0ic6k77hj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bblpWl/btsQH6B2JYp/JK5mT5hqZFUK0ic6k77hj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbblpWl%2FbtsQH6B2JYp%2FJK5mT5hqZFUK0ic6k77hj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1331&quot; height=&quot;916&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리도 어떤것들이 있는지 볼 수 있다.&lt;/p&gt;</description>
      <category>개발 아카이브/개발 관련 지식</category>
      <category>JWT</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/166</guid>
      <comments>https://wooncloud.tistory.com/166#entry166comment</comments>
      <pubDate>Sun, 21 Sep 2025 23:33:20 +0900</pubDate>
    </item>
    <item>
      <title>Spring 기본 - 게시판 만들기</title>
      <link>https://wooncloud.tistory.com/165</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/161&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wooncloud.tistory.com/161&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758460817939&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 시작 - 세팅부터 CRUD API 까지&quot; data-og-description=&quot;1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream API 등 중요한 기능이 도입된 버전장기적으로는 점차 마이그레이션하는 추&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/161&quot; data-og-url=&quot;https://wooncloud.tistory.com/161&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VpXVk/hyZJGUmCNi/RcqugEKk2LCfzrkR0mw4rk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cObxJS/hyZJtPjkj2/nlgwwAGdcwDV3L7deuz8M1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/banP1m/hyZI8EVt3M/cKQQEdK6zdqJDrPXyQ7HMK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/161&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/161&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VpXVk/hyZJGUmCNi/RcqugEKk2LCfzrkR0mw4rk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cObxJS/hyZJtPjkj2/nlgwwAGdcwDV3L7deuz8M1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/banP1m/hyZI8EVt3M/cKQQEdK6zdqJDrPXyQ7HMK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 시작 - 세팅부터 CRUD API 까지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream API 등 중요한 기능이 도입된 버전장기적으로는 점차 마이그레이션하는 추&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/162&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wooncloud.tistory.com/162&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758460828379&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 - Service 계층&quot; data-og-description=&quot;이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream &quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/162&quot; data-og-url=&quot;https://wooncloud.tistory.com/162&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SrI4K/hyZJMtwTpu/s6f9qmAhpeN0KCNQ6vJv5k/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/bmrmcq/hyZJm3H0ib/Y6DyfmuRKrO3MUVDajK8J0/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/162&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/162&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SrI4K/hyZJMtwTpu/s6f9qmAhpeN0KCNQ6vJv5k/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/bmrmcq/hyZJm3H0ib/Y6DyfmuRKrO3MUVDajK8J0/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 - Service 계층&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/163&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wooncloud.tistory.com/163&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758460838094&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 - DTO (Data Transfer Object) 패턴&quot; data-og-description=&quot;https://wooncloud.tistory.com/161 Spring 기본 - Service 계층이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/163&quot; data-og-url=&quot;https://wooncloud.tistory.com/163&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kGJ1z/hyZJhaOgAZ/JoMWvvuS9slA0BUfNlU9f1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cZzVEJ/hyZJtuYT7L/KVs6HsgPCXwjv2qAcJ6Oak/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/163&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/163&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kGJ1z/hyZJhaOgAZ/JoMWvvuS9slA0BUfNlU9f1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cZzVEJ/hyZJtuYT7L/KVs6HsgPCXwjv2qAcJ6Oak/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 - DTO (Data Transfer Object) 패턴&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://wooncloud.tistory.com/161 Spring 기본 - Service 계층이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmfwf6/btsQJ7fApTE/aufRismk9iK4ZkPok1nGDk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmfwf6/btsQJ7fApTE/aufRismk9iK4ZkPok1nGDk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmfwf6/btsQJ7fApTE/aufRismk9iK4ZkPok1nGDk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmfwf6%2FbtsQJ7fApTE%2FaufRismk9iK4ZkPok1nGDk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;120&quot; height=&quot;120&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 게시판 기능을 만들어볼까 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;게시판이고&amp;nbsp;글,&amp;nbsp;댓글&amp;nbsp;을&amp;nbsp;달&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;2.&amp;nbsp;스크롤&amp;nbsp;페이징&lt;br /&gt;3.&amp;nbsp;회원만&amp;nbsp;글과&amp;nbsp;댓글을&amp;nbsp;달&amp;nbsp;수&amp;nbsp;있음&lt;br /&gt;4.&amp;nbsp;조회는&amp;nbsp;비회원&amp;nbsp;누구나&amp;nbsp;가능&lt;br /&gt;5.&amp;nbsp;회원가입과&amp;nbsp;회원은&amp;nbsp;JWT&amp;nbsp;사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 기본 기능을 만들려고 하고, front를 따로 둔 순수 api 서버로서 역할을 하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 게시판 기능을 위해, Post, Comment 기능을 만들고자 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테이블 구성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;users 테이블에는 user_id와 password를 넣어 정말 계정으로서 역할을 할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;posts 테이블과 comments 테이블을 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJvgjD/btsQI11dRi9/9yc53eYfKQCeF9LhXsJHZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJvgjD/btsQI11dRi9/9yc53eYfKQCeF9LhXsJHZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJvgjD/btsQI11dRi9/9yc53eYfKQCeF9LhXsJHZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJvgjD%2FbtsQI11dRi9%2F9yc53eYfKQCeF9LhXsJHZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;816&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 테이블 구성은 entity로 만들고 하이버네이트로 테이블을 생성할 수 있도록 만들어 테스트 할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Post Entity&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Post Entity 생성한다. entity 패키지에 Post.java 생성&lt;/p&gt;
&lt;pre id=&quot;code_1758455785574&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.entity;

import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.List;

@Entity
@Table(name = &quot;posts&quot;)
public class Post {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String title;
    
    @Column(nullable = false, columnDefinition = &quot;TEXT&quot;)
    private String content;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;user_id&quot;, nullable = false)
    private User author;  // 작성자
    
    @OneToMany(mappedBy = &quot;post&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
    private List&amp;lt;Comment&amp;gt; comments;  // 댓글들
    
    @Column(name = &quot;created_at&quot;)
    private LocalDateTime createdAt;
    
    @Column(name = &quot;updated_at&quot;)
    private LocalDateTime updatedAt;
    
    @Column(name = &quot;deleted_at&quot;)
	private LocalDateTime deletedAt;  // null이면 삭제되지 않음, 값이 있으면 삭제됨
    
    // 기본 생성자
    public Post() {}
    
    // 생성자
    public Post(String title, String content, User author) {
        this.title = title;
        this.content = content;
        this.author = author;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    // getter, setter 추가
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter, setter는 알아서 만드시구요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 이전 내용에서 나오지 않은 관계 매핑이 있다.&lt;br /&gt;&lt;br /&gt;@ManyToOne&amp;nbsp;관계&amp;nbsp;설정&lt;/p&gt;
&lt;pre id=&quot;code_1758455983599&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = &quot;user_id&quot;, nullable = false)
private User author;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다대일 관계: 여러 게시글 &amp;rarr; 한 명의 작성자&lt;/li&gt;
&lt;li&gt;LAZY: 필요할 때만 User 정보를 로딩 (성능 최적화)&lt;/li&gt;
&lt;li&gt;user_id&amp;nbsp;외래키로&amp;nbsp;연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@OneToMany&amp;nbsp;관계&amp;nbsp;설정&lt;/p&gt;
&lt;pre id=&quot;code_1758456024556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@OneToMany(mappedBy = &quot;post&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
private List&amp;lt;Comment&amp;gt; comments;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일대다 관계: 한 게시글 &amp;rarr; 여러 댓글&lt;/li&gt;
&lt;li&gt;mappedBy = &quot;post&quot;: Comment 엔티티의 post 필드가 주인&lt;/li&gt;
&lt;li&gt;cascade = ALL: 게시글 삭제시 댓글도 함께 삭제&lt;/li&gt;
&lt;li&gt;orphanRemoval&amp;nbsp;=&amp;nbsp;true:&amp;nbsp;댓글이&amp;nbsp;게시글에서&amp;nbsp;제거되면&amp;nbsp;DB에서도&amp;nbsp;삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Post와 Comment는 deletedAt를 넣음으로서 soft delete 정책을 가져가려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실수로 삭제한 데이터를 복구할 수 있음&lt;/li&gt;
&lt;li&gt;삭제된 데이터도 통계나 분석에 활용 가능&lt;/li&gt;
&lt;li&gt;외래키 참조 무결성 문제 해결 (댓글이 있는 게시글을 삭제할 때)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Comment Entity&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Comment Entity 생성한다. entity 패키지에 Comment.java 생성.&lt;/p&gt;
&lt;pre id=&quot;code_1758456750346&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.entity;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = &quot;comments&quot;)
public class Comment {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, columnDefinition = &quot;TEXT&quot;)
    private String content;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;user_id&quot;, nullable = false)
    private User author;  // 댓글 작성자
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;post_id&quot;, nullable = false)
    private Post post;  // 어떤 게시글의 댓글인지
    
    @Column(name = &quot;created_at&quot;)
    private LocalDateTime createdAt;
    
    @Column(name = &quot;updated_at&quot;)
    private LocalDateTime updatedAt;
    
    @Column(name = &quot;deleted_at&quot;)
    private LocalDateTime deletedAt;  // Soft Delete
    
    // 기본 생성자
    public Comment() {}
    
    // 생성자
    public Comment(String content, User author, Post post) {
        this.content = content;
        this.author = author;
        this.post = post;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    // getter, setter..
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Comment는 아래와 같은 특징을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Post와 User 둘 다와 다대일 관계&lt;/li&gt;
&lt;li&gt;한 댓글은 하나의 게시글에만 속함&lt;/li&gt;
&lt;li&gt;한&amp;nbsp;댓글은&amp;nbsp;한&amp;nbsp;명의&amp;nbsp;작성자만&amp;nbsp;가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Repository 인터페이스 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 최대한 JPA의&amp;nbsp;기본&amp;nbsp;함수들을&amp;nbsp;최대한&amp;nbsp;활용하는&amp;nbsp;방향으로&amp;nbsp;Repository를 만들어보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA가 제공하는 쿼리 메소드 네이밍 규칙을 활용하면 별도의 쿼리를 작성하지 않아도 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PostRepository.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758456941828&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.repository;

import com.example.demo_api.entity.Post;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface PostRepository extends JpaRepository&amp;lt;Post, Long&amp;gt; {
    
    // === JPA 기본 함수들 ===
    // save() - 생성/수정
    // findById() - ID로 조회
    // findAll() - 전체 조회
    // deleteById() - 삭제 (실제로는 Soft Delete로 처리)
    // existsById() - 존재 여부 확인
    
    // === Soft Delete를 위한 최소한의 커스텀 쿼리만 ===
    @Query(&quot;SELECT p FROM Post p WHERE p.deletedAt IS NULL ORDER BY p.createdAt DESC&quot;)
    Page&amp;lt;Post&amp;gt; findAllNotDeleted(Pageable pageable);
    
    @Query(&quot;SELECT p FROM Post p WHERE p.id = :id AND p.deletedAt IS NULL&quot;)
    Optional&amp;lt;Post&amp;gt; findByIdAndNotDeleted(@Param(&quot;id&quot;) Long id);
    
    // === JPA 메소드 이름 규칙 활용 ===
    // 특정 사용자의 게시글 (deletedAt이 null인 것만)
    List&amp;lt;Post&amp;gt; findByAuthorIdAndDeletedAtIsNullOrderByCreatedAtDesc(Long authorId);
    
    // 제목으로 검색
    Page&amp;lt;Post&amp;gt; findByTitleContainingAndDeletedAtIsNullOrderByCreatedAtDesc(String keyword, Pageable pageable);
    
    // 존재 여부 확인 (삭제되지 않은 것만)
    boolean existsByIdAndDeletedAtIsNull(Long id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA 메소드 네이밍 규칙&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;findBy: SELECT 쿼리&lt;/li&gt;
&lt;li&gt;AuthorId: author.id 필드로 조건&lt;/li&gt;
&lt;li&gt;And: AND 조건&lt;/li&gt;
&lt;li&gt;DeletedAtIsNull: deletedAt IS NULL 조건&lt;/li&gt;
&lt;li&gt;OrderBy: 정렬&lt;/li&gt;
&lt;li&gt;CreatedAtDesc:&amp;nbsp;createdAt&amp;nbsp;내림차순&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 메소드 이름만으로도 복잡한 쿼리를 만들 수 있다. 개 신기함..&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CommentRepository.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758456957234&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.repository;

import com.example.demo_api.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface CommentRepository extends JpaRepository&amp;lt;Comment, Long&amp;gt; {
    
    // === JPA 기본 함수들 ===
    // save() - 생성/수정
    // findById() - ID로 조회  
    // deleteById() - 삭제 (실제로는 Soft Delete로 처리)
    
    // === JPA 메소드 이름 규칙만 사용 ===
    // 특정 게시글의 댓글들
    List&amp;lt;Comment&amp;gt; findByPostIdAndDeletedAtIsNullOrderByCreatedAtAsc(Long postId);
    
    // 특정 사용자의 댓글들
    List&amp;lt;Comment&amp;gt; findByAuthorIdAndDeletedAtIsNullOrderByCreatedAtDesc(Long authorId);
    
    // ID로 조회 (삭제되지 않은 것만)
    Optional&amp;lt;Comment&amp;gt; findByIdAndDeletedAtIsNull(Long id);
    
    // 댓글 개수
    long countByPostIdAndDeletedAtIsNull(Long postId);
    
    // 존재 여부 확인
    boolean existsByIdAndDeletedAtIsNull(Long id);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Page과&amp;nbsp;&amp;nbsp;Optional이 보이는데 간단히 설명을 하자면..&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Page&amp;lt;T&amp;gt;&amp;nbsp;(페이징&amp;nbsp;처리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data에서 제공하는 페이징 결과를 담는 컨테이너이다. Page&amp;lt;T&amp;gt; 안에 있는 함수들을 살펴보면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1758457092898&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Page&amp;lt;Post&amp;gt; posts = postRepository.findAllNotDeleted(pageable);

// Page가 제공하는 정보들
posts.getContent();          // 실제 데이터 (List&amp;lt;Post&amp;gt;)
posts.getTotalElements();    // 전체 데이터 개수
posts.getTotalPages();       // 전체 페이지 수
posts.getNumber();           // 현재 페이지 번호 (0부터 시작)
posts.getSize();             // 페이지 크기
posts.hasNext();             // 다음 페이지 존재 여부
posts.hasPrevious();         // 이전 페이지 존재 여부
posts.isFirst();             // 첫 번째 페이지인지
posts.isLast();              // 마지막 페이지인지&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;pre id=&quot;code_1758457103507&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Controller에서
@GetMapping(&quot;/posts&quot;)
public Page&amp;lt;PostDto&amp;gt; getPosts(@RequestParam(defaultValue = &quot;0&quot;) int page,
                             @RequestParam(defaultValue = &quot;10&quot;) int size) {
    Pageable pageable = PageRequest.of(page, size);
    return postService.getAllPosts(pageable);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;세상 많이 좋아졌다.. 라떼는 페이징 하나 만들라면.. 하루이틀 걸렸는ㄷ..&lt;/s&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Optional&amp;lt;T&amp;gt;&amp;nbsp;(null&amp;nbsp;안전성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거는 Java 8에서 제공되는 null 처리 컨테이너이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 null 체크를 위해 if문을 남발했는데, Optional을 사용하면 훨씬 안전하고 깔끔하게 처리할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1758457162008&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;Post&amp;gt; post = postRepository.findByIdAndNotDeleted(1L);

// Optional 메소드들
post.isPresent();           // 값이 있는지 확인
post.isEmpty();             // 값이 없는지 확인
post.get();                 // 값 가져오기 (위험함 - NoSuchElementException 가능)
post.orElse(null);          // 값이 없으면 기본값 반환
post.orElseThrow(() -&amp;gt; new PostNotFoundException()); // 값이 없으면 예외 발생&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;pre id=&quot;code_1758457168977&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Service에서
public PostDto getPost(Long id) {
    Post post = postRepository.findByIdAndNotDeleted(id)
                              .orElse(null);
    return post != null ? convertToDto(post) : null;
}

// 또는
public PostDto getPost(Long id) {
    return postRepository.findByIdAndNotDeleted(id)
                        .map(this::convertToDto)
                        .orElse(null);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optional을 사용하면 명시적으로 &quot;값이 없을 수 있음&quot;을 표현할 수 있고 NullPointerException를 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. DTO&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PostDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758457328609&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

import java.time.LocalDateTime;

public class PostDto {
    private Long id;
    private String title;
    private String content;
    private String authorName;
    private Long authorId;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private int commentCount; // 댓글 개수
    
    // 기본 생성자
    public PostDto() {}
    
    // 생성자
    public PostDto(Long id, String title, String content, String authorName, 
                   Long authorId, LocalDateTime createdAt, LocalDateTime updatedAt, 
                   int commentCount) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.authorName = authorName;
        this.authorId = authorId;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
        this.commentCount = commentCount;
    }
    
    // getter, setter..
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PostCreateDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758457348950&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class PostCreateDto {
    private String title;
    private String content;
    private Long authorId; // 나중에 JWT에서 가져올 예정
    
    // 생성자, getter, setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PostUpdateDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758457358073&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class PostUpdateDto {
    private String title;
    private String content;
    
    // 생성자, getter, setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CommentDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758457425254&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

import java.time.LocalDateTime;

public class CommentDto {
    private Long id;
    private String content;
    private String authorName;
    private Long authorId;
    private Long postId;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // 기본 생성자
    public CommentDto() {}
    
    // 생성자
    public CommentDto(Long id, String content, String authorName, Long authorId, 
                     Long postId, LocalDateTime createdAt, LocalDateTime updatedAt) {
        this.id = id;
        this.content = content;
        this.authorName = authorName;
        this.authorId = authorId;
        this.postId = postId;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
    }
    
    // getter, setter...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CommentCreateDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758457435039&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class CommentCreateDto {
    private String content;
    private Long postId;
    private Long authorId; // 나중에 JWT에서 가져올 예정
    
    // 생성자, getter, setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CommentUpdateDto.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758457439961&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class CommentUpdateDto {
    private String content;
    
    // 생성자, getter, setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Service&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PostService.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758457517081&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.service;

import com.example.demo_api.dto.*;
import com.example.demo_api.entity.Post;
import com.example.demo_api.entity.User;
import com.example.demo_api.repository.PostRepository;
import com.example.demo_api.repository.UserRepository;
import com.example.demo_api.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class PostService {
    
    @Autowired
    private PostRepository postRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private CommentRepository commentRepository;
    
    // Entity -&amp;gt; DTO 변환
    private PostDto convertToDto(Post post) {
        int commentCount = (int) commentRepository.countByPostIdAndDeletedAtIsNull(post.getId());
        return new PostDto(
            post.getId(),
            post.getTitle(),
            post.getContent(),
            post.getAuthor().getName(),
            post.getAuthor().getId(),
            post.getCreatedAt(),
            post.getUpdatedAt(),
            commentCount
        );
    }
    
    // 전체 게시글 조회 (페이징)
    public Page&amp;lt;PostDto&amp;gt; getAllPosts(Pageable pageable) {
        Page&amp;lt;Post&amp;gt; posts = postRepository.findAllNotDeleted(pageable);
        return posts.map(this::convertToDto);
    }
    
    // 게시글 생성
    public PostDto createPost(PostCreateDto postCreateDto) {
        User author = userRepository.findById(postCreateDto.getAuthorId()).orElse(null);
        if (author == null) {
            return null; // 사용자가 존재하지 않음
        }
        
        Post post = new Post(postCreateDto.getTitle(), postCreateDto.getContent(), author);
        Post savedPost = postRepository.save(post);
        return convertToDto(savedPost);
    }
    
    // 특정 게시글 조회
    public PostDto getPostById(Long id) {
        Post post = postRepository.findByIdAndNotDeleted(id).orElse(null);
        return post != null ? convertToDto(post) : null;
    }

    // 게시글 수정
    public PostDto updatePost(Long id, PostUpdateDto postUpdateDto) {
        Post post = postRepository.findByIdAndNotDeleted(id).orElse(null);
        if (post != null) {
            post.setTitle(postUpdateDto.getTitle());
            post.setContent(postUpdateDto.getContent());
            post.setUpdatedAt(LocalDateTime.now());
            Post savedPost = postRepository.save(post);
            return convertToDto(savedPost);
        }
        return null;
    }

    // 게시글 삭제 (Soft Delete)
    public boolean deletePost(Long id) {
        Post post = postRepository.findByIdAndNotDeleted(id).orElse(null);
        if (post != null) {
            post.setDeletedAt(LocalDateTime.now());
            postRepository.save(post);
            return true;
        }
        return false;
    }

    // 특정 사용자의 게시글들 조회
    public List&amp;lt;PostDto&amp;gt; getPostsByUserId(Long userId) {
        List&amp;lt;Post&amp;gt; posts = postRepository.findByAuthorIdAndDeletedAtIsNullOrderByCreatedAtDesc(userId);
        return posts.stream()
                    .map(this::convertToDto)
                    .collect(Collectors.toList());
    }

    // 제목으로 검색
    public Page&amp;lt;PostDto&amp;gt; searchPostsByTitle(String keyword, Pageable pageable) {
        Page&amp;lt;Post&amp;gt; posts = postRepository.findByTitleContainingAndDeletedAtIsNullOrderByCreatedAtDesc(keyword, pageable);
        return posts.map(this::convertToDto);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CommentService.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758458475754&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.service;

import com.example.demo_api.dto.*;
import com.example.demo_api.entity.Comment;
import com.example.demo_api.entity.Post;
import com.example.demo_api.entity.User;
import com.example.demo_api.repository.CommentRepository;
import com.example.demo_api.repository.PostRepository;
import com.example.demo_api.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class CommentService {
    
    @Autowired
    private CommentRepository commentRepository;
    
    @Autowired
    private PostRepository postRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    // Entity -&amp;gt; DTO 변환
    private CommentDto convertToDto(Comment comment) {
        return new CommentDto(
            comment.getId(),
            comment.getContent(),
            comment.getAuthor().getName(),
            comment.getAuthor().getId(),
            comment.getPost().getId(),
            comment.getCreatedAt(),
            comment.getUpdatedAt()
        );
    }
    
    // 특정 게시글의 댓글들 조회
    public List&amp;lt;CommentDto&amp;gt; getCommentsByPostId(Long postId) {
        List&amp;lt;Comment&amp;gt; comments = commentRepository.findByPostIdAndDeletedAtIsNullOrderByCreatedAtAsc(postId);
        return comments.stream()
                      .map(this::convertToDto)
                      .collect(Collectors.toList());
    }
    
    // 댓글 생성
    public CommentDto createComment(CommentCreateDto commentCreateDto) {
        User author = userRepository.findById(commentCreateDto.getAuthorId()).orElse(null);
        Post post = postRepository.findByIdAndNotDeleted(commentCreateDto.getPostId()).orElse(null);
        
        if (author == null || post == null) {
            return null; // 사용자나 게시글이 존재하지 않음
        }
        
        Comment comment = new Comment(commentCreateDto.getContent(), author, post);
        Comment savedComment = commentRepository.save(comment);
        return convertToDto(savedComment);
    }

    // 댓글 조회
    public CommentDto getCommentById(Long id) {
        Comment comment = commentRepository.findByIdAndDeletedAtIsNull(id).orElse(null);
        return comment != null ? convertToDto(comment) : null;
    }

    // 댓글 수정
    public CommentDto updateComment(Long id, CommentUpdateDto commentUpdateDto) {
        Comment comment = commentRepository.findByIdAndDeletedAtIsNull(id).orElse(null);
        if (comment != null) {
            comment.setContent(commentUpdateDto.getContent());
            comment.setUpdatedAt(LocalDateTime.now());
            Comment savedComment = commentRepository.save(comment);
            return convertToDto(savedComment);
        }
        return null;
    }

    // 댓글 삭제 (Soft Delete)
    public boolean deleteComment(Long id) {
        Comment comment = commentRepository.findByIdAndDeletedAtIsNull(id).orElse(null);
        if (comment != null) {
            comment.setDeletedAt(LocalDateTime.now());
            commentRepository.save(comment);
            return true;
        }
        return false;
    }

    // 특정 사용자의 댓글들 조회
    public List&amp;lt;CommentDto&amp;gt; getCommentsByUserId(Long userId) {
        List&amp;lt;Comment&amp;gt; comments = commentRepository.findByAuthorIdAndDeletedAtIsNullOrderByCreatedAtDesc(userId);
        return comments.stream()
                      .map(this::convertToDto)
                      .collect(Collectors.toList());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Controller&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful API 설계&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET /api/posts: 전체 조회&lt;/li&gt;
&lt;li&gt;GET /api/posts/{id}: 특정 게시글 조회&lt;/li&gt;
&lt;li&gt;POST /api/posts: 생성&lt;/li&gt;
&lt;li&gt;PUT /api/posts/{id}: 수정&lt;/li&gt;
&lt;li&gt;DELETE /api/posts/{id}: 삭제&lt;/li&gt;
&lt;li&gt;GET /api/posts/search: 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RequestParam 와 @PathVariable&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@PathVariable: URL 경로의 일부 (예: /posts/{id}의 id)&lt;/li&gt;
&lt;li&gt;@RequestParam: 쿼리 파라미터 (예: ?page=0&amp;amp;size=10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기타 참고사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본값 설정: defaultValue로 클라이언트가 값을 안 보내도 기본값 사용&lt;/li&gt;
&lt;li&gt;페이징&amp;nbsp;처리:&amp;nbsp;PageRequest.of()로&amp;nbsp;Pageable&amp;nbsp;객체&amp;nbsp;생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PostController.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758458529169&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.controller;

import com.example.demo_api.dto.*;
import com.example.demo_api.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping(&quot;/api/posts&quot;)
public class PostController {
    
    @Autowired
    private PostService postService;
    
    // 전체 게시글 조회 (페이징)
    @GetMapping
    public Page&amp;lt;PostDto&amp;gt; getAllPosts(@RequestParam(defaultValue = &quot;0&quot;) int page,
                                    @RequestParam(defaultValue = &quot;10&quot;) int size) {
        Pageable pageable = PageRequest.of(page, size);
        return postService.getAllPosts(pageable);
    }
    
    // 특정 게시글 조회
    @GetMapping(&quot;/{id}&quot;)
    public PostDto getPostById(@PathVariable Long id) {
        return postService.getPostById(id);
    }
    
    // 게시글 생성
    @PostMapping
    public PostDto createPost(@RequestBody PostCreateDto postCreateDto) {
        return postService.createPost(postCreateDto);
    }
    
    // 게시글 수정
    @PutMapping(&quot;/{id}&quot;)
    public PostDto updatePost(@PathVariable Long id, @RequestBody PostUpdateDto postUpdateDto) {
        return postService.updatePost(id, postUpdateDto);
    }
    
    // 게시글 삭제
    @DeleteMapping(&quot;/{id}&quot;)
    public String deletePost(@PathVariable Long id) {
        boolean deleted = postService.deletePost(id);
        return deleted ? &quot;게시글이 삭제되었습니다.&quot; : &quot;게시글을 찾을 수 없습니다.&quot;;
    }
    
    // 제목으로 검색
    @GetMapping(&quot;/search&quot;)
    public Page&amp;lt;PostDto&amp;gt; searchPosts(@RequestParam String keyword,
                                    @RequestParam(defaultValue = &quot;0&quot;) int page,
                                    @RequestParam(defaultValue = &quot;10&quot;) int size) {
        Pageable pageable = PageRequest.of(page, size);
        return postService.searchPostsByTitle(keyword, pageable);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CommentController.java&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계층적 URL 구조: /api/comments/post/{postId}로 특정 게시글의 댓글들 조회&lt;/li&gt;
&lt;li&gt;사용자별 댓글 조회: /api/comments/user/{userId}로 마이페이지 기능 지원&lt;/li&gt;
&lt;li&gt;일관된&amp;nbsp;응답&amp;nbsp;형태:&amp;nbsp;성공시&amp;nbsp;DTO&amp;nbsp;반환,&amp;nbsp;삭제시&amp;nbsp;메시지&amp;nbsp;반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758458523215&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.controller;

import com.example.demo_api.dto.*;
import com.example.demo_api.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping(&quot;/api/comments&quot;)
public class CommentController {
    
    @Autowired
    private CommentService commentService;
    
    // 특정 게시글의 댓글들 조회
    @GetMapping(&quot;/post/{postId}&quot;)
    public List&amp;lt;CommentDto&amp;gt; getCommentsByPostId(@PathVariable Long postId) {
        return commentService.getCommentsByPostId(postId);
    }
    
    // 특정 댓글 조회
    @GetMapping(&quot;/{id}&quot;)
    public CommentDto getCommentById(@PathVariable Long id) {
        return commentService.getCommentById(id);
    }
    
    // 댓글 생성
    @PostMapping
    public CommentDto createComment(@RequestBody CommentCreateDto commentCreateDto) {
        return commentService.createComment(commentCreateDto);
    }
    
    // 댓글 수정
    @PutMapping(&quot;/{id}&quot;)
    public CommentDto updateComment(@PathVariable Long id, @RequestBody CommentUpdateDto commentUpdateDto) {
        return commentService.updateComment(id, commentUpdateDto);
    }
    
    // 댓글 삭제
    @DeleteMapping(&quot;/{id}&quot;)
    public String deleteComment(@PathVariable Long id) {
        boolean deleted = commentService.deleteComment(id);
        return deleted ? &quot;댓글이 삭제되었습니다.&quot; : &quot;댓글을 찾을 수 없습니다.&quot;;
    }
    
    // 특정 사용자의 댓글들 조회
    @GetMapping(&quot;/user/{userId}&quot;)
    public List&amp;lt;CommentDto&amp;gt; getCommentsByUserId(@PathVariable Long userId) {
        return commentService.getCommentsByUserId(userId);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 테스트!!&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 사용자 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1758459688534&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8080/api/users \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;name&quot;: &quot;김철수&quot;, &quot;email&quot;: &quot;kim@example.com&quot;}'&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 게시글 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1758459680383&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8080/api/posts \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;title&quot;: &quot;첫 번째 게시글&quot;, &quot;content&quot;: &quot;안녕하세요!&quot;, &quot;authorId&quot;: 1}'&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 댓글 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1758459671407&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8080/api/comments \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;content&quot;: &quot;좋은 글이네요!&quot;, &quot;postId&quot;: 1, &quot;authorId&quot;: 1}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 게시글 목록 조회&lt;/h3&gt;
&lt;pre id=&quot;code_1758459728068&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X GET http://localhost:8080/api/posts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 특정 게시글의 댓글 조회&lt;/h3&gt;
&lt;pre id=&quot;code_1758459723370&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X GET http://localhost:8080/api/comments/post/1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 안된다면 에러로그 보고 뭐가 문제인지 잘 봐야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;휴.. 힘들다.. 다음은 회원 시스템!&lt;/p&gt;</description>
      <category>개발 아카이브/JAVA</category>
      <category>Java</category>
      <category>spring</category>
      <category>게시판</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/165</guid>
      <comments>https://wooncloud.tistory.com/165#entry165comment</comments>
      <pubDate>Sun, 21 Sep 2025 22:04:01 +0900</pubDate>
    </item>
    <item>
      <title>JAVA equals, hashCode 오버라이드시 instanceof와 getClass 차이</title>
      <link>https://wooncloud.tistory.com/164</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crkQvI/btsQJv143ek/7OFL59glWf5oCfnkkTdcZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crkQvI/btsQJv143ek/7OFL59glWf5oCfnkkTdcZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crkQvI/btsQJv143ek/7OFL59glWf5oCfnkkTdcZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrkQvI%2FbtsQJv143ek%2F7OFL59glWf5oCfnkkTdcZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;256&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텔리제이에서 equals()나 hashCode()를 생성할때 instanceof와 getClass 중에 어느걸 사용할꺼냐 선택하는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;instanceof 방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속 관계를 허용.&lt;/li&gt;
&lt;li&gt;부모 클래스와 자식 클래스 간의 비교가 가능.&lt;/li&gt;
&lt;li&gt;더 유연하지만 대칭성(symmetry) 원칙을 위반할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758434989960&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof MyClass)) return false;
    MyClass myClass = (MyClass) obj;
    // 필드 비교 로직
    return Objects.equals(name, myClass.name);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;getClass()&amp;nbsp;방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확히 같은 클래스인 경우에만 true를 반환.&lt;/li&gt;
&lt;li&gt;상속 관계에서도 부모와 자식은 다른 것으로 취급.&lt;/li&gt;
&lt;li&gt;equals 계약의 대칭성을 보장.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758435026735&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;
    MyClass myClass = (MyClass) obj;
    // 필드 비교 로직
    return Objects.equals(name, myClass.name);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 어떤 것을 선택해야 할까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;instanceof
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속 구조에서 부모-자식 간 비교가 필요한 경우&lt;/li&gt;
&lt;li&gt;인터페이스&amp;nbsp;기반&amp;nbsp;비교가&amp;nbsp;필요한&amp;nbsp;경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getClass()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엄격한 타입 체크가 필요한 경우&lt;/li&gt;
&lt;li&gt;equals 계약의 대칭성을 확실히 보장하고 싶은 경우&lt;/li&gt;
&lt;li&gt;일반적으로&amp;nbsp;권장되는&amp;nbsp;방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DTO 같은 데이터 클래스에서는 getClass() 방식을 쓰는 것이 좋다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO는 주로&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API 요청/응답 데이터&lt;/li&gt;
&lt;li&gt;데이터베이스 매핑&lt;/li&gt;
&lt;li&gt;계층&amp;nbsp;간&amp;nbsp;데이터&amp;nbsp;전송&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 용도로 사용되는데 정확한 타입 매칭이 중요하기 때문에, 부모가 같다고 같은 인스턴스라고 쳐버리면 안됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO는 보통 정확히 같은 구조의 데이터를 나타내므로, 클래스가 다르면 다른 것으로 취급하는 것이 맞다.&lt;/p&gt;
&lt;pre id=&quot;code_1758435171003&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDto {
    private String name;
    private int age;
    // getClass() 방식 사용
}

public class AdminDto extends UserDto {
    private String role;
    // 상속받았지만 UserDto와는 다른 객체로 취급
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1758435163524&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UserDto user = new UserDto(&quot;김철수&quot;, 25);
AdminDto admin = new AdminDto(&quot;김철수&quot;, 25, &quot;ADMIN&quot;);

// getClass() 방식: false (올바름)
// instanceof 방식: true (위험할 수 있음)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;instanceof 방식은 상속 구조에서 다형성을 활용하고 싶을 때 사용한다.&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.&amp;nbsp;추상&amp;nbsp;클래스/인터페이스&amp;nbsp;기반&amp;nbsp;설계&lt;/h4&gt;
&lt;pre id=&quot;code_1758435333883&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 도형 클래스 계층
public abstract class Shape {
    protected String color;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Shape)) return false;
        Shape shape = (Shape) obj;
        return Objects.equals(color, shape.color);
    }
}

public class Circle extends Shape {
    private double radius;
    // color가 같고 radius가 같으면 동일로 취급
}

public class Rectangle extends Shape {
    private double width, height;
    // color가 같고 width, height가 같으면 동일로 취급
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 같은 도형군(Shape)으로 취급하고 싶다면 instanceof를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.&amp;nbsp;전략&amp;nbsp;패턴에서의&amp;nbsp;활용&lt;/h4&gt;
&lt;pre id=&quot;code_1758435401699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface PaymentMethod {
    void pay(int amount);
    
    // 같은 결제 수단으로 취급
    default boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof PaymentMethod)) return false;
        // 구체적인 구현체가 달라도 같은 인터페이스면 비교 가능
        return isSamePaymentType(obj);
    }
}

public class CreditCard implements PaymentMethod { }
public class DebitCard implements PaymentMethod { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.&amp;nbsp;컬렉션/컨테이너&amp;nbsp;클래스&lt;/h4&gt;
&lt;pre id=&quot;code_1758435450443&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class Animal {
    protected String name;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Animal)) return false;
        Animal animal = (Animal) obj;
        return Objects.equals(name, animal.name);
    }
}

public class Dog extends Animal { }
public class Cat extends Animal { }

// 사용 예시
Dog dog = new Dog(&quot;멍멍이&quot;);
Cat cat = new Cat(&quot;멍멍이&quot;);  // 이름이 같은 고양이

// instanceof 방식: true (같은 동물로 취급)
// getClass() 방식: false (다른 종류의 동물)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;instanceof 선택 기준&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의미적으로 같은 범주로 취급하고 싶을 때&lt;/li&gt;
&lt;li&gt;다형성을 활용한 설계일 때&lt;/li&gt;
&lt;li&gt;인터페이스나 추상 클래스 기반 비교가 필요할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 보면 '비즈니스 로직상 어떻게 같은 것으로 취급할 것인가?' 를 생각해보면 좋을 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1758435681273&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// instanceof가 적합한 경우
public abstract class Vehicle {
    // 모든 차량을 동일한 기준으로 비교하고 싶을 때
}

// getClass()가 적합한 경우  
public class UserRequestDto {
    // 정확히 같은 DTO 타입이어야 할 때
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DTO는 데이터 구조가 정확해야 하므로 getClass()&lt;/li&gt;
&lt;li&gt;도메인 객체는 개념적 동일성이 중요하므로 instanceof&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 아카이브/JAVA</category>
      <category>equals</category>
      <category>getClass</category>
      <category>hashCode</category>
      <category>instanceof</category>
      <category>Java</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/164</guid>
      <comments>https://wooncloud.tistory.com/164#entry164comment</comments>
      <pubDate>Sun, 21 Sep 2025 15:23:10 +0900</pubDate>
    </item>
    <item>
      <title>Spring 기본 - DTO (Data Transfer Object) 패턴</title>
      <link>https://wooncloud.tistory.com/163</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/161&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wooncloud.tistory.com/161&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758460889404&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 시작 - 세팅부터 CRUD API 까지&quot; data-og-description=&quot;1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream API 등 중요한 기능이 도입된 버전장기적으로는 점차 마이그레이션하는 추&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/161&quot; data-og-url=&quot;https://wooncloud.tistory.com/161&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VpXVk/hyZJGUmCNi/RcqugEKk2LCfzrkR0mw4rk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cObxJS/hyZJtPjkj2/nlgwwAGdcwDV3L7deuz8M1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/banP1m/hyZI8EVt3M/cKQQEdK6zdqJDrPXyQ7HMK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/161&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/161&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VpXVk/hyZJGUmCNi/RcqugEKk2LCfzrkR0mw4rk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/cObxJS/hyZJtPjkj2/nlgwwAGdcwDV3L7deuz8M1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/banP1m/hyZI8EVt3M/cKQQEdK6zdqJDrPXyQ7HMK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 시작 - 세팅부터 CRUD API 까지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream API 등 중요한 기능이 도입된 버전장기적으로는 점차 마이그레이션하는 추&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/162&quot;&gt;https://wooncloud.tistory.com/162&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758358932896&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 - Service 계층&quot; data-og-description=&quot;이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream &quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/162&quot; data-og-url=&quot;https://wooncloud.tistory.com/162&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dxqtPL/hyZJxjGi7E/NakW04Pmab9VBkK1qYerGK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/Lhl9h/hyZJtuMDQP/81oAwGCJ7b63HVQuxZRYj1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/162&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/162&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dxqtPL/hyZJxjGi7E/NakW04Pmab9VBkK1qYerGK/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/Lhl9h/hyZJtuMDQP/81oAwGCJ7b63HVQuxZRYj1/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 - Service 계층&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 내용https://wooncloud.tistory.com/161 Spring 기본 시작 - 세팅부터 CRUD API 까지1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbG55/btsQH2TIM8X/5XexI1kfFMhE7TobQRLCFk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbG55/btsQH2TIM8X/5XexI1kfFMhE7TobQRLCFk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbG55/btsQH2TIM8X/5XexI1kfFMhE7TobQRLCFk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlbG55%2FbtsQH2TIM8X%2F5XexI1kfFMhE7TobQRLCFk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;120&quot; height=&quot;120&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DTO&amp;nbsp;(Data&amp;nbsp;Transfer&amp;nbsp;Object)&amp;nbsp;패턴&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 DTO가 필요할까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안: Entity의 모든 필드를 노출하지 않음&lt;/li&gt;
&lt;li&gt;성능: 필요한 데이터만 전송&lt;/li&gt;
&lt;li&gt;유연성: API 응답 형식을 자유롭게 조정&lt;/li&gt;
&lt;li&gt;분리:&amp;nbsp;Entity&amp;nbsp;변경이&amp;nbsp;API에&amp;nbsp;영향주지&amp;nbsp;않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. DTO 패키지와 클래스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/main/java/com/example/demo_api에 dto 패키지를 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. UserDto.java 클래스 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1758359010610&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class UserDto {
    private Long id;
    private String name;
    private String email;
    
    // 기본 생성자
    public UserDto() {}
    
    // 모든 필드 생성자
    public UserDto(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    
    // getter, setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. 요청용 DTO도 생성 - CreateUserRequest.java&lt;/h3&gt;
&lt;pre id=&quot;code_1758359029659&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.dto;

public class CreateUserRequest {
    private String name;
    private String email;
    
    // 생성자, getter, setter 동일하게 작성
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;3. DTO 변환 유틸리티 메소드 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserService에 변환 메소드들을 추가.&lt;/p&gt;
&lt;pre id=&quot;code_1758359067359&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// UserService.java에 추가
// Entity -&amp;gt; DTO 변환
private UserDto convertToDto(User user) {
    return new UserDto(user.getId(), user.getName(), user.getEmail());
}

// DTO -&amp;gt; Entity 변환  
private User convertToEntity(UserCreateDto userCreateDto) {
    return new User(userCreateDto.getName(), userCreateDto.getEmail());
}

// List&amp;lt;Entity&amp;gt; -&amp;gt; List&amp;lt;DTO&amp;gt; 변환
private List&amp;lt;UserDto&amp;gt; convertToDtoList(List&amp;lt;User&amp;gt; users) {
    return users.stream()
            .map(this::convertToDto)
            .collect(Collectors.toList());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 Entity를 DTO로 바꾸는게 너무 귀찮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 귀찮음을 모든 개발자들이 알고 있고, 그래서 이 귀찮음을 해결하기 위한 방법들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ModelMapper&lt;/li&gt;
&lt;li&gt;MapStruct&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. ModelMapper 사용&lt;/h3&gt;
&lt;pre id=&quot;code_1758359130961&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle에 추가
implementation 'org.modelmapper:modelmapper:3.1.1'&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1758359138835&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {
    
    @Autowired
    private ModelMapper modelMapper;
    
    // 변환이 이렇게 간단해짐!
    public UserDto createUser(UserCreateDto userCreateDto) {
        User user = modelMapper.map(userCreateDto, User.class);
        User savedUser = userRepository.save(user);
        return modelMapper.map(savedUser, UserDto.class);
    }
    
    public List&amp;lt;UserDto&amp;gt; getAllUsers() {
        return userRepository.findAll().stream()
                .map(user -&amp;gt; modelMapper.map(user, UserDto.class))
                .collect(Collectors.toList());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. MapStruct&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(컴파일 시점에 생성, 성능 좋음)&lt;/p&gt;
&lt;pre id=&quot;code_1758360740492&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Mapper(componentModel = &quot;spring&quot;)
public interface UserMapper {
    UserDto toDto(User user);
    User toEntity(UserCreateDto dto);
    List&amp;lt;UserDto&amp;gt; toDtoList(List&amp;lt;User&amp;gt; users);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-3. 생성자 기반 변환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(간단하지만, Entity와 DTO에 들어있는 데이터가 많아지면 많아지고 복잡해짐.)&lt;/p&gt;
&lt;pre id=&quot;code_1758360759556&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DTO에 생성자 추가
public UserDto(User user) {
    this.id = user.getId();
    this.name = user.getName();
    this.email = user.getEmail();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 ModelMapper 라이브러리를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Controller를 DTO 사용으로 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserController를 다음과 같이 수정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1758359169405&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api/users&quot;)
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public List&amp;lt;UserDto&amp;gt; getAllUsers() {  // UserDto 반환
        return userService.getAllUsers();
    }
    
    @PostMapping
    public UserDto createUser(@RequestBody UserCreateDto userCreateDto) {  // DTO 사용
        return userService.createUser(userCreateDto);
    }
    
    @GetMapping(&quot;/{id}&quot;)
    public UserDto getUserById(@PathVariable Long id) {  // UserDto 반환
        return userService.getUserById(id);
    }
    
    @PutMapping(&quot;/{id}&quot;)
    public UserDto updateUser(@PathVariable Long id, @RequestBody UserCreateDto userCreateDto) {
        return userService.updateUser(id, userCreateDto);
    }
    
    @DeleteMapping(&quot;/{id}&quot;)
    public String deleteUser(@PathVariable Long id) {
        // 삭제는 그대로
        boolean deleted = userService.deleteUser(id);
        return deleted ? &quot;사용자가 삭제되었습니다.&quot; : &quot;사용자를 찾을 수 없습니다.&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/JAVA</category>
      <category>DTO</category>
      <category>Java</category>
      <category>spring</category>
      <category>스프링</category>
      <category>자바</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/163</guid>
      <comments>https://wooncloud.tistory.com/163#entry163comment</comments>
      <pubDate>Sat, 20 Sep 2025 18:36:28 +0900</pubDate>
    </item>
    <item>
      <title>Spring 기본 - Service 계층</title>
      <link>https://wooncloud.tistory.com/162</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이전 내용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/161&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wooncloud.tistory.com/161&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758358657210&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring 기본 시작 - 세팅부터 CRUD API 까지&quot; data-og-description=&quot;1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream API 등 중요한 기능이 도입된 버전장기적으로는 점차 마이그레이션하는 추&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/161&quot; data-og-url=&quot;https://wooncloud.tistory.com/161&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lW3RD/hyZJnuzYRJ/xs2jSPRMu6s5dPaoQn8ogk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/qWEXy/hyZJGmnCBe/qZMZTdOgzfEIwXLBJ71Z8K/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/Qa0Nv/hyZJqY85oh/EG8ZxQ4VWmnKkgw25H96Mk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/161&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/161&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lW3RD/hyZJnuzYRJ/xs2jSPRMu6s5dPaoQn8ogk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/qWEXy/hyZJGmnCBe/qZMZTdOgzfEIwXLBJ71Z8K/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550,https://scrap.kakaocdn.net/dn/Qa0Nv/hyZJqY85oh/EG8ZxQ4VWmnKkgw25H96Mk/img.jpg?width=550&amp;amp;height=550&amp;amp;face=0_0_550_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring 기본 시작 - 세팅부터 CRUD API 까지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 자바 설치 (Java JDK 17 이상 설치)Java 8 (2014년 출시)여전히 많은 기업에서 레거시 시스템으로 사용 중람다 표현식, Stream API 등 중요한 기능이 도입된 버전장기적으로는 점차 마이그레이션하는 추&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VCpyE/btsQHx7AUDa/dtbzmCqICThJO6lG0EYBwk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VCpyE/btsQHx7AUDa/dtbzmCqICThJO6lG0EYBwk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VCpyE/btsQHx7AUDa/dtbzmCqICThJO6lG0EYBwk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVCpyE%2FbtsQHx7AUDa%2FdtbzmCqICThJO6lG0EYBwk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;120&quot; height=&quot;120&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Service 계층 추가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 Service 계층이 필요할까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Controller: HTTP 요청/응답 처리만 한다.&lt;/li&gt;
&lt;li&gt;Service: 실질적인 비즈니스 로직 처리를 service에서 모두 함.&lt;/li&gt;
&lt;li&gt;Repository: 데이터베이스 접근만 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Service 패키지와 클래스 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/main/java/com/example/demo_api에 service 패키지를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;UserService.java 클래스 생성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1758358372604&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo_api.service;

import com.example.demo_api.entity.User;
import com.example.demo_api.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    // 모든 사용자 조회
    public List&amp;lt;User&amp;gt; getAllUsers() {
        return userRepository.findAll();
    }
    
    // 사용자 생성
    public User createUser(User user) {
        // 여기에 비즈니스 로직 추가 가능
        // 예: 이메일 중복 체크, 데이터 검증 등
        return userRepository.save(user);
    }
    
    // ID로 사용자 조회
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    // 사용자 정보 수정
    public User updateUser(Long id, User userDetails) {
        User user = userRepository.findById(id).orElse(null);
        if (user != null) {
            user.setName(userDetails.getName());
            user.setEmail(userDetails.getEmail());
            return userRepository.save(user);
        }
        return null;
    }

    // 사용자 삭제
    public boolean deleteUser(Long id) {
        if (userRepository.existsById(id)) {
            userRepository.deleteById(id);
            return true;
        }
        return false;
    }

    // 사용자 존재 여부 확인
    public boolean existsById(Long id) {
        return userRepository.existsById(id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;UserController 수정 - Repository 대신 Service 사용&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1758358479627&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api/users&quot;)
public class UserController {
    
    @Autowired
    private UserService userService;  // Repository 대신 Service 사용
    
    @GetMapping
    public List&amp;lt;User&amp;gt; getAllUsers() {
        return userService.getAllUsers();
    }
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
    
    @GetMapping(&quot;/{id}&quot;)
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }
    
    // 이 부분들을 Service 사용으로 수정하세요
    @PutMapping(&quot;/{id}&quot;)
    public User updateUser(@PathVariable Long id, @RequestBody User userDetails) {
        return userService.updateUser(id, userDetails);
    }
    
    @DeleteMapping(&quot;/{id}&quot;)
    public String deleteUser(@PathVariable Long id) {
        boolean deleted = userService.deleteUser(id);
        if (deleted) {
            return &quot;사용자가 삭제되었습니다.&quot;;
        } else {
            return &quot;사용자를 찾을 수 없습니다.&quot;;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로서 아키텍처는 다음과 같이 동작하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Controller &amp;rarr; Service &amp;rarr; Repository &amp;rarr; Database&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service 계층의 장점으로 비즈니스 로직을 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 유저를 만들때 이메일 중복 체크를 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1758358601425&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// UserService의 createUser 메소드를 수정해보세요
public User createUser(User user) {
    // 이메일 중복 체크 로직 추가
    if (userRepository.existsByEmail(user.getEmail())) {
        throw new RuntimeException(&quot;이미 존재하는 이메일입니다: &quot; + user.getEmail());
    }
    return userRepository.save(user);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/JAVA</category>
      <category>Java</category>
      <category>spring</category>
      <category>스프링</category>
      <category>자바</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/162</guid>
      <comments>https://wooncloud.tistory.com/162#entry162comment</comments>
      <pubDate>Sat, 20 Sep 2025 18:00:36 +0900</pubDate>
    </item>
    <item>
      <title>Spring 기본 시작 - 세팅부터 CRUD API 까지</title>
      <link>https://wooncloud.tistory.com/161</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzV75s/btsQjcXdQbC/1MNBtboBoDp5dNiV5S4kz0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzV75s/btsQjcXdQbC/1MNBtboBoDp5dNiV5S4kz0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzV75s/btsQjcXdQbC/1MNBtboBoDp5dNiV5S4kz0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzV75s%2FbtsQjcXdQbC%2F1MNBtboBoDp5dNiV5S4kz0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;240&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 자바 설치 (Java JDK 17 이상 설치)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 (2014년 출시)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여전히 많은 기업에서 레거시 시스템으로 사용 중&lt;/li&gt;
&lt;li&gt;람다 표현식, Stream API 등 중요한 기능이 도입된 버전&lt;/li&gt;
&lt;li&gt;장기적으로는 점차 마이그레이션하는 추세&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 11 (2018년 출시)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 가장 널리 채택되고 있는 LTS(Long Term Support) 버전&lt;/li&gt;
&lt;li&gt;Oracle의 상업적 지원이 필요 없는 OpenJDK 기반으로 무료 사용 가능&lt;/li&gt;
&lt;li&gt;많은 새로운 프로젝트들이 Java 11을 기준으로 개발&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 17 (2021년 출시)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최신 LTS 버전으로 채택률이 빠르게 증가하고 있음&lt;/li&gt;
&lt;li&gt;Spring Boot 3.0+ 등 주요 프레임워크들이 Java 17을 최소 요구사항으로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/160&quot;&gt;https://wooncloud.tistory.com/160&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1756918387747&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java 17 다운로드 방법&quot; data-og-description=&quot;주요 Java 17 배포판1. Oracle JDK 17다운로드: https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html특징: Oracle의 공식 배포판라이선스: 개발/테스트는 무료, 상업적 사용 시 유료2. Eclipse Adoptium다&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/160&quot; data-og-url=&quot;https://wooncloud.tistory.com/160&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjFxuX/hyZGe5wmt9/AdRwQjSr42BlSAFqkaoopk/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bCQYeD/hyZGnalDpv/2iRUqUl9psijU7D6DoAv7K/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/160&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/160&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjFxuX/hyZGe5wmt9/AdRwQjSr42BlSAFqkaoopk/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bCQYeD/hyZGnalDpv/2iRUqUl9psijU7D6DoAv7K/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java 17 다운로드 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;주요 Java 17 배포판1. Oracle JDK 17다운로드: https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html특징: Oracle의 공식 배포판라이선스: 개발/테스트는 무료, 상업적 사용 시 유료2. Eclipse Adoptium다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Spring Boot 프로젝트 생성 (Gradle 버전)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://start.spring.io&quot;&gt;https://start.spring.io &lt;/a&gt;에서 다음과 같이 설정:&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Project: Gradle - Groovy (또는 Gradle - Kotlin)
Language: Java
Spring Boot: 3.5.5 (포스팅 시점 안정적인 버전)
Group: com.example
Artifact: demo-api
Name: demo-api
Package name: com.example.demoapi
Packaging: Jar
Java: 17&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dependencies&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Web&lt;/li&gt;
&lt;li&gt;Spring Boot DevTools&lt;/li&gt;
&lt;li&gt;Spring Data JPA&lt;/li&gt;
&lt;li&gt;PostgreSQL Driver &amp;larr; PostgreSQL 연결용&lt;/li&gt;
&lt;li&gt;Thymeleaf&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료하고 zip 파일 다운로드.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. IntelliJ에서 프로젝트 열기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ IDEA를 실행&lt;/li&gt;
&lt;li&gt;&quot;Open&quot; 버튼을 클릭 (또는 File &amp;gt; Open)&lt;/li&gt;
&lt;li&gt;다운로드한 demo-api.zip을 압축 해제한 폴더를 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;demo-api 폴더를 선택 &amp;gt; 폴더 안에 build.gradle 파일이 있는 폴더&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ가 프로젝트를 열고 Gradle 의존성을 다운로드함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 첫 번째 실행 테스트&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션이 제대로 실행되는지 테스트.&lt;/li&gt;
&lt;li&gt;DemoApiApplication.java 파일에서 main 메소드 옆의 초록색 실행 버튼을 클릭해서 실행.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러! PostgreSQL 설정을 안 했기 때문. =&amp;gt; 이게 정상&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. docker로 데이터베이스 서버 오픈.&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.docker.com/&quot;&gt;https://www.docker.com/&lt;/a&gt; 도커 다운로드.&lt;/li&gt;
&lt;li&gt;Docker Hub에서 postgres 검색&lt;/li&gt;
&lt;li&gt;pull 클릭.&lt;/li&gt;
&lt;li&gt;Images에서 pull 한 postgres image를 ▶️ 버튼 눌러서 실행.&lt;/li&gt;
&lt;li&gt;그럼 컨테이너 이름과 포트, Environment variables 설정창이 나오는데 다음과 같이 입력.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너명: demo-db&lt;/li&gt;
&lt;li&gt;Host port: 5432&lt;/li&gt;
&lt;li&gt;Environment variables:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Variable: POSTGRES_DB&lt;/li&gt;
&lt;li&gt;Value: demoapi&lt;/li&gt;
&lt;li&gt;Variable: POSTGRES_USER&lt;/li&gt;
&lt;li&gt;Value: demo&lt;/li&gt;
&lt;li&gt;Variable: POSTGRES_PASSWORD&lt;/li&gt;
&lt;li&gt;Value: demo123&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure id=&quot;og_1756918412912&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Docker: Accelerated Container Application Development&quot; data-og-description=&quot;Docker is a platform designed to help developers build, share, and run container applications. We handle the tedious setup, so you can focus on the code.&quot; data-og-host=&quot;www.docker.com&quot; data-og-source-url=&quot;https://www.docker.com/&quot; data-og-url=&quot;https://www.docker.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NHMU1/hyZGbOxeh5/NO8wfemOeRl6wV98FOhbxk/img.png?width=1110&amp;amp;height=580&amp;amp;face=0_0_1110_580,https://scrap.kakaocdn.net/dn/bW0axz/hyZG5Nr4Hl/1JBd92k3keO49xaJRh7nsk/img.png?width=1110&amp;amp;height=580&amp;amp;face=0_0_1110_580,https://scrap.kakaocdn.net/dn/lNYUw/hyZG0yBaHn/r5FiGJbtfCPa0tk7bWkLUK/img.png?width=1119&amp;amp;height=896&amp;amp;face=0_0_1119_896&quot;&gt;&lt;a href=&quot;https://www.docker.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.docker.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NHMU1/hyZGbOxeh5/NO8wfemOeRl6wV98FOhbxk/img.png?width=1110&amp;amp;height=580&amp;amp;face=0_0_1110_580,https://scrap.kakaocdn.net/dn/bW0axz/hyZG5Nr4Hl/1JBd92k3keO49xaJRh7nsk/img.png?width=1110&amp;amp;height=580&amp;amp;face=0_0_1110_580,https://scrap.kakaocdn.net/dn/lNYUw/hyZG0yBaHn/r5FiGJbtfCPa0tk7bWkLUK/img.png?width=1119&amp;amp;height=896&amp;amp;face=0_0_1119_896');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Docker: Accelerated Container Application Development&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Docker is a platform designed to help developers build, share, and run container applications. We handle the tedious setup, so you can focus on the code.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 아래 내용 복사해서 실행&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;docker run --name demo-db \
  -e POSTGRES_DB=demoapi \
  -e POSTGRES_USER=demo \
  -e POSTGRES_PASSWORD=demo123 \
  -p 5432:5432 \
  -d postgres:15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. src/main/resources/application.properties에 환경 세팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;src/main/resources/application.properties&lt;/code&gt; 에 아래의 내용 입력.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# PostgreSQL 데이터베이스 연결 설정
spring.datasource.url=jdbc:postgresql://localhost:5432/demoapi
spring.datasource.username=demo
spring.datasource.password=demo123
spring.datasource.driver-class-name=org.postgresql.Driver

# JPA 설정
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 의미:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;: localhost의 9998 포트로 연결&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ddl-auto=create-drop&lt;/code&gt;: 애플리케이션 실행할 때마다 테이블 재생성 (개발용)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;show-sql=true&lt;/code&gt;: 실행되는 SQL 쿼리를 콘솔에 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 다시 실행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹브라우저를 열어서 &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt; 에 접속.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과: 아마 에러 페이지가 나올것임 (404 Not Found 같은) -&amp;gt; 정상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아직 API 엔드포인트를 만들지 않았기 때문.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 컨트롤러 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;src/main/java/com/example/demo_api&lt;/code&gt; 위치에 HelloController.java 생성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HelloController 클래스에 코드를 작성&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package com.example.demoapi;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping(&quot;/hello&quot;)
    public String hello() {
        return &quot;Hello, Spring Boot API!&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 설명:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@RestController: 이 클래스가 REST API 컨트롤러라는 표시.&lt;/li&gt;
&lt;li&gt;@GetMapping(&quot;/hello&quot;): GET 요청으로 /hello 경로에 매핑.&lt;/li&gt;
&lt;li&gt;return &quot;Hello, Spring Boot API!&quot;: 응답으로 이 문자열을 반환.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 재시작 후, &lt;code&gt;localhost:8080/hello&lt;/code&gt;에 접속&lt;/li&gt;
&lt;li&gt;&quot;Hello, Spring Boot API!&quot; 가 나오면 성공.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. Thymeleaf 사용.&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;src/main/resources/templates&lt;/code&gt; 위치에 index.html을 만듬.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index.html&lt;/code&gt;에 아래 내용 입력:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Spring Boot + Thymeleaf&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
        body { font-family: Arial, sans-serif; margin: 40px; }
        h1 { color: #2c3e50; }
        .container { max-width: 600px; margin: 0 auto; }
        .highlight { color: #e74c3c; font-weight: bold; }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class=&quot;container&quot;&amp;gt;
        &amp;lt;h1&amp;gt;  Thymeleaf 템플릿&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;현재 시간: &amp;lt;span class=&quot;highlight&quot; th:text=&quot;${currentTime}&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;메시지: &amp;lt;span th:text=&quot;${message}&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;ul&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;/hello&quot;&amp;gt;텍스트 API&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;/&quot;&amp;gt;Thymeleaf 템플릿&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HelloController 수정&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;package com.example.demo_api;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {

    @GetMapping(&quot;/&quot;)
    public String index(Model model) {
        model.addAttribute(&quot;currentTime&quot;, java.time.LocalDateTime.now());
        model.addAttribute(&quot;message&quot;, &quot;Thymeleaf가 정상 작동중입니다!&quot;);
        return &quot;index&quot;;
    }

    @GetMapping(&quot;/hello&quot;)
    @ResponseBody
    public String hello() {
        return &quot;Hello, Spring Boot API!&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Controller: 뷰 템플릿을 반환할 수 있음&lt;/li&gt;
&lt;li&gt;@RestController: 항상 데이터(JSON/문자열)만 반환&lt;/li&gt;
&lt;li&gt;@ResponseBody: Controller에서 특정 메소드만 직접 데이터 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/&quot;&gt;http://localhost:8080/&lt;/a&gt; 입력하면   Thymeleaf 템플릿 나와야함.&lt;/li&gt;
&lt;li&gt;http://localhost:8080/hello&amp;nbsp;는 여전히 &quot;Hello, Spring Boot API!&quot;가 나옴.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. JPA Entity와 데이터베이스 연동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스와 연동해서 CRUD API를 만들기.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 새로운 패키지 생성.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;src/main/java/com/example/demo_api&lt;/code&gt; 안에 &lt;code&gt;entity&lt;/code&gt; 패키지를 만들기.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우클릭 &amp;gt; New &amp;gt; Package &amp;gt; &lt;code&gt;entity&lt;/code&gt; 입력&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. User Entity 클래스 생성.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;entity&lt;/code&gt; 패키지에서 우클릭 &amp;gt; New &amp;gt; Java Class &amp;gt; &lt;code&gt;User&lt;/code&gt; 입력&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;code&gt;User.java&lt;/code&gt;에 다음 코드를 작성.&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package com.example.demo_api.entity;

import jakarta.persistence.*;

@Entity
@Table(name = &quot;users&quot;)
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(nullable = false)
  private String name;

  @Column(nullable = false, unique = true)
  private String email;

  // 기본 생성자 (JPA 필수)
  public User() {}

  // 생성자
  public User(String name, String email) {
      this.name = name;
      this.email = email;
  }

  // Getter, Setter
  public Long getId() {
      return id;
  }

  public void setId(Long id) {
      this.id = id;
  }

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  public String getEmail() {
      return email;
  }

  public void setEmail(String email) {
      this.email = email;
  }

  // toString 메소드 (디버깅용)
  @Override
  public String toString() {
      return &quot;User{&quot; +
              &quot;id=&quot; + id +
              &quot;, name='&quot; + name + '\'' +
              &quot;, email='&quot; + email + '\'' +
              '}';
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. Repository 인터페이스 생성&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;repository&lt;/code&gt; 패키지 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;src/main/java/com/example/demo_api&lt;/code&gt; 안에 &lt;code&gt;repository&lt;/code&gt; 패키지 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UserRepository.java&lt;/code&gt; 인터페이스 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package com.example.demo_api.repository;

import com.example.demo_api.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
    // 기본 CRUD는 JpaRepository가 자동 제공
    // findAll(), save(), findById(), deleteById() 등
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. User API Controller 생성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;code&gt;controller&lt;/code&gt; 패키지 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;src/main/java/com/example/demo_api&lt;/code&gt; 안에 &lt;code&gt;controller&lt;/code&gt; 패키지 만들기&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 기존 &lt;code&gt;HelloController.java&lt;/code&gt;를 &lt;code&gt;controller&lt;/code&gt; 패키지로 이동&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;HelloController.java&lt;/code&gt;를 드래그해서 &lt;code&gt;controller&lt;/code&gt; 패키지로 옮기고&lt;/li&gt;
&lt;li&gt;패키지 선언을 &lt;code&gt;package com.example.demo_api.controller;&lt;/code&gt;로 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;code&gt;UserController.java&lt;/code&gt; 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;controller&lt;/code&gt; 패키지에 새 클래스 생성하고 다음 코드 작성:&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package com.example.demo_api.controller;

import com.example.demo_api.entity.User;
import com.example.demo_api.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping(&quot;/api/users&quot;)
public class UserController {

    @Autowired
    private UserRepository userRepository;

    // 모든 사용자 조회
    @GetMapping
    public List&amp;lt;User&amp;gt; getAllUsers() {
        return userRepository.findAll();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 &lt;code&gt;@RestController&lt;/code&gt;를 사용. JSON 데이터를 반환하기 위함!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13. 첫 번째 API 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 애플리케이션 재시작&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ에서 애플리케이션을 중지하고 다시 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. API 테스트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저에서 &lt;a href=&quot;http://localhost:8080/api/users&quot;&gt;http://localhost:8080/api/users&lt;/a&gt; 에 접속&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 결과&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 사용자가 없어 빈 배열 나옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. CRUD API 만들기 - 사용자 API 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;UserController.java&lt;/code&gt;에 다음 메소드들을 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api/users&quot;)
public class UserController {
  @Autowired
  private UserRepository userRepository;

  // 모든 사용자 조회
  @GetMapping
  public List&amp;lt;User&amp;gt; getAllUsers() {
      return userRepository.findAll();
  }

  // 새 사용자 생성
  @PostMapping
  public User createUser(@RequestBody User user) {
      return userRepository.save(user);
  }

  // 특정 사용자 조회
  @GetMapping(&quot;/{id}&quot;)
  public User getUserById(@PathVariable Long id) {
      return userRepository.findById(id).orElse(null);
  }

  // 사용자 정보 수정
  @PutMapping(&quot;/{id}&quot;)
  public User updateUser(@PathVariable Long id, @RequestBody User userDetails) {
      User user = userRepository.findById(id).orElse(null);
      if (user != null) {
          user.setName(userDetails.getName());
          user.setEmail(userDetails.getEmail());
          return userRepository.save(user);
      }
      return null;
  }

  // 사용자 삭제
  @DeleteMapping(&quot;/{id}&quot;)
  public String deleteUser(@PathVariable Long id) {
      if (userRepository.existsById(id)) {
          userRepository.deleteById(id);
          return &quot;사용자가 삭제되었습니다.&quot;;
      }
      return &quot;사용자를 찾을 수 없습니다.&quot;;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET &lt;code&gt;/api/users&lt;/code&gt; - 모든 사용자 조회&lt;/li&gt;
&lt;li&gt;POST &lt;code&gt;/api/users&lt;/code&gt; - 사용자 생성&lt;/li&gt;
&lt;li&gt;GET &lt;code&gt;/api/users/{id}&lt;/code&gt; - 특정 사용자 조회&lt;/li&gt;
&lt;li&gt;PUT &lt;code&gt;/api/users/{id}&lt;/code&gt; - 사용자 정보 수정&lt;/li&gt;
&lt;li&gt;DELETE &lt;code&gt;/api/users/{id}&lt;/code&gt; - 사용자 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;15. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 CRUD API가 완성되면 다음 단계:&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예외 처리 &amp;amp; 검증&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Valid&lt;/code&gt; 어노테이션으로 데이터 검증&lt;/li&gt;
&lt;li&gt;전역 예외 처리 (&lt;code&gt;@ControllerAdvice&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;사용자 정의 예외 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Service 계층 추가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Controller-Service-Repository 아키텍처&lt;/li&gt;
&lt;li&gt;비즈니스 로직 분리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Service&lt;/code&gt; 어노테이션&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DTO (Data Transfer Object)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Entity와 API 응답 분리&lt;/li&gt;
&lt;li&gt;보안 강화 (민감한 필드 숨기기)&lt;/li&gt;
&lt;li&gt;ModelMapper 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관계형 데이터베이스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 Entity 추가 (예: Post, Comment)&lt;/li&gt;
&lt;li&gt;JPA 관계 매핑 (&lt;code&gt;@OneToMany&lt;/code&gt;, &lt;code&gt;@ManyToOne&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;복잡한 쿼리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API 문서화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Swagger/OpenAPI 추가&lt;/li&gt;
&lt;li&gt;API 문서 자동 생성&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 아카이브/JAVA</category>
      <category>spring</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/161</guid>
      <comments>https://wooncloud.tistory.com/161#entry161comment</comments>
      <pubDate>Thu, 4 Sep 2025 01:58:56 +0900</pubDate>
    </item>
    <item>
      <title>Java 17 다운로드 방법</title>
      <link>https://wooncloud.tistory.com/160</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdeAXz/btsQjRZzubT/KSa0AiNEIMQMwipBOjpsT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdeAXz/btsQjRZzubT/KSa0AiNEIMQMwipBOjpsT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdeAXz/btsQjRZzubT/KSa0AiNEIMQMwipBOjpsT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdeAXz%2FbtsQjRZzubT%2FKSa0AiNEIMQMwipBOjpsT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;135&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 Java 17 배포판&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;Oracle JDK 17&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다운로드&lt;/b&gt;: &lt;a href=&quot;https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html&quot;&gt;https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&lt;/b&gt;: Oracle의 공식 배포판&lt;/li&gt;
&lt;li&gt;&lt;b&gt;라이선스&lt;/b&gt;: 개발/테스트는 무료, 상업적 사용 시 유료&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;Eclipse Adoptium&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다운로드&lt;/b&gt;: &lt;a href=&quot;https://adoptium.net/temurin/releases/?version=17&quot;&gt;https://adoptium.net/temurin/releases/?version=17&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&lt;/b&gt;: 커뮤니티 기반, 가장 인기 있는 오픈소스 배포판&lt;/li&gt;
&lt;li&gt;&lt;b&gt;라이선스&lt;/b&gt;: 완전 무료 (OpenJDK 기반)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;b&gt;Amazon Corretto 17&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다운로드&lt;/b&gt;: &lt;a href=&quot;https://aws.amazon.com/corretto/&quot;&gt;https://aws.amazon.com/corretto/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&lt;/b&gt;: AWS에서 지원하는 무료 배포판&lt;/li&gt;
&lt;li&gt;&lt;b&gt;라이선스&lt;/b&gt;: 완전 무료&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. &lt;b&gt;Microsoft Build of OpenJDK&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다운로드&lt;/b&gt;: &lt;a href=&quot;https://www.microsoft.com/openjdk&quot;&gt;https://www.microsoft.com/openjdk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&lt;/b&gt;: Microsoft에서 지원&lt;/li&gt;
&lt;li&gt;&lt;b&gt;라이선스&lt;/b&gt;: 완전 무료&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영체제별 설치 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Windows&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;위 사이트에서 .msi 또는 .zip 파일 다운로드&lt;/li&gt;
&lt;li&gt;.msi 파일 실행하여 설치 마법사 따라하기&lt;/li&gt;
&lt;li&gt;환경변수 JAVA_HOME 설정 (보통 자동 설정됨)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;macOS&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;# Homebrew 사용 (권장)
brew install openjdk@17

# 또는 pkg 파일 직접 다운로드 후 설치
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Linux (Ubuntu/Debian)&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;# Eclipse Adoptium 설치
sudo apt update
sudo apt install openjdk-17-jdk

# 또는 수동 설치를 위해 tar.gz 다운로드
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Linux (RHEL/CentOS)&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;sudo yum install java-17-openjdk-devel
# 또는
sudo dnf install java-17-openjdk-devel
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치 확인&lt;/h2&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;java -version
javac -version
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추천 사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;개인/오픈소스 프로젝트&lt;/b&gt;: Eclipse Adoptium 추천&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기업 환경&lt;/b&gt;: Amazon Corretto 또는 Microsoft Build of OpenJDK&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Oracle 생태계 사용&lt;/b&gt;: Oracle JDK&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 아카이브/JAVA</category>
      <category>Java</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/160</guid>
      <comments>https://wooncloud.tistory.com/160#entry160comment</comments>
      <pubDate>Wed, 3 Sep 2025 23:23:13 +0900</pubDate>
    </item>
    <item>
      <title>Jules - 구글 Gemini에게 코딩 오마카세 시키기</title>
      <link>https://wooncloud.tistory.com/159</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;해당 글은 AI 정보들을 모아 보관하는 글입니다.&lt;br /&gt;급변하는 기술인만큼 포스팅 시간이 지날수록 잘못된 정보일 수 있습니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0m0Wn/btsPDWA6Wf3/HEKC4nAYAz4E9CZACmTKV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0m0Wn/btsPDWA6Wf3/HEKC4nAYAz4E9CZACmTKV1/img.png&quot; data-alt=&quot;오징어인지 문어인지 졸귀탱&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0m0Wn/btsPDWA6Wf3/HEKC4nAYAz4E9CZACmTKV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0m0Wn%2FbtsPDWA6Wf3%2FHEKC4nAYAz4E9CZACmTKV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;600&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;오징어인지 문어인지 졸귀탱&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jules.google/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jules.google/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754130529476&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Jules - An Asynchronous Coding Agent&quot; data-og-description=&quot;Jules creates a PR of the changes. Approve the PR, merge it to your branch, and publish it on GitHub. Also, you can get caught up fast. Jules creates an audio summary of the changes.&quot; data-og-host=&quot;jules.google&quot; data-og-source-url=&quot;https://jules.google/&quot; data-og-url=&quot;https://jules.google&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/clzMOI/hyZrypKiRi/ABGp4ZGbBDf09K3YpaqRqK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://jules.google/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jules.google/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/clzMOI/hyZrypKiRi/ABGp4ZGbBDf09K3YpaqRqK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Jules - An Asynchronous Coding Agent&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jules creates a PR of the changes. Approve the PR, merge it to your branch, and publish it on GitHub. Also, you can get caught up fast. Jules creates an audio summary of the changes.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jules.google&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 OpenAI에서 Codex를 내놓으려 할 당시, 구글에서 먼저 jules를 출시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex를 써보고 싶었으나, 월 200달러인가 하는 요금제를 결제해야 쓸 수 있어서 그냥 입구컷 당했다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신에 줄스는 무료라서 바로 찍먹 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/thqTG/btsPFarLCRr/eVQlShW8puuCaFoLq7En2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/thqTG/btsPFarLCRr/eVQlShW8puuCaFoLq7En2k/img.png&quot; data-alt=&quot;좀 하는데?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/thqTG/btsPFarLCRr/eVQlShW8puuCaFoLq7En2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FthqTG%2FbtsPFarLCRr%2FeVQlShW8puuCaFoLq7En2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1630&quot; height=&quot;918&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;좀 하는데?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jules는 Google Labs에서 공개한&amp;nbsp;&lt;b&gt;비동기 코딩 에이전트&lt;/b&gt;. 이번에 퍼블릭 베타로 전환되면서 대기 목록 없이 누구나 사용 가능해졌다고 함.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 도구들과 뭐가 다른가&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;코파일럿이 아님&lt;/b&gt;: 옆에서 도와주는 게 아니라&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자율적인 에이전트&lt;/b&gt;: 코드를 읽고, 의도를 파악하고, 알아서 작업 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단순히 코드 자동완성이나 제안이 아니라 &lt;b&gt;실제로 작업을 대신 해주는&lt;/b&gt; 개념.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떻게 동작하는지&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;리포지토리 복제&lt;/b&gt;: 내 코드베이스를 Google Cloud VM에 복제&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전체 컨텍스트 이해&lt;/b&gt;: 프로젝트 전체를 파악&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기 작업&lt;/b&gt;: 백그라운드에서 알아서 작업&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과 제시&lt;/b&gt;: 계획, 추론 과정, 변경사항 diff 보여줌&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 기능들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;할 수 있는 일들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 작성&lt;/li&gt;
&lt;li&gt;새 기능 개발&lt;/li&gt;
&lt;li&gt;버그 수정&lt;/li&gt;
&lt;li&gt;의존성 버전 업데이트&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오디오 체인지로그&lt;/b&gt; 제공 (이게 특이함)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실제 코드베이스&lt;/b&gt;에서 작업 (샌드박스 아님)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;병렬 실행&lt;/b&gt;: 여러 작업을 동시에 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에는 하루에 10개까지 돌릴 수 있었는데, 현시간 기준 60개 에이전트 동시 병렬 수행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GitHub 통합&lt;/b&gt;: 기존 워크플로우에 바로 연동 - 내 깃허브 리포지토리에서 가져와 쓰고 코딩한거 PR 날려줌.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 제어&lt;/b&gt;: 계획 수정 가능, 실행 중에도 개입 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안&lt;/b&gt;: 프라이빗 코드로 학습 안 함, 데이터 격리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 사용 과정&lt;/h2&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;1. GitHub 연결하고 브랜치 생성
2. 작업 프롬프트 입력
3. Jules가 계획 제시 &amp;rarr; 승인
4. 작업 수행하면서 피드백 가능
5. 패널에서 모든 작업 관리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 써보니까, 잘하는 부분이 있고 못하는 부분이 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ 잘 설명해주면 특정 일부 기능을 구현하는 것은 잘함.&lt;/li&gt;
&lt;li&gt;✅ 특정 오류 부분을 잘 설명해주고 어떻게 고쳐야 하는지 알려주면 잘함.&lt;/li&gt;
&lt;li&gt;❌ 전체적인 기획을 주고 만들어라고 하면 내가 원하는대로 잘 안나옴.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 개발 시작과 아키텍처 등을 맡기는건 클로드에게 맡기는게 낫다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 클로드가 대부분 AI 중에서도 압도적으로 코딩을 잘하는데, 토큰 사용량이 걱정된다면, 간단한 구현과 버그 고치는데 Gemini를 사용하면 좀 아낄 수 있다고 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jules.google/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jules.google/docs/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754130848968&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Getting started&quot; data-og-description=&quot;Set up and run your first task with Jules&quot; data-og-host=&quot;jules.google&quot; data-og-source-url=&quot;https://jules.google/docs/&quot; data-og-url=&quot;https://jules.google/docs/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://jules.google/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jules.google/docs/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Getting started&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Set up and run your first task with Jules&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jules.google&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/AI, 인공지능</category>
      <category>Ai</category>
      <category>Google</category>
      <category>Jules</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/159</guid>
      <comments>https://wooncloud.tistory.com/159#entry159comment</comments>
      <pubDate>Sat, 2 Aug 2025 19:37:46 +0900</pubDate>
    </item>
    <item>
      <title>Google Stitch - AI에게 UI 디자인 시키기</title>
      <link>https://wooncloud.tistory.com/158</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;해당 글은 AI 정보들을 모아 보관하는 글입니다.&lt;br /&gt;급변하는 기술인만큼 포스팅 시간이 지날수록 잘못된 정보일 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dElTN1/btsPEEz7DLz/wj0UvVvFhG6B60Q7pnduI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dElTN1/btsPEEz7DLz/wj0UvVvFhG6B60Q7pnduI0/img.png&quot; data-alt=&quot;https://developers.googleblog.com/ko/stitch-a-new-way-to-design-uis/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dElTN1/btsPEEz7DLz/wj0UvVvFhG6B60Q7pnduI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdElTN1%2FbtsPEEz7DLz%2Fwj0UvVvFhG6B60Q7pnduI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;380&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://developers.googleblog.com/ko/stitch-a-new-way-to-design-uis/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stitch.withgoogle.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stitch.withgoogle.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754129415828&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Stitch - Design with AI&quot; data-og-description=&quot;&quot; data-og-host=&quot;stitch.withgoogle.com&quot; data-og-source-url=&quot;https://stitch.withgoogle.com/&quot; data-og-url=&quot;https://stitch.withgoogle.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bvSpWb/hyZvxo9H9x/zpY47J6COU9KIIhgfbCAH0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/tlgZ2/hyZuKvs6a9/V92xDa9LWbUaMkrfqmSGgK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://stitch.withgoogle.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stitch.withgoogle.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bvSpWb/hyZvxo9H9x/zpY47J6COU9KIIhgfbCAH0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/tlgZ2/hyZuKvs6a9/V92xDa9LWbUaMkrfqmSGgK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Stitch - Design with AI&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stitch.withgoogle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 사이드를 하면서 늘 고민이였던게 UI 디자인이었는데, 그만큼 CSS 프레임워크에도 많이 의존도 하고 UI 컨셉도 생각하기 정말 어려운 부분이긴 했다. AI가 UI 디자인을 해주는게 없나? 생각했지만, 최근에 구글이 Stitch를 내놓으면서 이거다 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google&amp;nbsp;Labs에서&amp;nbsp;새로&amp;nbsp;내놓은&amp;nbsp;실험적인&amp;nbsp;도구.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트나&amp;nbsp;이미지를&amp;nbsp;입력하면&amp;nbsp;몇&amp;nbsp;분&amp;nbsp;안에&amp;nbsp;UI&amp;nbsp;디자인과&amp;nbsp;프런트엔드&amp;nbsp;코드를&amp;nbsp;생성해준다고&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해본 찍먹.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p6B5g/btsPDsHdp0F/Ejm6Zgrl9KLSvC5gQWkT00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p6B5g/btsPDsHdp0F/Ejm6Zgrl9KLSvC5gQWkT00/img.png&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;849&quot; data-is-animation=&quot;false&quot; style=&quot;width: 78.9593%; margin-right: 10px;&quot; data-widthpercent=&quot;79.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p6B5g/btsPDsHdp0F/Ejm6Zgrl9KLSvC5gQWkT00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp6B5g%2FbtsPDsHdp0F%2FEjm6Zgrl9KLSvC5gQWkT00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1517&quot; height=&quot;849&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H58xQ/btsPFf7Agtg/5az2H2KF8KmrU5bnNf6I9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H58xQ/btsPFf7Agtg/5az2H2KF8KmrU5bnNf6I9K/img.png&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;1734&quot; data-is-animation=&quot;false&quot; style=&quot;width: 19.8779%;&quot; data-widthpercent=&quot;20.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H58xQ/btsPFf7Agtg/5az2H2KF8KmrU5bnNf6I9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH58xQ%2FbtsPFf7Agtg%2F5az2H2KF8KmrU5bnNf6I9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;1734&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;google stitch로 만들어본 앱 디자인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사진에서 보면 알지만 만들어진 내용을 피그마로 옮길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 내가 피그마를 잘 못쓴다는거다. 이쯤되면 피그마를 좀 배워야할듯..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 Figma MCP로 커서든 클로드코드든 연결해서 사용하면 정말 바이브코딩 완성인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 아래 푸터 버튼을 보면 일관성이 없다는 생각을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전히 원하는 내용을 만들게끔 하는건 어려울 것 같고, 전체적인 컨셉을 보거나 레이아웃을 빠르게 만들기엔 좋은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 처음부터 다 해주는건 없다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.googleblog.com/ko/stitch-a-new-way-to-design-uis/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.googleblog.com/ko/stitch-a-new-way-to-design-uis/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754129861220&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;아이디어에서 앱으로: UI를 디자인하는 새로운 방법, Stitch 소개- Google Developers Blog&quot; data-og-description=&quot;훌륭한 애플리케이션을 만드는 일은 항상 디자인과 개발 사이의 강력한 파트너십으로 귀결됩니다. 디자이너는 직관적이고 매력적인 인터페이스를 만들어 훌륭한 사용자 경험을 구상합니다. 그&quot; data-og-host=&quot;developers.googleblog.com&quot; data-og-source-url=&quot;https://developers.googleblog.com/ko/stitch-a-new-way-to-design-uis/&quot; data-og-url=&quot;https://developers.googleblog.com/ko/stitch-a-new-way-to-design-uis/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/elRpXW/hyZqUsXYFk/6rT4mqncrrBSUNBAYekOI1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bp6S18/hyZvp5I2z1/FqTFSneaE62HwBGnorAFa0/img.png?width=1600&amp;amp;height=476&amp;amp;face=0_0_1600_476,https://scrap.kakaocdn.net/dn/Stflo/hyZrwrSuXv/Xi3HVKfSNkCaoZdrb9HUPk/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400&quot;&gt;&lt;a href=&quot;https://developers.googleblog.com/ko/stitch-a-new-way-to-design-uis/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.googleblog.com/ko/stitch-a-new-way-to-design-uis/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/elRpXW/hyZqUsXYFk/6rT4mqncrrBSUNBAYekOI1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bp6S18/hyZvp5I2z1/FqTFSneaE62HwBGnorAFa0/img.png?width=1600&amp;amp;height=476&amp;amp;face=0_0_1600_476,https://scrap.kakaocdn.net/dn/Stflo/hyZrwrSuXv/Xi3HVKfSNkCaoZdrb9HUPk/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;아이디어에서 앱으로: UI를 디자인하는 새로운 방법, Stitch 소개- Google Developers Blog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;훌륭한 애플리케이션을 만드는 일은 항상 디자인과 개발 사이의 강력한 파트너십으로 귀결됩니다. 디자이너는 직관적이고 매력적인 인터페이스를 만들어 훌륭한 사용자 경험을 구상합니다. 그&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.googleblog.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/AI, 인공지능</category>
      <category>Ai</category>
      <category>Stitch</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/158</guid>
      <comments>https://wooncloud.tistory.com/158#entry158comment</comments>
      <pubDate>Sat, 2 Aug 2025 19:20:39 +0900</pubDate>
    </item>
    <item>
      <title>Uint8Array.from()이 반복문보다 빠른 이유</title>
      <link>https://wooncloud.tistory.com/156</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFPC6p/btsPFCnWvH9/79UHCucHQkcFKHFMzuAI31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFPC6p/btsPFCnWvH9/79UHCucHQkcFKHFMzuAI31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFPC6p/btsPFCnWvH9/79UHCucHQkcFKHFMzuAI31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFPC6p%2FbtsPFCnWvH9%2F79UHCucHQkcFKHFMzuAI31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;200&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;코드 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경 전: 수동 반복문 (느림)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i &amp;lt; binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경 후: Uint8Array.from() (빠름)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const bytes = Uint8Array.from(binaryString, char =&amp;gt; char.charCodeAt(0));&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성능 차이가 나는 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 내부 구현 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수동 반복문의 처리 과정:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JavaScript 인터프리터가 각 반복을 처리&lt;/li&gt;
&lt;li&gt;매번 조건 확인 (&lt;code&gt;i &amp;lt; binaryString.length&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;개별 인덱스 접근과 할당&lt;/li&gt;
&lt;li&gt;증가 연산 (&lt;code&gt;i++&lt;/code&gt;) 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Uint8Array.from()의 처리 과정:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네이티브 C++ 코드로 구현&lt;/li&gt;
&lt;li&gt;벡터화된 연산 가능 (SIMD)&lt;/li&gt;
&lt;li&gt;메모리 접근 패턴 최적화&lt;/li&gt;
&lt;li&gt;루프 언롤링 자동 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. JavaScript 엔진 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;V8 엔진에서의 이점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네이티브 C++ 구현으로 JavaScript 해석 오버헤드 제거&lt;/li&gt;
&lt;li&gt;SIMD(Single Instruction, Multiple Data) 연산 활용&lt;/li&gt;
&lt;li&gt;CPU 캐시 효율성 향상&lt;/li&gt;
&lt;li&gt;메모리 프리페칭 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 메모리 접근 패턴&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방식&lt;/th&gt;
&lt;th&gt;메모리 접근&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;수동 반복문&lt;/td&gt;
&lt;td&gt;개별 접근&lt;/td&gt;
&lt;td&gt;캐시 미스 가능성, 순차적이지만 비효율적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uint8Array.from()&lt;/td&gt;
&lt;td&gt;배치 처리&lt;/td&gt;
&lt;td&gt;메모리 블록 단위 처리, 캐시 친화적&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 실제 성능 벤치마크&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 테스트 데이터 (10KB 문자열)
const testString = 'A'.repeat(10000);

// 방법 1: 수동 반복문
console.time('Manual Loop');
const bytes1 = new Uint8Array(testString.length);
for (let i = 0; i &amp;lt; testString.length; i++) {
    bytes1[i] = testString.charCodeAt(i);
}
console.timeEnd('Manual Loop'); // ~2.5ms

// 방법 2: Uint8Array.from()
console.time('Uint8Array.from');
const bytes2 = Uint8Array.from(testString, char =&amp;gt; char.charCodeAt(0));
console.timeEnd('Uint8Array.from'); // ~1.2ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과: 약 50% 성능 향상&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 성능 요소 비교&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;요소&lt;/th&gt;
&lt;th&gt;수동 반복문&lt;/th&gt;
&lt;th&gt;Uint8Array.from()&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구현 레벨&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;JavaScript 해석&lt;/td&gt;
&lt;td&gt;네이티브 C++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;최적화&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;JIT 컴파일러 의존&lt;/td&gt;
&lt;td&gt;컴파일 타임 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;메모리 처리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;개별 접근&lt;/td&gt;
&lt;td&gt;배치 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;캐시 효율성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;캐시 미스 가능&lt;/td&gt;
&lt;td&gt;캐시 친화적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SIMD 활용&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가 최적화 팁&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 빠른 방법이 있는 경우:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// 일반 문자열의 경우 TextEncoder가 가장 빠름
const encoder = new TextEncoder();
const bytes = encoder.encode(string);

// 하지만 Base64 디코딩 등 바이너리 데이터의 경우
// charCodeAt() 방식이 필요&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Uint8Array.from()이 빠른 핵심 이유:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;네이티브 구현&lt;/b&gt;: JavaScript에서 C++로 처리 이동&lt;/li&gt;
&lt;li&gt;&lt;b&gt;벡터화 연산&lt;/b&gt;: SIMD 명령어 활용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 최적화&lt;/b&gt;: 배치 단위 메모리 처리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;루프 오버헤드 제거&lt;/b&gt;: 조건문, 증가연산 등 제거&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 성능 향상: 20-50%&lt;/b&gt; (데이터 크기가 클수록 차이 증가)&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>javascript</category>
      <category>Uint8Array</category>
      <category>성능</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/156</guid>
      <comments>https://wooncloud.tistory.com/156#entry156comment</comments>
      <pubDate>Sat, 2 Aug 2025 18:55:26 +0900</pubDate>
    </item>
    <item>
      <title>TypedArray와 일반 배열과의 성능 차이 정리</title>
      <link>https://wooncloud.tistory.com/155</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;protobuf를 알아보다가 Uint8Array를 알게되고, 그러다가 TypedArray을 알게 되었다.&lt;br /&gt;자바스크립트 쓰면서 타입이 없어 비효율이 많다는 점을 내면속에 알고만 있었는데, 이런 부분을 보완한 Array가 있다고 해서 알아봤다.&lt;br /&gt;당연히 이런것들이 존재했을 법 한데, 새삼 이제야 알게된다.&lt;br /&gt;TypedArray는 단순히 타입이 있는것 뿐만 아니라 많은 용도들이 있는데 신기해서 가져와 봄.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypedArray&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypedArray&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754127800973&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;TypedArray - JavaScript | MDN&quot; data-og-description=&quot;TypedArray 객체는 이진 데이터 버퍼에 기초하여 배열과 같은 보기를 만들어냅니다. 하지만 TypedArray라는 전역 속성은 존재하지 않으며, 직접 볼 수 있는 TypedArray 생성자도 존재하지 않습니다. 대신 &quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypedArray&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypedArray&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bpl6fm/hyZuLungpk/mC1kC3l4L7Gjkxkeyi14vK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypedArray&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypedArray&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bpl6fm/hyZuLungpk/mC1kC3l4L7Gjkxkeyi14vK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;TypedArray - JavaScript | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;TypedArray 객체는 이진 데이터 버퍼에 기초하여 배열과 같은 보기를 만들어냅니다. 하지만 TypedArray라는 전역 속성은 존재하지 않으며, 직접 볼 수 있는 TypedArray 생성자도 존재하지 않습니다. 대신&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TypedArray가 필요한 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일반 배열의 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript의 일반 배열은 매우 유연하지만, 성능면에서 한계가 있음.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 일반 배열: 타입이 섞여있고 메모리 비효율적
const normalArray = [1, &quot;hello&quot;, 3.14, true, null];

// 메모리에서는 각 요소가 다른 타입으로 저장됨
// 숫자도 내부적으로는 64비트 부동소수점으로 저장&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반 배열의 한계:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;모든 숫자가 64비트 부동소수점으로 저장 (메모리 낭비)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;타입 체크 오버헤드&lt;/li&gt;
&lt;li&gt;메모리가 연속적이지 않을 수 있음&lt;/li&gt;
&lt;li&gt;바이너리 데이터 처리에 부적합&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TypedArray의 장점&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// TypedArray: 타입이 고정되고 메모리 효율적
const uint8Array = new Uint8Array([1, 2, 3, 255]);

// 각 요소가 정확히 1바이트씩 연속된 메모리에 저장
// 0~255 범위의 정수만 저장 가능&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TypedArray 종류와 특징&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정수형 TypedArray&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;크기&lt;/th&gt;
&lt;th&gt;범위&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Int8Array&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1바이트&lt;/td&gt;
&lt;td&gt;-128 ~ 127&lt;/td&gt;
&lt;td&gt;부호있는 8비트 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Uint8Array&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1바이트&lt;/td&gt;
&lt;td&gt;0 ~ 255&lt;/td&gt;
&lt;td&gt;부호없는 8비트 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Uint8ClampedArray&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1바이트&lt;/td&gt;
&lt;td&gt;0 ~ 255 (고정)&lt;/td&gt;
&lt;td&gt;이미지 픽셀 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Int16Array&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2바이트&lt;/td&gt;
&lt;td&gt;-32,768 ~ 32,767&lt;/td&gt;
&lt;td&gt;부호있는 16비트 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Uint16Array&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2바이트&lt;/td&gt;
&lt;td&gt;0 ~ 65,535&lt;/td&gt;
&lt;td&gt;부호없는 16비트 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Int32Array&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4바이트&lt;/td&gt;
&lt;td&gt;-2^31 ~ 2^31-1&lt;/td&gt;
&lt;td&gt;부호있는 32비트 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Uint32Array&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4바이트&lt;/td&gt;
&lt;td&gt;0 ~ 2^32-1&lt;/td&gt;
&lt;td&gt;부호없는 32비트 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부동소수점 TypedArray&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;타입&lt;/th&gt;
&lt;th&gt;크기&lt;/th&gt;
&lt;th&gt;정밀도&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Float32Array&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4바이트&lt;/td&gt;
&lt;td&gt;단정밀도&lt;/td&gt;
&lt;td&gt;3D 그래픽, 게임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Float64Array&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8바이트&lt;/td&gt;
&lt;td&gt;배정밀도&lt;/td&gt;
&lt;td&gt;과학 계산&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특수한 Uint8ClampedArray&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const normal = new Uint8Array([300, -10, 50]);
console.log(normal); // [44, 246, 50] - 오버플로우 발생

const clamped = new Uint8ClampedArray([300, -10, 50]);
console.log(clamped); // [255, 0, 50] - 범위에 고정됨&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TypedArray 생성 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 길이로 생성&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;const buffer = new Uint8Array(4); // 4바이트 배열
console.log(buffer); // [0, 0, 0, 0]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 배열로 생성&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const buffer = new Uint8Array([1, 2, 3, 4]);
console.log(buffer); // [1, 2, 3, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. ArrayBuffer로 생성&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const arrayBuffer = new ArrayBuffer(4); // 4바이트 메모리
const uint8View = new Uint8Array(arrayBuffer);
const uint16View = new Uint16Array(arrayBuffer);

uint8View[0] = 255;
uint8View[1] = 255;
console.log(uint16View[0]); // 65535 (같은 메모리를 다른 방식으로 해석)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 다른 TypedArray로부터 생성&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const original = new Uint32Array([1, 2, 3, 4]);
const copy = new Uint8Array(original.buffer); // 같은 메모리 공유&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TypedArray vs 일반 배열 성능 비교&lt;/h2&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;// 성능 테스트: 1백만 개 숫자 처리
const size = 1000000;

// 일반 배열
console.time('일반 배열');
const normalArray = new Array(size);
for (let i = 0; i &amp;lt; size; i++) {
  normalArray[i] = i % 256;
}
console.timeEnd('일반 배열'); // ~50ms

// TypedArray
console.time('Uint8Array');
const typedArray = new Uint8Array(size);
for (let i = 0; i &amp;lt; size; i++) {
  typedArray[i] = i % 256;
}
console.timeEnd('Uint8Array'); // ~20ms

// 메모리 사용량
console.log('일반 배열 메모리:', normalArray.length * 8, '바이트'); // 8MB
console.log('TypedArray 메모리:', typedArray.byteLength, '바이트'); // 1MB&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 사용 사례&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기는 알아보다가 안쓸것 같지만 그렇구나 싶었다. 바이너리를 이해하면 많은걸 할 수 있구나 싶더라.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 파일 처리&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 파일을 바이너리로 읽기
const fileInput = document.querySelector('input[type=&quot;file&quot;]');
fileInput.addEventListener('change', async (event) =&amp;gt; {
  const file = event.target.files[0];
  const arrayBuffer = await file.arrayBuffer();
  const uint8Array = new Uint8Array(arrayBuffer);

  console.log('파일 크기:', uint8Array.length, '바이트');
  console.log('첫 4바이트:', uint8Array.slice(0, 4));
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 이미지 픽셀 조작&lt;/h3&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;// Canvas에서 이미지 데이터 가져오기
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// Uint8ClampedArray로 픽셀 데이터 접근
const pixels = imageData.data; // [R, G, B, A, R, G, B, A, ...]

// 이미지를 흑백으로 변환
for (let i = 0; i &amp;lt; pixels.length; i += 4) {
  const avg = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
  pixels[i] = avg;     // Red
  pixels[i + 1] = avg; // Green
  pixels[i + 2] = avg; // Blue
  // pixels[i + 3]은 Alpha (투명도)
}

ctx.putImageData(imageData, 0, 0);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 네트워크 통신&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 바이너리 데이터 전송
const data = new Uint8Array([72, 101, 108, 108, 111]); // &quot;Hello&quot;의 ASCII

fetch('/api/binary', {
  method: 'POST',
  body: data,
  headers: {
    'Content-Type': 'application/octet-stream'
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. WebGL과 3D 그래픽&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 3D 좌표 데이터
const vertices = new Float32Array([
  -1.0, -1.0,  0.0,  // 점 1
   1.0, -1.0,  0.0,  // 점 2
   0.0,  1.0,  0.0   // 점 3
]);

// WebGL 버퍼에 데이터 전송
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의사항과 팁&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 범위 오버플로우&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const uint8 = new Uint8Array([300]); // 300은 255를 초과
console.log(uint8[0]); // 44 (300 % 256 = 44)

// 안전한 값 할당
function safeAssign(array, index, value) {
  array[index] = Math.max(0, Math.min(255, value));
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 메모리 효율적인 복사&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 비효율적인 방법
const copy1 = new Uint8Array([...original]);

// 효율적인 방법
const copy2 = new Uint8Array(original.length);
copy2.set(original);

// 가장 빠른 방법 (같은 타입일 때)
const copy3 = original.slice();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 기타.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔디안이란 개념도 보였는데 이건 잘 이해를 못해서 패스..&lt;br /&gt;말로는 멀티바이트 데이터를 메모리에 저장하는 순서를 말한다고 함.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 TypedArray를 사용해야 할까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용하면 좋은 경우&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;바이너리 데이터 처리&lt;/b&gt;: 파일, 이미지, 오디오&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대용량 숫자 배열&lt;/b&gt;: 과학 계산, 데이터 분석&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 효율성이 중요한 경우&lt;/b&gt;: 게임, 실시간 애플리케이션&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WebGL/Canvas 작업&lt;/b&gt;: 3D 그래픽, 이미지 처리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 통신&lt;/b&gt;: Protocol Buffers, 바이너리 API&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일반 배열을 사용하는 경우&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다양한 타입이 섞인 데이터&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문자열과 숫자가 함께 있는 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작은 크기의 배열&lt;/b&gt; (수십 개 이하)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복잡한 객체를 저장하는 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypedArray는 JavaScript에서 바이너리 데이터와 대용량 숫자 배열을 효율적으로 처리할 수 있다. 특히 웹에서 파일 처리, 이미지 조작, 3D 그래픽, 실시간 데이터 처리 등의 작업을 할 때 필수.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 효율성&lt;/b&gt;: 일반 배열보다 8배 적은 메모리 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 향상&lt;/b&gt;: 2-3배 빠른 처리 속도&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타입 안정성&lt;/b&gt;: 정해진 범위의 값만 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바이너리 호환성&lt;/b&gt;: C/C++ 등 다른 언어와 데이터 교환 용이&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/155</guid>
      <comments>https://wooncloud.tistory.com/155#entry155comment</comments>
      <pubDate>Sat, 2 Aug 2025 18:46:14 +0900</pubDate>
    </item>
    <item>
      <title>protobufjs로 JSON보다 10배 빠른 데이터 통신하기</title>
      <link>https://wooncloud.tistory.com/154</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWUaQB/btsPFYj4WDz/U7aK2xEdjlbtOXS2uyoukk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWUaQB/btsPFYj4WDz/U7aK2xEdjlbtOXS2uyoukk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWUaQB/btsPFYj4WDz/U7aK2xEdjlbtOXS2uyoukk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWUaQB%2FbtsPFYj4WDz%2FU7aK2xEdjlbtOXS2uyoukk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;640&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/protobufjs/protobuf.js/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/protobufjs/protobuf.js/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754125522514&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - protobufjs/protobuf.js: Protocol Buffers for JavaScript &amp;amp; TypeScript.&quot; data-og-description=&quot;Protocol Buffers for JavaScript &amp;amp; TypeScript. Contribute to protobufjs/protobuf.js development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/protobufjs/protobuf.js/&quot; data-og-url=&quot;https://github.com/protobufjs/protobuf.js&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/e3TG5/hyZrrc3d83/lJvAexKyXqhr4Apu9HPv6K/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/GYEgI/hyZqPyqj99/KA9IgadlRfOQLvXdbzEQj0/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/protobufjs/protobuf.js/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/protobufjs/protobuf.js/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/e3TG5/hyZrrc3d83/lJvAexKyXqhr4Apu9HPv6K/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/GYEgI/hyZqPyqj99/KA9IgadlRfOQLvXdbzEQj0/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - protobufjs/protobuf.js: Protocol Buffers for JavaScript &amp;amp; TypeScript.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Protocol Buffers for JavaScript &amp;amp; TypeScript. Contribute to protobufjs/protobuf.js development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Protocol Buffers란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google이 개발한 &lt;b&gt;언어 중립적 데이터 직렬화 형식&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSON/XML보다 &lt;b&gt;3-10배 빠름&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;더 작은 크기&lt;/b&gt;로 데이터 전송&lt;/li&gt;
&lt;li&gt;성능 중요한 애플리케이션에서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;.proto 파일 구조&lt;/h3&gt;
&lt;pre class=&quot;protobuf&quot;&gt;&lt;code&gt;package ecommerce;  // 네임스페이스

message Product {
 required string product_id = 1;         // 필수: 상품 ID
 required string name = 2;               // 필수: 상품명
 optional string description = 3;        // 선택적: 상품 설명
 required double price = 4;              // 필수: 가격
 optional bool is_available = 5;         // 선택적: 판매 가능 여부
 repeated string categories = 6;         // 배열: 카테고리 목록
 optional int32 stock_quantity = 7;      // 선택적: 재고 수량
 repeated string image_urls = 8;         // 배열: 이미지 URL 목록
 optional int64 created_timestamp = 9;   // 선택적: 생성 시간
 optional bytes thumbnail = 10;          // 선택적: 썸네일 이미지 데이터
 repeated string tags = 11;              // 배열: 태그 목록
 optional float rating = 12;             // 선택적: 평점
 required string seller_id = 13;         // 필수: 판매자 ID
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;필드 타입&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;required&lt;/code&gt;: 반드시 필요한 필드&lt;/li&gt;
&lt;li&gt;&lt;code&gt;optional&lt;/code&gt;: 선택적 필드&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repeated&lt;/code&gt;: 배열/리스트 필드&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 타입&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;string&lt;/code&gt;, &lt;code&gt;int32&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;bytes&lt;/code&gt; 등&lt;/li&gt;
&lt;li&gt;각 필드는 &lt;b&gt;고유 번호&lt;/b&gt; 필수 (&lt;code&gt;= 1&lt;/code&gt;, &lt;code&gt;= 2&lt;/code&gt;, ...)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JavaScript 사용법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 설치&lt;/h4&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install protobufjs&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 기본 사용&lt;/h4&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;const protobuf = require(&quot;protobufjs&quot;);

// .proto 파일 로드
const root = protobuf.loadSync(&quot;proto/ecommerce.proto&quot;);
const Product = root.lookupType(&quot;ecommerce.Product&quot;);

// 메시지 생성
const message = Product.create({
 product_id: &quot;PROD-12345&quot;,
 name: &quot;무선 블루투스 이어폰&quot;,
 description: &quot;고음질 노이즈 캔슬링 기능&quot;,
 price: 129.99,
 is_available: true,
 categories: [&quot;전자제품&quot;, &quot;오디오&quot;, &quot;이어폰&quot;],
 stock_quantity: 50,
 image_urls: [
   &quot;https://example.com/img1.jpg&quot;,
   &quot;https://example.com/img2.jpg&quot;
 ],
 created_timestamp: Date.now(),
 tags: [&quot;블루투스&quot;, &quot;노이즈캔슬링&quot;, &quot;무선&quot;],
 rating: 4.5,
 seller_id: &quot;SELLER-789&quot;
});

// 직렬화 (바이너리로 변환)
const buffer = Product.encode(message).finish();

// 역직렬화 (바이너리에서 객체로)
const decoded = Product.decode(buffer);
console.log(decoded.name); // &quot;무선 블루투스 이어폰&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;table style=&quot;height: 100px; width: 420px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px; width: 85px;&quot;&gt;특징&lt;/th&gt;
&lt;th style=&quot;height: 20px; width: 99px;&quot;&gt;JSON&lt;/th&gt;
&lt;th style=&quot;height: 20px; width: 236px;&quot;&gt;Protocol Buffers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 85px;&quot;&gt;&lt;b&gt;속도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 99px;&quot;&gt;기준&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 236px;&quot;&gt;&lt;b&gt;3-10배 빠름&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 85px;&quot;&gt;&lt;b&gt;크기&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 99px;&quot;&gt;기준&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 236px;&quot;&gt;&lt;b&gt;더 작음&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 85px;&quot;&gt;&lt;b&gt;타입 안전성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 99px;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 236px;&quot;&gt;&lt;b&gt;있음&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 85px;&quot;&gt;&lt;b&gt;스키마 진화&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 99px;&quot;&gt;어려움&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 236px;&quot;&gt;&lt;b&gt;쉬움&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://protobuf.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://protobuf.dev/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754125816709&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Protocol Buffers&quot; data-og-description=&quot;Protocol Buffers are language-neutral, platform-neutral extensible mechanisms for serializing structured data.&quot; data-og-host=&quot;protobuf.dev&quot; data-og-source-url=&quot;https://protobuf.dev/&quot; data-og-url=&quot;https://protobuf.dev/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://protobuf.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://protobuf.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Protocol Buffers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Protocol Buffers are language-neutral, platform-neutral extensible mechanisms for serializing structured data.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;protobuf.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>json</category>
      <category>Protobuf</category>
      <category>protocal buffer</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/154</guid>
      <comments>https://wooncloud.tistory.com/154#entry154comment</comments>
      <pubDate>Sat, 2 Aug 2025 18:04:33 +0900</pubDate>
    </item>
    <item>
      <title>Claude Code Usage Monitor for macOS - 메뉴바에서 클로드 토큰 사용량 실시간 모니터링</title>
      <link>https://wooncloud.tistory.com/153</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HWseE/btsPDlAyUEQ/kcPikZ8dwH5QPkiekZ8Zyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HWseE/btsPDlAyUEQ/kcPikZ8dwH5QPkiekZ8Zyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HWseE/btsPDlAyUEQ/kcPikZ8dwH5QPkiekZ8Zyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHWseE%2FbtsPDlAyUEQ%2FkcPikZ8dwH5QPkiekZ8Zyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;606&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code를 쓰다 보면 토큰 사용량이 궁금한데, 이걸 macOS 메뉴바에서 실시간으로 볼 수 있게 해주는 앱이다. Swift로 만들어진 네이티브 macOS 앱이고, 원래 Python 버전(&lt;a href=&quot;https://github.com/Maciek-roboblog/Claude-Code-Usage-Monitor&quot;&gt;Claude-Code-Usage-Monitor&lt;/a&gt;)을 macOS용으로 포팅한 거라고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 기능들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실시간 모니터링&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;6초마다 업데이트&lt;/li&gt;
&lt;li&gt;토큰 사용량을 색깔별로 표시 (초록/노랑/빨강)&lt;/li&gt;
&lt;li&gt;메뉴바에 사용률 퍼센테이지랑 번레이트 이모지 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;번레이트 추적&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최근 1시간 기준으로 얼마나 빠르게 토큰을 쓰고 있는지 보여줌&lt;/li&gt;
&lt;li&gt;이모지로 속도 표시:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;  100 토큰/분 미만 (여유로움)&lt;/li&gt;
&lt;li&gt;  100-300 토큰/분 (보통)&lt;/li&gt;
&lt;li&gt;  300-600 토큰/분 (좀 빠름)&lt;/li&gt;
&lt;li&gt;  600-1000 토큰/분 (꽤 빠름)&lt;/li&gt;
&lt;li&gt;✈️ 1000-2000 토큰/분 (빠름)&lt;/li&gt;
&lt;li&gt;  2000 토큰/분 초과 (매우 빠름)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스마트 예측&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 속도로 계속 쓰면 언제 토큰이 떨어질지 예측&lt;/li&gt;
&lt;li&gt;플랜 자동 감지 (Pro/Max5/Max20)&lt;/li&gt;
&lt;li&gt;세션 리셋 시간 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치 방법&lt;/h2&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;# 레포 클론  
git clone https://github.com/Sapeet/claude-code-usage-monitor-macos.git  
cd claude-code-usage-monitor-macos  

# 빌드하고 앱 번들 생성  
make bundle  

# Applications 폴더에 복사  
cp -r &quot;output/Claude Code Usage Monitor.app&quot; /Applications/  &lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떻게 작동하나?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code가 만드는 JSONL 파일들을 읽어서 사용량을 계산한다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 경로: &lt;code&gt;~/.claude/projects/*.jsonl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;환경변수 &lt;code&gt;CLAUDE_DATA_PATHS&lt;/code&gt;로 다른 경로도 설정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5시간 세션 윈도우 기준으로 토큰 사용량을 추적하고, 최근 1시간 번레이트를 기반으로 예측한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시스템 요구사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;macOS 13.0 이상&lt;/li&gt;
&lt;li&gt;Swift 5.9 이상&lt;/li&gt;
&lt;li&gt;Claude Code 사용 중이어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생각해볼 점들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메뉴바에서 바로 확인 가능해서 편함&lt;/li&gt;
&lt;li&gt;실시간 업데이트로 현재 상황 파악하기 좋음&lt;/li&gt;
&lt;li&gt;이모지로 직관적인 속도 표시&lt;/li&gt;
&lt;li&gt;네이티브 앱이라 성능 좋을 듯&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;macOS만 지원 (윈도우/리눅스는 원본 Python 버전 써야 함)&lt;/li&gt;
&lt;li&gt;Claude Code 써야만 작동함&lt;/li&gt;
&lt;li&gt;아직 개발 초기 단계인 것 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code 많이 쓰는 사람한테는 꽤 유용할 것 같다. 특히 토큰 사용량 신경 쓰면서 작업하는 경우에는 메뉴바에서 바로 확인할 수 있어서 좋을 듯. 다만 아직 새로운 프로젝트라 버그나 이슈가 있을 수 있으니 주의해서 써야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 Python 버전도 있으니 다른 OS 쓰는 사람들은 그쪽 확인해보면 될 것 같고.&lt;/p&gt;</description>
      <category>개발 아카이브/개발 관련 지식</category>
      <category>Ai</category>
      <category>Claude</category>
      <category>claude code</category>
      <category>클로드</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/153</guid>
      <comments>https://wooncloud.tistory.com/153#entry153comment</comments>
      <pubDate>Thu, 31 Jul 2025 14:00:47 +0900</pubDate>
    </item>
    <item>
      <title>크롬 AI 번역 API - Translator API</title>
      <link>https://wooncloud.tistory.com/151</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5hwn5/btsPxLGjYeY/ZokhHNksyTpQeuKNVDUu6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5hwn5/btsPxLGjYeY/ZokhHNksyTpQeuKNVDUu6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5hwn5/btsPxLGjYeY/ZokhHNksyTpQeuKNVDUu6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5hwn5%2FbtsPxLGjYeY%2FZokhHNksyTpQeuKNVDUu6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;240&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용은 포스팅 시각 기준 실험적인 기능입니다. 그래도 좋은 기능이 나와서 포스팅합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능은 MDN에 포스팅 되어 있지만, 크롬에서만 선두적으로 개발해둔 실험적 기능이라 다른 브라우저에서는 지원하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 크롬 기반에서는 쉽게 번역 기능을 구현할 수 있겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Translator&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/Translator&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753374149260&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Translator - Web APIs | MDN&quot; data-og-description=&quot;The Translator interface of the Translator and Language Detector APIs contains all the associated translation functionality, including checking AI model availability, creating a new Translator instance, using it to create a translation, and more.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Translator&quot; data-og-url=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Translator&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/brQRdj/hyZqSG6eqj/9sjwo6COK7rDcHy2Zkc1mK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Translator&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Translator&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/brQRdj/hyZqSG6eqj/9sjwo6COK7rDcHy2Zkc1mK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Translator - Web APIs | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Translator interface of the Translator and Language Detector APIs contains all the associated translation functionality, including checking AI model availability, creating a new Translator instance, using it to create a translation, and more.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Translator&amp;nbsp;API는&amp;nbsp;AI&amp;nbsp;모델&amp;nbsp;가용성&amp;nbsp;확인,&amp;nbsp;번역기&amp;nbsp;인스턴스&amp;nbsp;생성,&amp;nbsp;텍스트&amp;nbsp;번역&amp;nbsp;등의&amp;nbsp;기능을&amp;nbsp;제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;주요&amp;nbsp;속성&amp;nbsp;및&amp;nbsp;메서드&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인스턴스 속성&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;inputQuota: 번역을 위해 브라우저에서 사용 가능한 입력 할당량&lt;/li&gt;
&lt;li&gt;sourceLanguage: 번역할 텍스트의 원본 언어&lt;/li&gt;
&lt;li&gt;targetLanguage:&amp;nbsp;번역될&amp;nbsp;대상&amp;nbsp;언어&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정적 메서드&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;availability(): AI 모델의 가용성 확인&lt;/li&gt;
&lt;li&gt;create(): 새로운 Translator 인스턴스 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인스턴스 메서드&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;translate(): 입력 문자열을 번역하여 결과 문자열 반환&lt;/li&gt;
&lt;li&gt;translateStreaming(): 스트림 형태로 번역 결과를 점진적으로 생성&lt;/li&gt;
&lt;li&gt;measureInputUsage(): 특정 텍스트 번역에 필요한 입력 할당량 측정&lt;/li&gt;
&lt;li&gt;destroy(): 번역기 인스턴스 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1753374811687&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 번역기 생성 (영어 &amp;rarr; 일본어)
const translator = await Translator.create({
  sourceLanguage: &quot;en&quot;,
  targetLanguage: &quot;ja&quot;,
});

// 일반 번역
const translation = await translator.translate(myTextString);

// 스트리밍 번역
const stream = translator.translateStreaming(myTextString);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API로 서버없이 브라우저에서 직접 AI기반 번역을 구현할 수 있습니다. 그래서 개인정보 보호나 응답속도가 빨라서 좋네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 데브킷에 넣어봐야지 ㅎㅎ&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>MDN</category>
      <category>Translator API</category>
      <category>번역</category>
      <category>크롬</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/151</guid>
      <comments>https://wooncloud.tistory.com/151#entry151comment</comments>
      <pubDate>Fri, 25 Jul 2025 01:41:19 +0900</pubDate>
    </item>
    <item>
      <title>ASCII Art - 터미널에 힙한 텍스트 그림 넣기</title>
      <link>https://wooncloud.tistory.com/150</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wd7rb/btsPpUwUJMc/kLIqfIth3qQPiOsD776kT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wd7rb/btsPpUwUJMc/kLIqfIth3qQPiOsD776kT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wd7rb/btsPpUwUJMc/kLIqfIth3qQPiOsD776kT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwd7rb%2FbtsPpUwUJMc%2FkLIqfIth3qQPiOsD776kT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;500&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 oh my zsh를 설치하면서, 터미널을 설치하면 위와 같은 아스키 아트가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 회사에서도 flow api 서버에서는 터미널에서 서버를 실행하면 위처럼 아스키 아트가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것보고 내가 예전에 LLM 서버에도 적용한적 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gjs5y/btsPqPORWXS/BqxK5iGOmYFRlRcVgWVmu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gjs5y/btsPqPORWXS/BqxK5iGOmYFRlRcVgWVmu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gjs5y/btsPqPORWXS/BqxK5iGOmYFRlRcVgWVmu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGjs5y%2FbtsPqPORWXS%2FBqxK5iGOmYFRlRcVgWVmu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;242&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단간단하게 만들었는데, 도커 터미널에서 보니 뭔가 깨지는 느낌이 들어서 아쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 서버에서 보면 예쁘게 잘 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 많은곳의 터미널이 예뻐지고 힙해지길 바라며, 이 아스키 아트를 만들 수 있는 사이트를 공유해볼까 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://patorjk.com/software/taag/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://patorjk.com/software/taag/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753018675425&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Text to ASCII Art Generator (TAAG)&quot; data-og-description=&quot;&quot; data-og-host=&quot;patorjk.com&quot; data-og-source-url=&quot;https://patorjk.com/software/taag/&quot; data-og-url=&quot;https://patorjk.com/software/taag/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://patorjk.com/software/taag/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://patorjk.com/software/taag/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Text to ASCII Art Generator (TAAG)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;patorjk.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 링크로 들어가도 좋고, 구글에 ASCII Art Generator 라고 치면 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mQGnX/btsPpyAPt8T/ZbKU0QloeRUyhIrjqTwZu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mQGnX/btsPpyAPt8T/ZbKU0QloeRUyhIrjqTwZu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mQGnX/btsPpyAPt8T/ZbKU0QloeRUyhIrjqTwZu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmQGnX%2FbtsPpyAPt8T%2FZbKU0QloeRUyhIrjqTwZu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1344&quot; height=&quot;498&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법은 별것 없고, 그냥 텍스트를 위에 입력하면, 아래 textarea에 아스키 아트를 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 복사해서 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1089&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z9IGS/btsPqRFWfvA/onDWFsiaVqOASiVbjxXUfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z9IGS/btsPqRFWfvA/onDWFsiaVqOASiVbjxXUfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z9IGS/btsPqRFWfvA/onDWFsiaVqOASiVbjxXUfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz9IGS%2FbtsPqRFWfvA%2FonDWFsiaVqOASiVbjxXUfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1089&quot; height=&quot;335&quot; data-origin-width=&quot;1089&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;font를 고르면 엄청나게 많은 폰트가 나오는데 취향것 고르면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;</description>
      <category>정보/유용한 사이트</category>
      <category>ASCII</category>
      <category>ascii art</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/150</guid>
      <comments>https://wooncloud.tistory.com/150#entry150comment</comments>
      <pubDate>Sun, 20 Jul 2025 22:44:43 +0900</pubDate>
    </item>
    <item>
      <title>FossFlow - Isometric Diagramming Tool</title>
      <link>https://wooncloud.tistory.com/149</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YKjRo/btsPggy4pZg/8e2rbnd1FKVLgUOJtUtKP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YKjRo/btsPggy4pZg/8e2rbnd1FKVLgUOJtUtKP0/img.png&quot; data-alt=&quot;FossFlow 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YKjRo/btsPggy4pZg/8e2rbnd1FKVLgUOJtUtKP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYKjRo%2FbtsPggy4pZg%2F8e2rbnd1FKVLgUOJtUtKP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;536&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FossFlow 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;FossFlow는 워크플로우를 쉽고 직관적으로 관리할 수 있게 도와주는 오픈소스 툴이다. 처음엔 가볍게 둘러보려는 마음으로 살펴봤는데, 예상보다 더 깔끔하고 직관적인 인터페이스가 인상적이었다. 보통 워크플로우 관리 도구는 처음에 설정이 복잡하거나 학습이 오래 걸리는 경우가 많은데, FossFlow는 그런 어려움 없이 금방 사용할 수 있었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사용 경험 및 주요 기능&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발자 문서를 만들때 유용하겠다 싶었다. 보통 excalidraw를 이용해서 다이어그램을 그리거나 아키텍처를 그렸는데, 특히 아키텍처 그림을 그릴때 FossFlow 쓰는게 좋을것 같다고 생각이 들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IVwAa/btsPe65DrRs/562tWfP3H9ppt6bf1MVhlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IVwAa/btsPe65DrRs/562tWfP3H9ppt6bf1MVhlk/img.png&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;596&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.6422%; margin-right: 10px;&quot; data-widthpercent=&quot;49.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IVwAa/btsPe65DrRs/562tWfP3H9ppt6bf1MVhlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIVwAa%2FbtsPe65DrRs%2F562tWfP3H9ppt6bf1MVhlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;332&quot; height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAMYSw/btsPghdCn5C/ZoCYH5NYKH5vM5RqNOZPh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAMYSw/btsPghdCn5C/ZoCYH5NYKH5vM5RqNOZPh1/img.png&quot; data-origin-width=&quot;338&quot; data-origin-height=&quot;588&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.195%;&quot; data-widthpercent=&quot;50.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAMYSw/btsPghdCn5C/ZoCYH5NYKH5vM5RqNOZPh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAMYSw%2FbtsPghdCn5C%2FZoCYH5NYKH5vM5RqNOZPh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;338&quot; height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;제공해주는 이미지가 많아서 특히 좋은것 같다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용성에 있어서 드래그 앤 드롭 방식으로 간편하게 노드를 추가하거나 연결할 수 있는 부분이 매우 편리했다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;복잡한 프로세스를 설정할 때도 흐름이 명확하게 시각화되어 있어서, 한눈에 전체 과정을 파악할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 뭔가 인터페이스가 엑스칼리드로랑 비슷한것 같기도 하다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이것도 어디서 제공해주는게 있는건가?&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 사용하면서 좋았던 점을 요약하면 다음과 같다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3D 다이어그램에 보기 좋은 결과물이 나온다.&lt;/li&gt;
&lt;li&gt;설정 메뉴가 단순하고 직관적이라 적응하기 쉬웠다.&lt;/li&gt;
&lt;li&gt;다양한 옵션을 통해 원하는 대로 쉽게 커스터마이징이 가능했다.&lt;/li&gt;
&lt;li&gt;인터페이스가 가벼워서 성능도 빠르고 부드러웠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;FossFlow의 활용 방안&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트가 언라이선스(Unlicense)로 배포되고 있어 회사 내부 서비스에도 부담 없이 적용할 수 있을 것으로 보인다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/stan-smith/FossFLOW?tab=Unlicense-1-ov-file#readme&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/stan-smith/FossFLOW?tab=Unlicense-1-ov-file#readme&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 회사에서 업무 리뉴얼 한다고 이것저것 만들고 있는데, 업무 자동화에 대한 이야기가 나온다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최근 Make가 뜨면서 Make의 자동화 인터페이스가 엄청 시각적인것으로 알고 있는데, 자동화에 그런 시각적인 인터페이스를 제공한다면 FossFlow를 써보는것도 방법일 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/markmanx/isoflow&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/markmanx/isoflow&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1752301124558&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - markmanx/isoflow&quot; data-og-description=&quot;Contribute to markmanx/isoflow development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/markmanx/isoflow&quot; data-og-url=&quot;https://github.com/markmanx/isoflow&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/o9AXt/hyZjsCGSVD/PuumWvKKkqAjfWTFxcgPMK/img.png?width=1200&amp;amp;height=600&amp;amp;face=965_140_1045_228,https://scrap.kakaocdn.net/dn/cy81fa/hyZjtn4hcV/OREW9l48eTOhAvQvlRZ3jk/img.png?width=1200&amp;amp;height=600&amp;amp;face=965_140_1045_228&quot;&gt;&lt;a href=&quot;https://github.com/markmanx/isoflow&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/markmanx/isoflow&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/o9AXt/hyZjsCGSVD/PuumWvKKkqAjfWTFxcgPMK/img.png?width=1200&amp;amp;height=600&amp;amp;face=965_140_1045_228,https://scrap.kakaocdn.net/dn/cy81fa/hyZjtn4hcV/OREW9l48eTOhAvQvlRZ3jk/img.png?width=1200&amp;amp;height=600&amp;amp;face=965_140_1045_228');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - markmanx/isoflow&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to markmanx/isoflow development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 확인해보니 isoflow 라고 아이소메트릭(Isometric)한 그림을 그릴 수 있는 좋은 컴포넌트가 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;React 컴포넌트라서 굳이 도입하는데 FossFlow 말고 &lt;span style=&quot;text-align: start;&quot;&gt;isoflow써도 될 듯.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;기회되면 다음 포스트에 &lt;span style=&quot;text-align: start;&quot;&gt;isoflow에 대해 써보겠다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>정보/유용한 사이트</category>
      <category>fossflow</category>
      <category>isoflow</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/149</guid>
      <comments>https://wooncloud.tistory.com/149#entry149comment</comments>
      <pubDate>Sat, 12 Jul 2025 15:20:46 +0900</pubDate>
    </item>
    <item>
      <title>iCalendar 기본 문법</title>
      <link>https://wooncloud.tistory.com/148</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGexM/btsL48DFw1H/xjsFSsTojqvf39DgdeeeE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGexM/btsL48DFw1H/xjsFSsTojqvf39DgdeeeE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGexM/btsL48DFw1H/xjsFSsTojqvf39DgdeeeE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGexM%2FbtsL48DFw1H%2FxjsFSsTojqvf39DgdeeeE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;250&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 캘린더 기능을 만들게 되면서, 구현하다 보면 반드시 만나게 되는 것이 있습니다. 그건 바로 iCal(iCalendar) 문법이라는건데, 연동을 생각하는 캘린더 서비스를 만든다면 무조건 알아야 할 내용입니다. 각 캘린더 서비스들이 서로 연동을 위해 iCal이라는 특별한 형식의 데이터를 주고 받습니다. 실제로 많은 문법과 기능들이 있는데, 이번 포스트에는 가볍게 다뤄보겠습니다. 나중에 기회되면 더 자세하게 다뤄보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 iCal을 알아야 할까?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 캘린더 시스템이 iCal 형식으로 데이터를 주고받습니다. 다른 형식도 있지만 대체적으로 iCal을 사용합니다.&lt;/li&gt;
&lt;li&gt;구글 캘린더, 애플 캘린더 등과의 연동이 필요한 경우 필수적입니다.&lt;/li&gt;
&lt;li&gt;표준화된 포맷으로, 한 번 익혀두면 다양한 프로젝트에서 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;CalDAV, iTIP 등의 프로토콜과 연동이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;iCal의 기본 구조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 iCal 문법을 접하면 조금 복잡해 보일 수 있습니다. 이건 뭐 json도 xml 형식도 아니고.. 좀 자기만의 특별한 형식을 가지고 있습니다. 하지만 기본 구조만 이해하면 생각보다 단순합니다. 가장 기본이 되는 구성 요소들을 하나씩 살펴보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. VCALENDAR&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 iCal 데이터는 VCALENDAR로 시작합니다. 이것은 마치 HTML의 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 태그와 비슷한 역할을 한다고 보면 됩니다.&lt;br /&gt;그런데 앞에 왜 V가 붙는지는 모르겠네요.. 앞으로 나올 애들도 다 V로 시작하는데 이유를 모르겠슴다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
... 캘린더 내용 ...
END:VCALENDAR&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 주요 구성 요소&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더 안에는 다양한 종류의 일정 정보를 담을 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;VEVENT&lt;/b&gt;: 가장 많이 사용되는 요소로, 회의나 약속 같은 일정을 표현.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VTODO&lt;/b&gt;: 할 일 목록을 관리할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VALARM&lt;/b&gt;: 알림 설정을 담당.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VJOURNAL&lt;/b&gt;: 일지나 메모 같은 정보를 기록할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VFREEBUSY&lt;/b&gt;: 일정 가능한 시간대를 관리할 때 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 형식이 있는데, 저는 VEVENT와 VALARM만 사용할 것 같습니다. TODO 앱 같은 다양한 서비스를 만드는데 필요한 다른 형식도 제공해주는 것 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;날짜와 시간 표현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더에서 가장 중요한 것이 바로 날짜와 시간을 정확하게 표현하는 것입니다. iCal에서는 이를 매우 체계적으로 관리합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본 형식&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;YYYYMMDDTHHmmSSZ&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생긴 형식은 처음 보면 이해하기 어려울 수 있습니다. 하나씩 뜯어보면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;YYYY: 연도&lt;/li&gt;
&lt;li&gt;MM: 월&lt;/li&gt;
&lt;li&gt;DD: 일&lt;/li&gt;
&lt;li&gt;T: 시간 구분자&lt;/li&gt;
&lt;li&gt;HH: 시&lt;/li&gt;
&lt;li&gt;mm: 분&lt;/li&gt;
&lt;li&gt;SS: 초&lt;/li&gt;
&lt;li&gt;Z: UTC 시간대&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;20240315T143000Z  // 2024년 3월 15일 14시 30분 (UTC)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시간대 처리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 캘린더 개발에서 가장 까다로웠던 부분이 바로 시간대 처리였습니다. 특히 글로벌 서비스를 개발할 때는 더욱 신경써줘야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. UTC와 로컬 시간&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 한국에서 열리는 회의를 등록할 때는 이렇게 표현할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;BEGIN:VEVENT
DTSTART;TZID=Asia/Seoul:20240316T180000
DTEND;TZID=Asia/Seoul:20240316T190000
SUMMARY:로컬 시간 이벤트
END:VEVENT&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 시간대 정의&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 시간대에 대한 정보도 명확하게 정의할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;BEGIN:VTIMEZONE
TZID:Asia/Seoul
BEGIN:STANDARD
DTSTART:19700101T000000
TZOFFSETFROM:+0900
TZOFFSETTO:+0900
END:STANDARD
END:VTIMEZONE&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;반복 규칙 (RRULE)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복 규칙은 iCal의 가장 강력한 기능 중 하나입니다. 단순한 반복부터 복잡한 패턴까지 거의 모든 종류의 반복 일정을 표현할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 기본적인 반복 패턴&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 매주 월요일마다 반복
RRULE:FREQ=WEEKLY;BYDAY=MO

# 매월 15일마다 반복
RRULE:FREQ=MONTHLY;BYMONTHDAY=15

# 매년 1월 1일마다 반복
RRULE:FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=1&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 복잡한 반복 패턴&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 업무에서는 이런 복잡한 패턴도 자주 사용됩니다:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 매월 마지막 금요일
RRULE:FREQ=MONTHLY;BYDAY=-1FR

# 분기별 첫 번째 월요일 (1,4,7,10월)
RRULE:FREQ=MONTHLY;INTERVAL=3;BYDAY=1MO

# 평일마다 반복 (월-금)
RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 복잡해 보이는 iCal 문법이지만, 체계적인 구조를 가지고 있고 많은 기능들이 있습니다. 이 문법을 이해하고 있으면 다양한 캘린더 시스템과의 연동이 가능합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc5545&quot;&gt;RFC 5545 - iCalendar 스펙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://icalendar.org/&quot;&gt;iCalendar 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 아카이브/개발 관련 지식</category>
      <category>Calendar</category>
      <category>iCal</category>
      <category>iCalendar</category>
      <category>캘린더</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/148</guid>
      <comments>https://wooncloud.tistory.com/148#entry148comment</comments>
      <pubDate>Sat, 1 Feb 2025 18:35:52 +0900</pubDate>
    </item>
    <item>
      <title>Google Apps Script에 ChatGPT 연동하기</title>
      <link>https://wooncloud.tistory.com/147</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구글 앱 스크립트 (Google Apps Script)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 앱 스크립트는 JavaScript 기반의 클라우드 스크립팅 언어로, 구글 워크스페이스 제품군(Google Sheets, Docs, Forms 등)을 자동화할 수 있는 도구입니다. 이를 통해 사용자는 다양한 작업을 자동화하고, 반복적인 작업을 쉽게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구글 앱 스크립트로 스프레드시트 자동화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 스프레드시트에서 반복적인 데이터 입력이나 복잡한 데이터 처리 작업은 시간이 많이 소요될 수 있습니다. 구글 앱 스크립트를 사용하면 이러한 작업을 자동화하여 생산성을 높일 수 있습니다.&lt;br /&gt;예를 들어, 데이터를 자동으로 정리하거나 특정 조건에 따라 데이터를 필터링하는 스크립트를 작성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 스프레드시트 자동화에 ChatGPT를 사용하면 더욱 유용할 것입니다. 그래서 앱 스크립트에 사용할 간단한 ChatGPT 연동 함수를 만들어봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구글 앱 스크립트 &amp;amp; ChatGPT 연동 유틸 함수&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1721753679372&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function getOpenAIResponse(prompt) {
  // 시스템 프롬프트 정의
  const systemPrompt = `
    여기에 시스템 프롬프트를 입력합니다.
  `;
  
  // 메시지 배열 생성
  const message = [
    { role: &quot;system&quot;, content: systemPrompt },  // 시스템 메시지
    { role: &quot;user&quot;, content: prompt }           // 사용자 프롬프트
  ];

  // API 요청 URL 정의
  const url = 'https://api.openai.com/v1/chat/completions';
  
  // API 요청 옵션 설정
  const options = {
    'method': 'post',                          // HTTP 메서드
    'contentType': 'application/json',         // 콘텐츠 타입
    'headers': {
      'Authorization': `Bearer ${OPENAI_API_KEY}` // API 키 포함
    },
    'payload': JSON.stringify({
      'model': 'gpt-4o-mini-2024-07-18',       // 최신 모델 사용
      'messages': message,                     // 메시지 배열 포함
      'temperature': 0.7                       // 온도 설정
    })
  };
  
  // API 요청 보내기
  const response = UrlFetchApp.fetch(url, options);
  
  // 응답 JSON 파싱
  const json = JSON.parse(response.getContentText());
  
  // 응답에서 내용 추출 및 반환
  if (json &amp;amp;&amp;amp; json.choices &amp;amp;&amp;amp; json.choices.length &amp;gt; 0) {
    return json.choices[0]?.message?.content?.trim() || &quot;&quot;; // 첫 번째 선택지의 내용 반환
  }
  
  // 응답이 없을 경우 null 반환
  return null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 앱 스크립트에서 사용할 GPT 통신 유틸 함수입니다. 위의 함수를 이용하여 ChatGPT를 통해 다양한 앱 스크립트 자동화를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 다국어 번역이 되어 있는 스프레드시트에 위의 함수를 사용했습니다. 다국어는 여러 언어로 번역된 문장들이 있으며, 사람이 스프레드시트에 실수를 하여 내용을 잘못 입력하거나, 오역을 할 수 있습니다. 이러한 실수를 사람이 찾는 것은 인력 낭비이기 때문에, GPT를 이용하여 자동화를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예시코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1721753712605&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const col = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;E&quot;, &quot;F&quot;, &quot;G&quot;, &quot;H&quot;, &quot;I&quot;, &quot;J&quot;, &quot;K&quot;, &quot;L&quot;];
const lang = ['Language code', 'Korean', 'US', 'English', 'Japanese', 'Vietnamese', 'Spanish', 'Deutsch', 'French', 'Portuguese', 'Chinese(Simplified)', 'Chinese(Traditional)'];

// 번역 체크 함수.
function checkTranslations() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const data = sheet.getDataRange().getValues();
  
  const koreanColumn = 1; // B열 (index 1)

  for (let i = 1; i &amp;lt; data.length; i++) {
    const koreanText = data[i][koreanColumn];
    
    for (let j = 2; j &amp;lt; col.length; j++) { // C부터 L열까지
      const translatedText = data[i][j];
      const language = lang[j];
      
      // OpenAI API 호출을 통해 번역 검토
      const prompt = `The Korean sentence &quot;${koreanText}&quot; is translated to ${language} as &quot;${translatedText}&quot;`;
      const correctedTranslation = getOpenAIResponse(prompt);
      
      // 번역이 일치하지 않는 경우 콘솔에 표시
      if (correctedTranslation &amp;amp;&amp;amp; correctedTranslation.toUpperCase() !== &quot;YES&quot;) {
        console.log(`[정합성 확인 필요] - 위치: ${col[j]}${i + 1} | 내용: (${language}) - ${koreanText} / ${translatedText}`);
      }
    }
  }
}

// 유틸 함수
function getOpenAIResponse(prompt) {
  const systemPrompt = `
    You will receive Korean sentences and other language sentences from users. You should check whether the translated sentence is empty or completely incorrect. Even if the translation is slightly different, if a similar meaning can be conveyed, it has been translated and should be judged as 'YES'. If it is judged to have been paraphrased, the answer is &amp;lsquo;YES&amp;rsquo;. If it is completely unrelated, the answer is 'NO'. You must only answer YES or NO.
  `;
  const message = [
    { role: &quot;system&quot;, content: systemPrompt },
    { role: &quot;assistant&quot;, content: `The Korean sentence &quot;내용을 확인하려면 앱 잠금을 해제하세요&quot; is translated to English as &quot;Please check your file extension.&quot;` },
    { role: &quot;assistant&quot;, content: &quot;NO&quot;},
    { role: &quot;assistant&quot;, content: `The Korean sentence &quot;내용을 확인하려면 앱 잠금을 해제하세요&quot; is translated to English as &quot;Unlock your app to view message&quot;.` },
    { role: &quot;assistant&quot;, content: &quot;YES&quot;},
    { role: &quot;assistant&quot;, content: `The Korean sentence &quot;내용을 확인하려면 앱 잠금을 해제하세요&quot; is translated to English as &quot;&quot;.` },
    { role: &quot;assistant&quot;, content: &quot;NO&quot;},
    { role: &quot;assistant&quot;, content: `The Korean sentence &quot;내용을 확인하려면 앱 잠금을 해제하세요&quot; is translated to English as &quot;내용을 확인하려면 앱 잠금을 해제하세요&quot;.` },
    { role: &quot;assistant&quot;, content: &quot;NO&quot;},
    { role: &quot;user&quot;, content: prompt }
  ];

  const url = 'https://api.openai.com/v1/chat/completions';
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'headers': {
      'Authorization': `Bearer ${OPENAI_API_KEY}`
    },
    'payload': JSON.stringify({
      'model': 'gpt-4o-mini-2024-07-18', // 최신 모델 사용
      'messages': message,
      'temperature': 0.7
    })
  };
  
  const response = UrlFetchApp.fetch(url, options);
  const json = JSON.parse(response.getContentText());
  
  if (json &amp;amp;&amp;amp; json.choices &amp;amp;&amp;amp; json.choices.length &amp;gt; 0) {
    return json.choices[0]?.message?.content?.trim() || &quot;&quot;;
  }
  
  return null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 한국어와 번역된 다른 언어를 GPT에게 보여주고, 번역이 제대로 이루어졌는지 검사하는 코드입니다. GPT가 어떻게 판단해야 하는지 시스템 프롬프트와 예시를 제공하여 &amp;lsquo;&lt;i&gt;*퓨샷 러닝&lt;/i&gt;&amp;rsquo;을 했습니다. 위의 코드를 통해 GPT가 스프레드시트의 빈 셀이나 &lt;i&gt;*언어 언매치&lt;/i&gt;, 오역, 의역들을 잡아낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(*언어 언매치: 프랑스어로 번역되어 들어가야 하는 셀에 한국어나 영어 등 다른 언어가 들어가 있는 경우)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(*퓨샷 러닝(Few-shot Learning): AI에게 매우 적은 수의 학습 데이터(샘플)만을 가지고도 원하는 답변을 이끌어 내는 기술 &lt;a title=&quot;참고&quot; href=&quot;https://velog.io/@euisuk-chung/%EC%83%9D%EC%84%B1-AI%EC%9D%98-%ED%95%99%EC%8A%B5-%EB%B0%A9%EC%8B%9D-%EC%A0%9C%EB%A1%9C%EC%83%B7%EC%9B%90%EC%83%B7%ED%93%A8%EC%83%B7-%EB%9F%AC%EB%8B%9D&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[참고]&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최적화에 대한 고찰&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 솔직히 최적화된 코드가 아닙니다. 셀 하나씩 GPT에게 비교하도록 요청하면 많은 토큰이 소비될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 빈 셀이나 언어 언매치 같은 경우는 GPT 없이 정규식을 이용하여 자동화 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 셀 하나씩 비교하지 않고, 한 row 단위로 정합성 검사를 요청하면 좀 더 토큰 낭비를 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시는 GPT를 이용한 개발 예시로 봐주시면 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝!&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>Apps script</category>
      <category>chatGPT</category>
      <category>자동화</category>
      <category>자바스크립트</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/147</guid>
      <comments>https://wooncloud.tistory.com/147#entry147comment</comments>
      <pubDate>Wed, 24 Jul 2024 02:06:39 +0900</pubDate>
    </item>
    <item>
      <title>fffuel - Free SVG generators, color tools &amp;amp; web design tools</title>
      <link>https://wooncloud.tistory.com/146</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.fffuel.co/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.fffuel.co/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721488194288&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Free SVG generators, color tools &amp;amp; web design tools&quot; data-og-description=&quot;  On fffuel you'll find a collection of free SVG makers to create cool backgrounds, seamless patterns, gradients, textures, shapes and blobs. Use the generated vector patterns directly on the web or in your favorite design app.  &amp;zwj;♂️ The SVG and&quot; data-og-host=&quot;www.fffuel.co&quot; data-og-source-url=&quot;https://www.fffuel.co/&quot; data-og-url=&quot;https://www.fffuel.co/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.fffuel.co/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.fffuel.co/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Free SVG generators, color tools &amp;amp; web design tools&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  On fffuel you'll find a collection of free SVG makers to create cool backgrounds, seamless patterns, gradients, textures, shapes and blobs. Use the generated vector patterns directly on the web or in your favorite design app.  &amp;zwj;♂️ The SVG and&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.fffuel.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹을 꾸미다가 SVG이면서 다양한 패턴이 필요해 찾다 좋은 사이트를 발견했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfspQx/btsIHUWsbYK/b8Td4KsCfFLtZZYUUh76KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfspQx/btsIHUWsbYK/b8Td4KsCfFLtZZYUUh76KK/img.png&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;929&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4267%; margin-right: 10px;&quot; data-widthpercent=&quot;50.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfspQx/btsIHUWsbYK/b8Td4KsCfFLtZZYUUh76KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfspQx%2FbtsIHUWsbYK%2Fb8Td4KsCfFLtZZYUUh76KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1097&quot; height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhFN3d/btsIIAQKYui/Ujh8I06hBZ8ajNmXrgBkd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhFN3d/btsIIAQKYui/Ujh8I06hBZ8ajNmXrgBkd1/img.png&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;931&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4105%;&quot; data-widthpercent=&quot;49.99&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhFN3d/btsIIAQKYui/Ujh8I06hBZ8ajNmXrgBkd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhFN3d%2FbtsIIAQKYui%2FUjh8I06hBZ8ajNmXrgBkd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1099&quot; height=&quot;931&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트에서 &quot;fffuel은 &lt;span style=&quot;text-align: start;&quot;&gt;그라데이션, 패턴, 텍스처, 모양 및 배경을 위한 색상 도구 및 무료 SVG 생성기 모음&quot;이라고 소개하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;써보면 써볼수록 웹디자인에 굉장히 강력한 툴이라서 진짜 강추합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;정말 다양한 표현을 할 수 있고, 유용한 툴도 많으며, 모두 svg로 내려받아 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;거기다 모두 무료.. 그런데 웬만한 유료 사이트보다 강력합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천추천!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1899&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUuSuZ/btsIHD1OyoB/r4eEvoerFFR3MNXahDyNNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUuSuZ/btsIHD1OyoB/r4eEvoerFFR3MNXahDyNNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUuSuZ/btsIHD1OyoB/r4eEvoerFFR3MNXahDyNNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUuSuZ%2FbtsIHD1OyoB%2Fr4eEvoerFFR3MNXahDyNNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1899&quot; height=&quot;922&quot; data-origin-width=&quot;1899&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1903&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG43Qq/btsIGiLmfYm/oF1AvhOtxEVPE3wBpng601/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG43Qq/btsIGiLmfYm/oF1AvhOtxEVPE3wBpng601/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG43Qq/btsIGiLmfYm/oF1AvhOtxEVPE3wBpng601/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG43Qq%2FbtsIGiLmfYm%2FoF1AvhOtxEVPE3wBpng601%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1903&quot; height=&quot;870&quot; data-origin-width=&quot;1903&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biZPKK/btsIIzj0NmQ/drYFydQXOzU7m9OYcHMkt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biZPKK/btsIIzj0NmQ/drYFydQXOzU7m9OYcHMkt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biZPKK/btsIIzj0NmQ/drYFydQXOzU7m9OYcHMkt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiZPKK%2FbtsIIzj0NmQ%2FdrYFydQXOzU7m9OYcHMkt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1014&quot; height=&quot;650&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>정보/유용한 사이트</category>
      <category>SVG</category>
      <category>무료사이트</category>
      <category>웹디자인</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/146</guid>
      <comments>https://wooncloud.tistory.com/146#entry146comment</comments>
      <pubDate>Sun, 21 Jul 2024 00:18:53 +0900</pubDate>
    </item>
    <item>
      <title>[Sveltekit] 버튼 hover시 +page.server.js 실행을 막는 법</title>
      <link>https://wooncloud.tistory.com/145</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;sveltekit.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfMIhg/btsHZ8o2hKu/2GbN0soFKzkJXygdfATq80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfMIhg/btsHZ8o2hKu/2GbN0soFKzkJXygdfATq80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfMIhg/btsHZ8o2hKu/2GbN0soFKzkJXygdfATq80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfMIhg%2FbtsHZ8o2hKu%2F2GbN0soFKzkJXygdfATq80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;sveltekit.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Sveltekit에서 버튼이나 a tag에 라우터 url이 걸려 있으면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그 버튼을 hover만 해도, 라우터에 걸린 +page.server.js 가 실행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7WTDI/btsH0MrX2pW/kFKLvp3GMsb9BBy0zUCi6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7WTDI/btsH0MrX2pW/kFKLvp3GMsb9BBy0zUCi6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7WTDI/btsH0MrX2pW/kFKLvp3GMsb9BBy0zUCi6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7WTDI%2FbtsH0MrX2pW%2FkFKLvp3GMsb9BBy0zUCi6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;437&quot; height=&quot;100&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위의 코드를 보면 href에 user 라우터가 걸려있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wq858/btsH1jiv0VG/CKquUqKIKDw5mDjOicjzo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wq858/btsH1jiv0VG/CKquUqKIKDw5mDjOicjzo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wq858/btsH1jiv0VG/CKquUqKIKDw5mDjOicjzo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWq858%2FbtsH1jiv0VG%2FCKquUqKIKDw5mDjOicjzo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;193&quot; height=&quot;72&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;user 라우터에는 +page.server.js 가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위에서 말한대로 버튼을 hover만 해도, +page.server.js 가 실행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;SvelteKit에서 버튼을 호버했을 때 그 버튼을 클릭하면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;가는 페이지의 +page.server.js의 load 함수가 실행되는 것은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;의도&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;된 동작입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;왜 그럴까요?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;SvelteKit이 페이지 전환을 미리 준비하기 위해 preload를 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;preload은 사용자가 링크나 버튼을 호버했을 때, 해당 링크가 가리키는 페이지를 미리 로드하여 사용자 경험을 향상시키는 기술입니다. SvelteKit은 기본적으로 이 기능을 활성화하여 페이지 전환을 더 빠르게 만듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://kit.svelte.dev/docs/link-options#data-sveltekit-reload&quot;&gt;https://kit.svelte.dev/docs/link-options#data-sveltekit-reload&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718553697777&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Link options &amp;bull; Docs &amp;bull; SvelteKit&quot; data-og-description=&quot;Link options Edit this page on GitHub On this page On this page In SvelteKit, elements (rather than framework-specific components) are used to navigate between the routes of your app. If the user clicks on a link whose href is 'owned' by the app (as oppose&quot; data-og-host=&quot;kit.svelte.dev&quot; data-og-source-url=&quot;https://kit.svelte.dev/docs/link-options#data-sveltekit-reload&quot; data-og-url=&quot;https://kit.svelte.dev/docs/link-options#data-sveltekit-reload&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/crqroA/hyWleBihGW/haBNtp6ogQXT8P5ZiLGD0K/img.jpg?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640&quot;&gt;&lt;a href=&quot;https://kit.svelte.dev/docs/link-options#data-sveltekit-reload&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kit.svelte.dev/docs/link-options#data-sveltekit-reload&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/crqroA/hyWleBihGW/haBNtp6ogQXT8P5ZiLGD0K/img.jpg?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Link options &amp;bull; Docs &amp;bull; SvelteKit&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Link options Edit this page on GitHub On this page On this page In SvelteKit, elements (rather than framework-specific components) are used to navigate between the routes of your app. If the user clicks on a link whose href is 'owned' by the app (as oppose&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kit.svelte.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그러나 만약 +page.server.js에서 api서버로 데이터를 호출하는 로직이 있다면, api서버는 잦은 호출로 부하를 받을 수 있습니다. 왜냐하면 마우스가 버튼을 호버할때마다, &lt;span style=&quot;text-align: start;&quot;&gt;위처럼 preload가 되기 때문이죠.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;버튼을 hover하면 +page.server.js 실행을 막는 법&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;src 폴더 아래&amp;nbsp;app.html 가 있습니다. 이 안에 body에 data-sveltekit-preload-data 라는 속성이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;아마 data-sveltekit-preload-data가 &quot;hover&quot;로 되어 있을 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-19 오후 9.00.20.png&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm1jQE/btsH4od9fBQ/OAkBisVv8DJApCuTYzXbv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm1jQE/btsH4od9fBQ/OAkBisVv8DJApCuTYzXbv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm1jQE/btsH4od9fBQ/OAkBisVv8DJApCuTYzXbv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm1jQE%2FbtsH4od9fBQ%2FOAkBisVv8DJApCuTYzXbv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;661&quot; height=&quot;115&quot; data-filename=&quot;스크린샷 2024-06-19 오후 9.00.20.png&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;위의 사진처럼, data-sveltekit-preload-data=&quot;hover&quot;를 제거해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그러면 hover 할때 +page.server.js가 실행되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;대신 페이지 전환이 느려집니다.&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;data-sveltekit-preload-data=&quot;hover&quot; 로 되어 있다면, 버튼을 hover 할 때마다 데이터를 미리 불러옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;hover를 통해 미리 데이터를 불러온 후, 화면전환시 미리 준비한 데이터를 표시하기 때문에 즉각적인 페이지 전환이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그 예시를 아래 튜토리얼에서 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;a href=&quot;https://learn.svelte.dev/tutorial/preload&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.svelte.dev/tutorial/preload&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718557782306&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Welcome to Svelte &amp;bull; Svelte Tutorial&quot; data-og-description=&quot;Welcome to the Svelte tutorial! This will teach you everything you need to know to easily build web applications of all sizes, with high performance and a small footprint. You can also consult the API docs and the examples, or &amp;mdash; if you're impatient to st&quot; data-og-host=&quot;learn.svelte.dev&quot; data-og-source-url=&quot;https://learn.svelte.dev/tutorial/preload&quot; data-og-url=&quot;https://learn.svelte.dev&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/deZgJz/hyWlnZhOd9/sJecEoEzBnm5lP2Tz70PS1/img.jpg?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640,https://scrap.kakaocdn.net/dn/btSZDL/hyWoNB2ubQ/XD4SPiYn0MydSosn6mdfR0/img.jpg?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640&quot;&gt;&lt;a href=&quot;https://learn.svelte.dev/tutorial/preload&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.svelte.dev/tutorial/preload&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/deZgJz/hyWlnZhOd9/sJecEoEzBnm5lP2Tz70PS1/img.jpg?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640,https://scrap.kakaocdn.net/dn/btSZDL/hyWoNB2ubQ/XD4SPiYn0MydSosn6mdfR0/img.jpg?width=640&amp;amp;height=640&amp;amp;face=0_0_640_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to Svelte &amp;bull; Svelte Tutorial&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to the Svelte tutorial! This will teach you everything you need to know to easily build web applications of all sizes, with high performance and a small footprint. You can also consult the API docs and the examples, or &amp;mdash; if you're impatient to st&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.svelte.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>preload</category>
      <category>svelte</category>
      <category>sveltekit</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/145</guid>
      <comments>https://wooncloud.tistory.com/145#entry145comment</comments>
      <pubDate>Mon, 17 Jun 2024 02:24:13 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript에서 export { }와 export default의 차이점</title>
      <link>https://wooncloud.tistory.com/144</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0440.PNG&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NeHTg/btsHp47KhgW/O91vK1jKjXCkev8dykP661/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NeHTg/btsHp47KhgW/O91vK1jKjXCkev8dykP661/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NeHTg/btsHp47KhgW/O91vK1jKjXCkev8dykP661/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNeHTg%2FbtsHp47KhgW%2FO91vK1jKjXCkev8dykP661%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;480&quot; data-filename=&quot;IMG_0440.PNG&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0445.PNG&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O2Pyu/btsHqJV4hd0/qP0koPzDv4wMtnCboFrBc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O2Pyu/btsHqJV4hd0/qP0koPzDv4wMtnCboFrBc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O2Pyu/btsHqJV4hd0/qP0koPzDv4wMtnCboFrBc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO2Pyu%2FbtsHqJV4hd0%2FqP0koPzDv4wMtnCboFrBc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;480&quot; data-filename=&quot;IMG_0445.PNG&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0446.PNG&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cClwXg/btsHqPBWkkE/ZvnBtMclMLHF7vbwsOQok1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cClwXg/btsHqPBWkkE/ZvnBtMclMLHF7vbwsOQok1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cClwXg/btsHqPBWkkE/ZvnBtMclMLHF7vbwsOQok1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcClwXg%2FbtsHqPBWkkE%2FZvnBtMclMLHF7vbwsOQok1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;480&quot; data-filename=&quot;IMG_0446.PNG&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;&amp;gt;&amp;gt; 그래서 적어본 export와 export default의 차이점! &amp;lt;&amp;lt;&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KCmpr/btsHpmuvbsN/wCpu8yHBuayikNKkuDOKn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KCmpr/btsHpmuvbsN/wCpu8yHBuayikNKkuDOKn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KCmpr/btsHpmuvbsN/wCpu8yHBuayikNKkuDOKn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKCmpr%2FbtsHpmuvbsN%2FwCpu8yHBuayikNKkuDOKn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;출처: &lt;a href=&quot;https://www.google.com/url?sa=i&amp;amp;url=https%3A%2F%2Fbootcamp.uxdesign.cc%2Fnamed-export-vs-default-export-in-es6-a2370b062f17&amp;amp;psig=AOvVaw3526ddTwrULsBlVYSnaxql&amp;amp;ust=1715784990773000&amp;amp;source=images&amp;amp;cd=vfe&amp;amp;opi=89978449&amp;amp;ved=0CBQQjhxqFwoTCKiR16azjYYDFQAAAAAdAAAAABAE&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.google.com/url?sa=i&amp;amp;url=https%3A%2F%2Fbootcamp.uxdesign.cc%2Fnamed-export-vs-default-export-in-es6-a2370b062f17&amp;amp;psig=AOvVaw3526ddTwrULsBlVYSnaxql&amp;amp;ust=1715784990773000&amp;amp;source=images&amp;amp;cd=vfe&amp;amp;opi=89978449&amp;amp;ved=0CBQQjhxqFwoTCKiR16azjYYDFQAAAAAdAAAAABAE&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript 모듈 시스템은 코드를 구성하고 재사용하기 위한 방법을 제공하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 export는 2가지 방법이 있습니다. &lt;b&gt;Named Export&lt;/b&gt; 라는 방법과 &lt;b&gt;Default Export&lt;/b&gt; 라는 방법이 있습니다. 이 두 가지 방법은 모듈에서 함수, 객체, 변수 등을 내보내고 다른 모듈에서 사용할 수 있게 해줍니다. 이 두 방법은 차이점이 있고, 효율적인 코드 관리를 하려면 알아두는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 정확한 명칭을 잘 몰라서 &lt;b&gt;export&lt;/b&gt; 와 &lt;b&gt;export default&lt;/b&gt; 라고 설명하고 다녔었는데, 이번에 조사하면서 이렇게 명칭이 있다는 것을 알았습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Named Exports (export)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Named exports는 모듈에서 여러 값을 내보낼 때 사용됩니다. 이 방식을 사용하면, 내보낸 각각의 값에 이름을 지정해야 하며, 다른 모듈에서 이 값을 가져올 때는 그 정확한 이름을 사용해야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;함수 앞에 export를 붙여 내보내기&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;// math.js
export function add(x, y) {
  return x + y;
}

export function subtract(x, y) {
  return x - y;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;별도로 export에 함수명만 기입하여 내보내기&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;// math.js
function multiply(x, y) {
  return x * y;
}

function divide(x, y) {
  return x / y;
}

export { multiply, divide };
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 파일에서 이러한 값들을 사용하려면, 다음과 같이 작성합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// app.js
import { add, subtract, multiply, divide } from './math';

const resultAdd = add(10, 5);
const resultSubtract = subtract(10, 5);
const resultMultiply = multiply(10, 5);
const resultDivide = divide(10, 5);

console.log(`10과 5를 더한 결과: ${resultAdd}`);
console.log(`10에서 5를 뺀 결과: ${resultSubtract}`);
console.log(`10과 5를 곱한 결과: ${resultMultiply}`);
console.log(`10을 5로 나눈 결과: ${resultDivide}`);

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Default Export (export default)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Default export는 모듈에서 단 하나의 값만 내보낼 때 사용됩니다. 이 방식은 모듈이 주로 하나의 기능을 제공할 때 유용하며, 가져올 때는 어떤 이름을 사용해도 됩니다.&lt;/p&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;// calculator.js
const calculator = {
  add(x, y) {
    return x + y;
  },
  subtract(x, y) {
    return x - y;
  }
};

export default calculator;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모듈을 가져올 때는 다음과 같이 작성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// app.js
import calc from './calculator';

// calculator 모듈에서 가져온 함수를 사용합니다.
const resultAdd = calc.add(10, 5);
const resultSubtract = calc.subtract(10, 5);

console.log(`10과 5를 더한 결과는 ${resultAdd}입니다.`);
console.log(`10에서 5를 뺀 결과는 ${resultSubtract}입니다.`);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;언제 어떤 방식을 사용해야 할까요?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단일 기능&lt;/b&gt;을 제공하는 모듈의 경우, &lt;b&gt;export default&lt;/b&gt; 를 사용하는 것이 좋습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;여러 기능&lt;/b&gt;을 제공하거나, 모듈의 사용자가 모듈의 특정 부분만 필요로 하는 경우, &lt;b&gt;named exports&lt;/b&gt;를 사용하는 것이 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;성능차이?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 stack overflow의 글은 성능 차이가 없다고 언급하고 있으며, 코딩 스타일의 차이에 따른 것이라고 설명하고 있습니다. 그래서 마음에 드는걸로 쓰셔용!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/77558530/differences-between-javascript-export-default-styles&quot;&gt;Differences between Javascript &quot;export default&quot; styles - Stack Overflow&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;자료&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freecodecamp.org/news/difference-between-default-and-named-exports-in-javascript/&quot;&gt;What&amp;rsquo;s the Difference Between Default and Named Exports in JavaScript?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/46913851/why-and-when-to-use-default-export-over-named-exports-in-es6-modules&quot;&gt;Stack Overflow: Why and when to use default export over named exports in ES6 Modules?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/difference-between-default-named-exports-in-javascript/&quot;&gt;GeeksforGeeks: Difference Between Default &amp;amp; Named Exports in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@heshramsis/understanding-the-difference-between-export-default-and-export-with-named-exports-in-javascript-f0569c221a3&quot;&gt;Medium: Understanding the Difference between export default and export with Named Exports in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@etherealm/named-export-vs-default-export-in-es6-affb483a0910&quot;&gt;Medium: Named Export vs Default Export in ES6&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>export</category>
      <category>javascript</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/144</guid>
      <comments>https://wooncloud.tistory.com/144#entry144comment</comments>
      <pubDate>Wed, 15 May 2024 00:03:13 +0900</pubDate>
    </item>
    <item>
      <title>가볍게 써보는 AI 개발 1차 후기</title>
      <link>https://wooncloud.tistory.com/143</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lk9a7/btsFFmpmQ7H/kdzVZn12TwOOvCYlrsVJoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lk9a7/btsFFmpmQ7H/kdzVZn12TwOOvCYlrsVJoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lk9a7/btsFFmpmQ7H/kdzVZn12TwOOvCYlrsVJoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flk9a7%2FbtsFFmpmQ7H%2FkdzVZn12TwOOvCYlrsVJoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1207&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24년 3월 8일부로 플로우에 AI 기능이 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1월 초부터 AI 기능을 만들기 시작했다. 단 2달 만에 여러 기능을 찍어내기 위해 야근도 많이 하고 고생도 많았다. 선두를 달리고 있던 부장님도 AI 개발의 길을 개척하느라 많은 고생을 하셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발은 우리 회사의 1분기 핵심 과제였다. 그래서 많은 주목을 받았다. 나는 이 주목받는 곳에서 일하는 것이 즐거웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2달 동안 급하게 기능을 만들면서, 뭔가 부족해도 &amp;ldquo;일단 이대로 가자!&amp;rdquo; 하며 만들어진 것이 많다. 대표님이 빠른 출시를 통해 경쟁사 견제를 주 목표로 삼았기 때문에, 이해는 한다. 하지만 더 사용성이 좋은 방향으로 개선되었으면 한다. &amp;lsquo;임시&amp;rsquo;가 &amp;lsquo;완성&amp;rsquo;이 아니길 바라면서..&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운것도 많고 배울 것도 많다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 AI 프로젝트를 통해 프롬프트 엔지니어링과 AI에 대해 많이 배우게 되었다. AI 분야는 앞으로도 계속해서 탐구해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 주요 role로 맡게 된다면, OpenAI와 AI의 최신 트렌드 및 지식을 적극적으로 공부해 나가야 한다. 그리고 스스로 길을 개척하는 자세가 필요하다. 나중에 기본부터 AI 관련 지식을 공부하게 되면 블로그에 한번씩 써보려고 한다. (제발 글쓰자 나자신!)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;갠적으로 불안한 마음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마음 한켠에 불안함이 자리 잡고 있다. &amp;ldquo;AI로 인해 웹 개발과의 거리가 멀어지지 않을까&amp;rdquo;, &amp;ldquo;기술 스택에 영향을 주지 않을까&amp;rdquo; 하는 걱정이다. 나는 지금까지 세 번의 이직을 통해 두 번의 기술 스택 변화를 경험했다. 그 때문에 경력이 있음에도 불구하고 신입으로 회사에 입사해야 했다. 그래서 기술 스택 변화에 대한 두려움이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 분야는 파이썬 기반의 파생 기술들이 많다고 알고 있다. 물론, 이 분야까지 가려면 전문가를 따로 뽑는 것이 낫겠지만, 그럼에도 불구하고 불안한 마음이 든다. 이 불안함을 누군가와 대화로 해소하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 불안한 마음 때문에 AI를 피하고 싶었는데, 나의 본심은 새로운 길을 탐구하는 것을 좋아해서 참을 수 없었다. ㅎㅎ 아마 이 본능이 나의 기술스택 변화를 만들고 두려움도 만든 것이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 자신이 앞으로 AI와 관련된 글을 좀 쓰길 바라면서.. 글을 마친다!&lt;/p&gt;</description>
      <category>이야기/개발일지</category>
      <category>Ai</category>
      <category>플로우</category>
      <category>회고</category>
      <category>후기</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/143</guid>
      <comments>https://wooncloud.tistory.com/143#entry143comment</comments>
      <pubDate>Mon, 11 Mar 2024 00:51:53 +0900</pubDate>
    </item>
    <item>
      <title>2023년 하반기 회고</title>
      <link>https://wooncloud.tistory.com/142</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;쓰고 지우고 쓰고 지우고 하다가 다시 쓴다. 나쁜말은 다 빼고 좋은말만 간단히 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;(그렇게 말했던 간단히는 없었다.)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실은 23년 하반기를 좋은 말을 해보려고 해도 나에겐 정말 심리적으로 힘든 일만 있었던 것 같아서 감정이 조금 새어 있을 수 있다. 하지만 페르소나를 써서라도 웃는모습 보여주며 힘내본다. 웃으며 살다보면 그것이 어느순간 내 모습이 될 것이니 말이다! 아자아자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 회사 이야기를 하자면, 나의 하반기 회사에서 했던 일은 단 하나로 설명할 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;유지보수!!&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하반기의 반 이상은 배포담당자를 맡으면서, 최대한 서비스의 안정성을 지키기 위해 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 배포담당자 &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하반기에 한번 서버가 크게 멈춘적이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chT19B/btsEf0Bpv1X/z5lY5ENn7TBseCrh5E3ks1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chT19B/btsEf0Bpv1X/z5lY5ENn7TBseCrh5E3ks1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chT19B/btsEf0Bpv1X/z5lY5ENn7TBseCrh5E3ks1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchT19B%2FbtsEf0Bpv1X%2Fz5lY5ENn7TBseCrh5E3ks1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;270&quot; height=&quot;174&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제인지 자세히 기억은 나지 않으나, DB 버전을 업데이트 하면서 생긴 문제가 있었다. 1시간 정도 서비스가 멈춰서 고객들에게 상당히 많은 배상을 했던 것으로 기억난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사람들은 이때부터 경각심을 가지고 더이상 이런 일이 일어나지 않도록 배포 프로세스부터 구축하기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실상 나는 배포를 하는 사람이지만 배포에 대한 결정권이나 특별한 권한이 없었다. 그러나 은근히 배포담당자에게 책임을 넘기려는 모먼트가 주변에서 보였었다. 그리고 우리 팀장님은 책임과 권한을 분산하고자 나에게 힘을 주기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이렇게 된 것, 한번 책임을 가지고 이 역할을 수행해보고자 했다. 그러나 처음엔 내가 가진 책임도 모르고 심지어 배포담당자인지도 잘 몰랐다. 그러니 당연히 권한도 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이런 상태가 너무 싫었다. 배포일정을 너무 빠듯하게 짜고, 어떤 기능을 올릴지 말지 우유부단하게 결정하는 것은 개발자들도 힘들고, 테스트 시간을 부족하게 만들어 서비스의 안정성도 떨어트리는 일이다. 거기다 책임은 생기고 권한이 없어 배포되는 기능이나 정보들을 알 수도 없어 답답했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 시행착오 끝에 나는 정기배포 회의를 참석하는 것으로부터 시작했다. 회의를 통해 배포 프로세스에 대한 개선 방법에 의견을 냈고, 그러면서 내 선에서 구멍이 나지 않도록 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1701&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH7gHY/btsEkza4bdM/KgZbeKgrCeaVzlsZmEI0Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH7gHY/btsEkza4bdM/KgZbeKgrCeaVzlsZmEI0Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH7gHY/btsEkza4bdM/KgZbeKgrCeaVzlsZmEI0Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH7gHY%2FbtsEkza4bdM%2FKgZbeKgrCeaVzlsZmEI0Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;656&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1701&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 배포 1주일 전 배포담당자로서 해야할 일들과 프로세스가 정해져있다. 그 프로세스들은 불편해도 나름 다 이유가 있고, 내가 사람들과 함께 만들었기 때문에 개발자, 기획자가 불평해도 왜 그런지 잘 설득하며 체계를 유지했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위의 사진처럼 9월부터 24년 1월까지의 배포에 관한 지식의 정수를 담은 문서를 만들고 다음 배포 담당자에게 권한을 위임했다. 열심히 문서를 만든 결과, 현재 배포담당자가 배포할 때 마다 문서를 보며 잘 진행하는 것을 보면 뿌듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 유지보수&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포담당자의 두번째 역할은 오류를 수정하고 유지보수에 힘쓰는 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 하반기 초에 &amp;ldquo;내부에서도 외부에서도 관심을 안가지지만 필요하다는 VOC 기능&amp;rdquo;을 하나 한 이후로 계속 유지보수만 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u7a9T/btsEkzCaVUJ/Axw9Z7uUhQOyuJJkYt0KWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u7a9T/btsEkzCaVUJ/Axw9Z7uUhQOyuJJkYt0KWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u7a9T/btsEkzCaVUJ/Axw9Z7uUhQOyuJJkYt0KWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu7a9T%2FbtsEkzCaVUJ%2FAxw9Z7uUhQOyuJJkYt0KWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;565&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1695&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨간색 테두리로 친 부분이 나의 스케줄이다. QA라고 적혀있는데 유지보수한다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 뭔가 형형색색 여러 일정으로 가득 차 있는데, 나는 그저 유지보수만 할 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 플로우 프로젝트에 오류건이 올라오면, 그 이슈를 트래킹해서 오류를 잡아야 한다. 그러면서 가끔 긴급 이슈가 생기면 얼른 스위칭해서 해결해야 하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류는 고객으로부터 들어온 오류이기 때문에 CX팀에서 고객에게 오류에 대한 설명과 설득을 많이한다. 그 과정에서 개발자에게 원인과 설득하기 위한 정보를 요청하는데, 그 내용에 대해 열심히 설명하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 보면 &amp;ldquo;성호님 혼자 대응하고 있다고 느끼면..&amp;rdquo; 이라는 글자가 있는데, 실제로 나 혼자 대응하고 있었던건 아니고.. 타 부서에서 개발팀에게 이슈 확인 요청과 원인 설명 등 여러 커뮤니케이션적 요청이 많이 들어오는데, 단톡방에 채팅이나 댓글로 이루어진다. 그래서 다들 할 일 하고 바쁘면 못 볼 수도 있고, 가끔은 &amp;ldquo;나 모르는건데?&amp;rdquo;, &amp;ldquo;누군가 말하겠지.&amp;rdquo; 하면서 넘어갈 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 배포담당자니까. 그리고 내가 말하지 않으면 아무도 대응하지 않을 것 같아서, 내가 열심히 답변했다. 그러나 나의 개발 R&amp;amp;R이 어드민 같은 쪽으로 치우쳐져 있어서, 다른 기능의 도메인지식이 부족하고 나의 답변이 틀릴 수 있다. 저 말은 내 답변 아래 제대로 답변을 달아달라는 말에서 붙은 코멘트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 내가 저 기간에 정신없이 불태워서, 저 문구를 보자마자 &amp;ldquo;뭐? 나만 대응하고 있어?&amp;rdquo; 라고 생각하게 되는 필터링이 걸려 있었는데 ㅋㅋㅋ.. 사실 내 답변이 그만큼 많이 틀렸다는 말인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 힘들어하는 우리 직원에게 기능을!&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 이메일 누락 이슈 대응&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7XEiF/btsEkermH2r/fFp59plXjTp8nlfGxbKB40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7XEiF/btsEkermH2r/fFp59plXjTp8nlfGxbKB40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7XEiF/btsEkermH2r/fFp59plXjTp8nlfGxbKB40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7XEiF%2FbtsEkermH2r%2FfFp59plXjTp8nlfGxbKB40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;200&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 유지보수를 하는 중, CX팀으로부터 메일 누락이 많이 생긴다는 말을 들었다. AWS SES가 메일 누락이 없고 안정적이라고 들어서 Gmail SMTP에서 SES로 교체했는데, 오히려 메일 누락이 심하다는 이슈가 있었다. 메일은 정상 보내졌지만, 보내진 메일이 손실된 것이다. 하지만 코드는 아무 이상없고, 문제는 해결 못하는 상황.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 CX가 바로 대응할 수 있도록, 특정 수신 대상자 한정으로 SES에서 Gmail로 교체하고, 메일 전송 이력과 재전송 기능까지 이틀만에 만들어서 제공해줬다. 그러면서 유지보수도 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이슈는 몇달동안 원인을 찾다가 결국 찾아내서 대체로 해결해둔 상황이지만, 수신측에서 막는 경우도 있어서 해당 기능은 그대로 두기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 데이터 업무 자동화 노력&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nVOpc/btsEkd656ke/gKnYvfTZTZlzwrZO1GE2B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nVOpc/btsEkd656ke/gKnYvfTZTZlzwrZO1GE2B0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nVOpc/btsEkd656ke/gKnYvfTZTZlzwrZO1GE2B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnVOpc%2FbtsEkd656ke%2FgKnYvfTZTZlzwrZO1GE2B0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;200&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노력이라는 말을 넣은 이유는 완전히 자동화는 못했다는 뜻. 자동화를 하려면 큰 프로젝트가 될 것 같아서, 우리 팀에게 어느정도 도움이 될 수 있는 기능을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리팀은 데이터를 뽑아주는 업무도 하고 있는데, 이 일이 생각보다 팀 리소스를 많이 먹었다. 그래서 클릭 한번으로 데이터를 뽑아주는 기능을 만들었었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 말곤 자잘한 내용밖에 없어서, 하반기 회사 일은 크게 보면 이게 끝이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런말은 안하고 싶었지만, 유지보수라는 말은 정말 대단해보이지 않다. 이미 회사에서 그렇게 보고 있는 모습이 많이 느껴진다. (우리팀만 빼고) 환경이 그래서 그런지 몰라도 이번 하반기의 나의 일은 정말 힘은 힘대로 뺏는데, 초라해보였다. 이 글을 본 다른 분들은 어떻게 생각할지 궁금합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 플테세 (플로우 테크 세미나)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8FaWf/btsEj61hRXt/WR6sKBMmDn1wf3bSSEYUp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8FaWf/btsEj61hRXt/WR6sKBMmDn1wf3bSSEYUp0/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2248&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.4932%; margin-right: 10px;&quot; data-widthpercent=&quot;32.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8FaWf/btsEj61hRXt/WR6sKBMmDn1wf3bSSEYUp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8FaWf%2FbtsEj61hRXt%2FWR6sKBMmDn1wf3bSSEYUp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;2248&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B5DVC/btsEllczTKl/WZ5dQztXkmM5qXoYiH6xtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B5DVC/btsEllczTKl/WZ5dQztXkmM5qXoYiH6xtk/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1101&quot; data-is-animation=&quot;false&quot; style=&quot;width: 66.344%;&quot; data-widthpercent=&quot;67.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B5DVC/btsEllczTKl/WZ5dQztXkmM5qXoYiH6xtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB5DVC%2FbtsEllczTKl%2FWZ5dQztXkmM5qXoYiH6xtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1101&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플테세는 플로우 테크 세미나라고, 개발자들이 자발적으로 참여하여 공유하고 싶은 기술과 지식을 발표하고 영상으로 남기는 문화이다. 매달마다 진행되며 상반기 회고에도 언급한적 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년도 플테세를 총 7번 참여했고 주최자인 우리 부장님을 제외하면 가장 많이 참여했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 원래 언변능력도 어휘력도 부족해서, 플테세를 통해 발표를 하는 연습을 키우려고 노력했다. 플테세를 하면서 얻은 변화는 3가지 정도 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y78ap/btsEkgQic7D/MwhGzdX2zjFe2QBOAWUt30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y78ap/btsEkgQic7D/MwhGzdX2zjFe2QBOAWUt30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y78ap/btsEkgQic7D/MwhGzdX2zjFe2QBOAWUt30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY78ap%2FbtsEkgQic7D%2FMwhGzdX2zjFe2QBOAWUt30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;200&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;차분하게 하는 발표가 대체로 좋은 발표다.&lt;/b&gt; &lt;br /&gt;처음에는 이야기하듯 웃고 재미있게 하려고 했었다. 그러나 다시 내 영상을 돌아보니, 오히려 지식전달에 방해가 되는 것을 느꼇다. 그리고 직업상 대체로 사람들이 차분한 대화를 좋아한다. 하반기부터 했던 발표는 좀 더 차분하게 하려고 노력했고, 앞으로 발표 기회가 생기면 차분하게 하려고 한다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대본을 잘 준비한 발표는 더 나은 발표를 만든다.&lt;/b&gt; &lt;br /&gt;나는 보통 대본을 쓰지 않는 스타일이었다. 대본없이 했던 나의 발표는 말을 더듬거리고 느리게 말하는 모습이 비춰졌다. 나는 말을 할 때, 말을 잘 구성하려고 노력하다보니 말의 버퍼링이 좀 심한 편이다. 그래서 오히려 잘 준비한 대본은 더 나은 발표를 만든다는 것을 알았다. 앞으로의 발표는 그렇게 할 예정이다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사람들에게 필요한 내용이 뭔지 생각하자.&lt;/b&gt; &lt;br /&gt;처음에는 사람들이 잘 모르는 주제로 선택을 많이 했다. 공부를 통해 알게된 지식을 전달하는 것은 나의 성장과 사람들에게 지식 전달에 좋다 생각했다. 특히 정규식 발표에서 힘을 봤다. 하지만 갈수록 주제를 억지로 찾게 되었고, 결국 아무도 관심이 없는 엉뚱한 주제를 선택하게 된것 같다. 그래서 스스로 반성하고, 내가 다른 사람에게 전달할 수 있는 것이 무엇일까 고민했다. 내가 만든 기능 중, 사람들이 자주 접하게 되는 코드들이나, 기능에 대한 정책 설명을 해주는 것이 좋다고 생각하게 됐다. 사람들은 다른사람이 어떤 기능을 만들고 있는지 관심이 없기 때문에 이런 미디어로 전달할 수 있는 수단이 생기면 좋을 것 같았다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Developer's Meetup 발표 &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 우리 부서 직원들이 좋은 개발 문화를 만들기 위해 노력을 많이 했다. 그 중 개발자들이 함께 모여 밋업하는 행사도 했고, 그 때 나는 글쓰기 문화를 전달하고 싶어서 발표를 한 적이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 회사 사람들은 글을 많이 작성하지만, 아직도 글을 쓰는게 귀찮고 어려워 하는 사람들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 쓰고, 잘 정리하고, 잘 공유하는 것은 아직도 어려운 것 같다. 그래도 글쓰기는 사람이 사는 세상안에서 가장 중요한 능력 중 하나라고 생각한다. 나는 이 중요성을 전달하고자 사람들 앞에 나오게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUrHjq/btsEf3x767u/HlSvdZMKOOt0ytYE9EzX90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUrHjq/btsEf3x767u/HlSvdZMKOOt0ytYE9EzX90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUrHjq/btsEf3x767u/HlSvdZMKOOt0ytYE9EzX90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUrHjq%2FbtsEf3x767u%2FHlSvdZMKOOt0ytYE9EzX90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;525&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhTND5/btsEj85Sf3J/u8yD2KeHqzqBMzXLnfo8O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhTND5/btsEj85Sf3J/u8yD2KeHqzqBMzXLnfo8O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhTND5/btsEj85Sf3J/u8yD2KeHqzqBMzXLnfo8O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhTND5%2FbtsEj85Sf3J%2Fu8yD2KeHqzqBMzXLnfo8O1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;525&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 스카우터 모니터링 화면 구축하기 &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 사소한 내용인데, 우리 회사의 벽면에는 2개의 TV가 붙어있다. 모니터링용 TV로 사용할 것을 당연하게 생각하고, 스카우터를 띄웠는데 생각보다 시행착오가 많았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모니터 케이블이 짧음 이슈&lt;/li&gt;
&lt;li&gt;무선으로 송출하려니 내부 와이파이망이 불안전.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 저런 복잡한 이슈 때문에, 결국 2개의 TV 중 하나만 컴퓨터에 연결된 채 사용을 쭉 해오다가.. 하반기에 우리의 글로벌 서비스인 모닝메이트의 중요성이 대두되면서, 모닝메이트 스카우터 모니터링을 해줘야 겠다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 PC에서 와이파이 무선 송출로 모니터링을 하려고 씨름하고 있었는데, 그냥 포기하고 경영지원팀에 케이블 구매요청해서 유선 연결로 2대의 TV를 사용중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 OBS Studio를 이용해서 모니터링 화면을 보기좋게 잘 꾸며놨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crJ7dI/btsEjSozmMa/mtrlh1f2BimilLMzVll0d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crJ7dI/btsEjSozmMa/mtrlh1f2BimilLMzVll0d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crJ7dI/btsEjSozmMa/mtrlh1f2BimilLMzVll0d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrJ7dI%2FbtsEjSozmMa%2Fmtrlh1f2BimilLMzVll0d1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;680&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 칠판 문화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 TV에 이어 칠판 꾸미기도 내가 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년도 초에 내가 사람들에게 그려준 그림을 프린트하여 붙이니 예쁘고 우리팀이 특별하다는 느낌을 얻었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23년 하반기동안 파트와 각자 하고 있는 일을 적어두는 것으로 하고 있었는데, 사람들이 잘 갱신도 안해주고 관심이 사라지는 느낌이라서 24년이 되자마자 바꿔보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 그림이 있고 아기자기한 예쁜 칠판이 있는건, 내가 이 부서에 있기 때문이라는 자부심을 가지고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p1ZHN/btsElkLu1mL/CnNh7yzthjkt8zZnNxZXcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p1ZHN/btsElkLu1mL/CnNh7yzthjkt8zZnNxZXcK/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p1ZHN/btsElkLu1mL/CnNh7yzthjkt8zZnNxZXcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp1ZHN%2FbtsElkLu1mL%2FCnNh7yzthjkt8zZnNxZXcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qfNNQ/btsEf4jwumY/wNgyQRgnYbZoukE173Ptb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qfNNQ/btsEf4jwumY/wNgyQRgnYbZoukE173Ptb0/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qfNNQ/btsEf4jwumY/wNgyQRgnYbZoukE173Ptb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqfNNQ%2FbtsEf4jwumY%2FwNgyQRgnYbZoukE173Ptb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;왼쪽이 23년도 버전 / 오른쪽은 24년도 버전!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 정말 하는 일이 많고 무엇을 했는지 어필하기 위해, 이렇게 칸반보드를 만들어서 쌓아 볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 데브킷&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SS2NH/btsElkdGa2d/cLMCQImv8b1JjQKGKpGpXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SS2NH/btsElkdGa2d/cLMCQImv8b1JjQKGKpGpXk/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1032&quot; data-is-animation=&quot;false&quot; style=&quot;width: 52.2619%; margin-right: 10px;&quot; data-widthpercent=&quot;52.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SS2NH/btsElkdGa2d/cLMCQImv8b1JjQKGKpGpXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSS2NH%2FbtsElkdGa2d%2FcLMCQImv8b1JjQKGKpGpXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sKw3g/btsEkSO9VEk/wZjktZx986rjeH4z7e9gIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sKw3g/btsEkSO9VEk/wZjktZx986rjeH4z7e9gIk/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1158&quot; data-is-animation=&quot;false&quot; style=&quot;width: 46.5753%;&quot; data-widthpercent=&quot;47.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sKw3g/btsEkSO9VEk/wZjktZx986rjeH4z7e9gIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsKw3g%2FbtsEkSO9VEk%2FwZjktZx986rjeH4z7e9gIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1158&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모멘텀 같은 툴은 우리 회사에서 테스트와 개발 편의를 도와줄 크롬 익스텐션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모멘텀처럼 시계를 보여주는게 끝이 아니고 flow에 들어가면 각종 보조 기능을 추가준다. 예를 들어서 회원가입 자동화라던지, 업무번호 복사나 사용자, 기업정보를 쉽게 볼수 있게 해주기 등 역할이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 코딩애플 유튜브 보고 &amp;ldquo;크롬 익스텐션도 우리가 만들 수 있구나&amp;rdquo; 하는 인지부터 시작했다. 호기심으로 찾아본 크롬 익스텐션은 우리 업무와 테스트에 도움이 될 수 있겠다는 생각을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 개발자와 테스트를 해주는 분들께 일을 덜어주고자 하는 마음에서 플로우 데브킷을 만들기 시작했다. 솔직히 크롬 익스텐션은 자료도 잘 없어서, 기능 구현과 프로젝트 구성에도 어려움이 있었다. 그래도 조금씩 디벨롭해서 사람들에게 나눠줬을땐 반응이 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이제 솔직히 말하면 괜히 했다는 생각이 든다. 일부 사용자는 도움이 된다고 이야기를 들었지만, 나는 혁신적인 도움이 되길 원했다. 여러 아이디어는 있었으나, 생각보다 시간이 많이 투자된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 크롬 익스텐션이 프레임워크를 접목하기 쉽지 않고 좀 독자적인 구조를 가지고 있었다고 생각한다. vite 같은 패키징 툴을 좀 더 잘 썻다면 이를 해결할 수 있었겠지만, 이것도 공부하고 연구하는데 시간이 들고 이미 크롬 익스텐션 개발 아키텍처를 짜는데 많은 시간을 소비했다. (이미 2번이나 프로젝트를 갈아 엎었음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이제 더이상 시간을 투자하기 어려운 상태가 되자, 그냥 있어도 그만 없어도 그만이 된 애매한 툴이 되어버렸다. 투자한 시간대비 사람들에게 도움도 내적 동기 부여도 얻지 못한것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해서 한번 자신의 일처리에 대한 회고와 크롬 익스텐션 개발 방법에 대해 글을 써봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데브킷은 적당히 필요한 정도만큼 완성시켜 배포한 후 건들지 않을 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 코딩 멘토 &amp;amp; 레슨&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r5Yt1/btsEhq0RSS7/cPeVLNf3Q5Ow2WXw13EKg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r5Yt1/btsEhq0RSS7/cPeVLNf3Q5Ow2WXw13EKg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r5Yt1/btsEhq0RSS7/cPeVLNf3Q5Ow2WXw13EKg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr5Yt1%2FbtsEhq0RSS7%2FcPeVLNf3Q5Ow2WXw13EKg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;472&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하반기동안 친한 동생의 코딩 멘토와 현재 스벨트킷 레슨도 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레슨의 목표는 끝까지 하는것이고, 끝난 후 이 레슨에 대한 회고도 작성해 볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 시국이 시국인 만큼 많이 어렵고 힘든 상황이라 감정이 글에 영향을 준 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 사적으로 힘든 이야기도 많이 있고 정신없는 이야기도 많지만, 그런 것을 돌이켜 보려고 회고를 하는 것이 아니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8U8ws/btsEizpmz4U/edc9woDKkYjknpA3e7uSd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8U8ws/btsEizpmz4U/edc9woDKkYjknpA3e7uSd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8U8ws/btsEizpmz4U/edc9woDKkYjknpA3e7uSd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8U8ws%2FbtsEizpmz4U%2Fedc9woDKkYjknpA3e7uSd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;115&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24년 시작하고 내가 적은 회사 개인 노션 페이지에 적은 말이다. 한 해가 지나고 전체적으로 나의 행동에 대해 다시 생각해봤다. 그리고 정말 일을 잘하는 사람들의 모습을 생각해보고 내가 부족한게 뭔지 내가 해야할 것이 뭔지도 생각해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 해가 지나면 더 발전한 나의 모습이 기다리고 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는 정말 활기차고 힘들어도 이겨낼 수 있도록 긍정적이고 건강하자!&lt;/p&gt;</description>
      <category>이야기/개발일지</category>
      <category>회고</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/142</guid>
      <comments>https://wooncloud.tistory.com/142#entry142comment</comments>
      <pubDate>Fri, 2 Feb 2024 02:15:13 +0900</pubDate>
    </item>
    <item>
      <title>Svelte와 vite로 CSR 개발 환경 세팅</title>
      <link>https://wooncloud.tistory.com/141</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;vite+svelte.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vOMwB/btsBjPiingH/MPDVUhXL5lPd7ZpdkyNYmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vOMwB/btsBjPiingH/MPDVUhXL5lPd7ZpdkyNYmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vOMwB/btsBjPiingH/MPDVUhXL5lPd7ZpdkyNYmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvOMwB%2FbtsBjPiingH%2FMPDVUhXL5lPd7ZpdkyNYmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;800&quot; data-filename=&quot;vite+svelte.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Svelte는 웹 애플리케이션 개발 프레임워크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적이고 쉬운 문법을 가지고 있는 Svelte로 만든 SSR 기반 프레임워크가 SvelteKit 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 Svelte에 관한 공식 문서를 보다가 한가지 이상함을 느낌적이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWZWNj/btsBogMYw0J/rk4OzX78HkqqKFH7xHubok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWZWNj/btsBogMYw0J/rk4OzX78HkqqKFH7xHubok/img.png&quot; data-alt=&quot;설치하는 방법을 알려주는 스벨트 공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWZWNj/btsBogMYw0J/rk4OzX78HkqqKFH7xHubok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWZWNj%2FbtsBogMYw0J%2Frk4OzX78HkqqKFH7xHubok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;931&quot; height=&quot;702&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;설치하는 방법을 알려주는 스벨트 공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Svelte 공식 문서를 보는데, 이상하게 설치는 SvelteKit을 알려주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식적으로 CSR(Client Side Rendering)인 Svelte보다 SSR(Server Side Rendering)을 사용하라는 말인것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 저는 최근에 오직 CSR을 위해 Svelte를 사용하고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것은 크롬 익스텐션 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 익스텐션은 SSR은 당연히 사용할리 없고.. CSR만 쓰고 싶은데 왜 설치 방법은 따로 없는거지..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하고 보니 요오즘 최신 번들러! vite를 이용하여 설치하면 금방 해결될 일이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vite로 CSR기반 Svelte 시작하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;851&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJjNs9/btsBiUj5GJP/z6EryKrQZ7vafffI6KtV20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJjNs9/btsBiUj5GJP/z6EryKrQZ7vafffI6KtV20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJjNs9/btsBiUj5GJP/z6EryKrQZ7vafffI6KtV20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJjNs9%2FbtsBiUj5GJP%2Fz6EryKrQZ7vafffI6KtV20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;851&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;851&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 npm을 사용하고 있어서, 가볍게 아래 코드를 입력하여 새 프로젝트를 만듭니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701706352744&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm create vite@latest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 어떤 프레임워크를 쓸거냐고 선택권을 주는데, 사뿐히 Svelte를 선택해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIlPOB/btsBl5E0HOh/YmgKx9ixkHKWt2yQR48olK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIlPOB/btsBl5E0HOh/YmgKx9ixkHKWt2yQR48olK/img.png&quot; data-alt=&quot;프레임워크 선택화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIlPOB/btsBl5E0HOh/YmgKx9ixkHKWt2yQR48olK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIlPOB%2FbtsBl5E0HOh%2FYmgKx9ixkHKWt2yQR48olK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;301&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프레임워크 선택화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfCS2c/btsBqzFeAGs/USQ3R4KlQ5FFMrg8FXc1T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfCS2c/btsBqzFeAGs/USQ3R4KlQ5FFMrg8FXc1T1/img.png&quot; data-alt=&quot;언어 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfCS2c/btsBqzFeAGs/USQ3R4KlQ5FFMrg8FXc1T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfCS2c%2FbtsBqzFeAGs%2FUSQ3R4KlQ5FFMrg8FXc1T1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;537&quot; height=&quot;204&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;언어 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 SvelteKit을 선택하면 말짱 도루묵이 되어버립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입맛에 맞는 언어를 선택합니다. 저는 상남자라 JavaScript를 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 터미널에 아래 내용을 입력하면, 프로젝트로 이동하고 라이브러리 설치 후, 실행할 겁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701706509029&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  cd vite-project
  npm install
  npm run dev&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1KVHZ/btsBk9ALz1p/55gLFLW2lqNNJxTksvoQF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1KVHZ/btsBk9ALz1p/55gLFLW2lqNNJxTksvoQF1/img.png&quot; data-alt=&quot;실행 장면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1KVHZ/btsBk9ALz1p/55gLFLW2lqNNJxTksvoQF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1KVHZ%2FbtsBk9ALz1p%2F55gLFLW2lqNNJxTksvoQF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;763&quot; height=&quot;612&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실행 장면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 돌아가는군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 디렉토리를 보면 아래와 같이 기본 세팅 되어 있는데, 개발을 하는 영역은 src 내부입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZQ3iP/btsBqdJeVNy/D6rmWVnuQxPBz5weWGHsnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZQ3iP/btsBqdJeVNy/D6rmWVnuQxPBz5weWGHsnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZQ3iP/btsBqdJeVNy/D6rmWVnuQxPBz5weWGHsnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZQ3iP%2FbtsBqdJeVNy%2FD6rmWVnuQxPBz5weWGHsnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;284&quot; height=&quot;404&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 내부 코드를 자세히 보시면 App.svelte가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 svelte 확장자는 모릅니다. 그래서 html, css, js로 바꿔주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 github page와 같은 곳에 이 Svelte로 만든 프로젝트를 올리고 싶다면 빌드를 하여 html, css, js를 추출해야 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vite가 바로 이 Svelte를 번들링하고 빌드도 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에 가볍에 아래의 내용을 입력해봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701706922818&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eoZLNt/btsBqe2rXjp/6JKvyXDCOdqQr2U5ecKY70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eoZLNt/btsBqe2rXjp/6JKvyXDCOdqQr2U5ecKY70/img.png&quot; data-alt=&quot;빌드 된 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eoZLNt/btsBqe2rXjp/6JKvyXDCOdqQr2U5ecKY70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeoZLNt%2FbtsBqe2rXjp%2F6JKvyXDCOdqQr2U5ecKY70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;287&quot; height=&quot;407&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빌드 된 파일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 dist 파일 안에 빌드 된 파일들이 남게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 오직 웹 브라우저를 위한 html, css, js 만 남게 됩니다. (+이미지 등 리소스 파일들)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 빌드파일을 nodejs, 번들러 등이 프로젝트를 실행할 수 없는 환경에서 배포해서 실행시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 github page나 firebase hosting 등이 있는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이제 이걸로 크롬 익스텐션 개발에 적용해볼 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MRf1B/btsBj7pGaLF/cOVKHk8l4bSQKfFEUNoZ30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MRf1B/btsBj7pGaLF/cOVKHk8l4bSQKfFEUNoZ30/img.png&quot; data-alt=&quot;sveltekit에서 빌드한 내용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MRf1B/btsBj7pGaLF/cOVKHk8l4bSQKfFEUNoZ30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMRf1B%2FbtsBj7pGaLF%2FcOVKHk8l4bSQKfFEUNoZ30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;138&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sveltekit에서 빌드한 내용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위는 SvelteKit에서 빌드한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 빌드해서 내부를 보시면 아시겠지만, 복잡하고 필요없는 서버 로직까지 함께 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 처음엔 SvelteKit으로 어찌 해보려다가 포기했습니다 ^_^&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하지만 많이 쓰는 지식. 메모 겸 공유하기 위해 남깁니다!&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>CSR</category>
      <category>javascript</category>
      <category>nodejs</category>
      <category>svelte</category>
      <category>Vite</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/141</guid>
      <comments>https://wooncloud.tistory.com/141#entry141comment</comments>
      <pubDate>Tue, 5 Dec 2023 01:33:24 +0900</pubDate>
    </item>
    <item>
      <title>서버에서 이메일 발신자 한글(UTF-8)로 설정하기 feat. AWS SES</title>
      <link>https://wooncloud.tistory.com/140</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;이메일 발신자 한글.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0sQpH/btsAJHSNf1b/R6i0qrUriw0W2ubTKKPopK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0sQpH/btsAJHSNf1b/R6i0qrUriw0W2ubTKKPopK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0sQpH/btsAJHSNf1b/R6i0qrUriw0W2ubTKKPopK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0sQpH%2FbtsAJHSNf1b%2FR6i0qrUriw0W2ubTKKPopK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;270&quot; data-filename=&quot;이메일 발신자 한글.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플로우를 개발하면서 메일과 관련된 기능을 개발하던 중, 서버에서 메일을 발송하는데 발신자 이름이 한글로 안나오고 글자가 깨지는 현상을 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일과 관련된 공통 지식인 것 같아서 블로그에 올려봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;57&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHkCZJ/btsAMZK7VkD/WRCHyCSKNsnc46qUlnAwb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHkCZJ/btsAMZK7VkD/WRCHyCSKNsnc46qUlnAwb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHkCZJ/btsAMZK7VkD/WRCHyCSKNsnc46qUlnAwb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHkCZJ%2FbtsAMZK7VkD%2FWRCHyCSKNsnc46qUlnAwb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;57&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;57&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증상은 위와 같이 발신자 이름이 한글로 깨지는 문제가 발생 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발신자 이름은 &quot;협업툴 플로우&quot; 라고 보냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일 서비스는 AWS SES를 사용하고 있었는데, 아무리 인코딩을 해서 보내도 계속 깨지는 문제가 있어서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇이 문제인지 찾아보다 AWS에 문의를 넣은 적이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AWS 측 답변&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You would need to use MIME encoded-word syntax to use non-ASCII characters in the&amp;nbsp;Source&amp;nbsp;text. There's some additional discussion in the&amp;nbsp;&lt;a href=&quot;http://docs.aws.amazon.com/ses/latest/APIReference/API_SendEmail.html&quot;&gt;SES API reference&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;In all cases, the email address must be 7-bit ASCII. If the text must contain any other characters, then you must use MIME encoded-word syntax (RFC 2047) instead of a literal string. MIME&amp;nbsp;encoded-word syntax uses the following form: =?charset?encoding?encoded-text?=. For more information, see&amp;nbsp;RFC 2047.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Source 부분에 ASCII characters 외의 다른 문자를 사용하려면, &lt;br /&gt;이메일에서 사용하는 Media-Type(MIME)라는 타입으로 변환해서 바꿔주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MIME Syntax&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;=?charset?encoding?encoded-text?=&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;charset : UTF-8 같은 인코딩 방식&lt;/li&gt;
&lt;li&gt;encoding : B 타입과 Q 타입이 있음. B는 Base64인것 같고, Q는 quoted-printable 의 약자라고 함. RFC 1342 문서 참고&lt;/li&gt;
&lt;li&gt;encoded-text : 내용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 예시 : =?utf-8?B?협업툴 플로우?=&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;적용 코드&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/szlAy/btsAMkIKsKS/rXfIWZZXc1KI4YFgGFMnhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/szlAy/btsAMkIKsKS/rXfIWZZXc1KI4YFgGFMnhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/szlAy/btsAMkIKsKS/rXfIWZZXc1KI4YFgGFMnhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FszlAy%2FbtsAMkIKsKS%2FrXfIWZZXc1KI4YFgGFMnhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;430&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static String getSesMailFrom(String senderAddress, String SenderName) {
    String senderEncodeBase64 = Base64.getEncoder().encodeToString(SenderName.getBytes());
    String senderMimeSyntax = &quot;=?UTF-8?B?&quot; + senderEncodeBase64 + &quot;?=&quot;;
    return senderMimeSyntax + &quot; &amp;lt;&quot; + senderAddress + &quot;&amp;gt;&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 해석하자면, 저는 &quot;협업툴 플로우 &amp;lt;info@flow.team&amp;gt;&quot; 이라는 형식으로 발신자를 보내고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저렇게 이메일을 보내면 발신자 이름엔 &quot;협업툴 플로우&quot; 라고 나오고 실제 발신자 주소는 &quot; info@flow.team&quot;로 나오게 되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &quot;협업툴 플로우&quot;라는 String을 자바에서 &lt;code&gt;Base64.getEncoder().encodeToString(SenderName.getBytes());&lt;/code&gt; 로 인코딩을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 Mime 문법으로 &quot;=?UTF-8?B?&quot; + 인코딩한 문자 + &quot;?=&quot; 로 감싸줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 &quot;=?UTF-8?B?협업툴 플로우?=&quot; 가 되겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 뒤에 이메일 주소를 붙이면 완성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;code&gt;&quot;=?UTF-8?B?협업툴 플로우?= &amp;lt; info@flow.team&amp;gt;&quot;&lt;/code&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;결과&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bumLL2/btsANJ85BN6/ZXkXszeTGN24edGJjMFd9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bumLL2/btsANJ85BN6/ZXkXszeTGN24edGJjMFd9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bumLL2/btsANJ85BN6/ZXkXszeTGN24edGJjMFd9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbumLL2%2FbtsANJ85BN6%2FZXkXszeTGN24edGJjMFd9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;2189&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2189&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발 아카이브/AWS, Cloud, Server</category>
      <category>MIME</category>
      <category>SES</category>
      <category>이메일</category>
      <category>인코딩</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/140</guid>
      <comments>https://wooncloud.tistory.com/140#entry140comment</comments>
      <pubDate>Thu, 23 Nov 2023 10:07:10 +0900</pubDate>
    </item>
    <item>
      <title>REST (Representational State Transfer)란 무엇인가?</title>
      <link>https://wooncloud.tistory.com/139</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;REST.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Nkmo/btspsUgyfFs/VskdxigiVZIOq7PikKIYwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Nkmo/btspsUgyfFs/VskdxigiVZIOq7PikKIYwK/img.png&quot; data-alt=&quot;REST&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Nkmo/btspsUgyfFs/VskdxigiVZIOq7PikKIYwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Nkmo%2FbtspsUgyfFs%2FVskdxigiVZIOq7PikKIYwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;REST&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;REST.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;REST&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 Representational State Transfer 줄임말이며, 웹 서비스 아키텍처의 한 형태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 자원을 정의하고, 자원에 대한 주소를 지정하는 통신 규약입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신 규약! 즉,일종의 약속입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7iaoo/btspmVAgMuQ/0XHPxlKt11aKn0sust8kAK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7iaoo/btspmVAgMuQ/0XHPxlKt11aKn0sust8kAK/tfile.svg&quot; data-alt=&quot;https://www.codecademy.com/article/what-is-rest&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7iaoo/btspmVAgMuQ/0XHPxlKt11aKn0sust8kAK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7iaoo%2FbtspmVAgMuQ%2F0XHPxlKt11aKn0sust8kAK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;REST2&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;233&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.codecademy.com/article/what-is-rest&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;REST의 기본 원칙&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;자원 (Resource)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서비스에서 제공하는 모든 것들을 각각의 이름으로 식별합니다. 예를 들어, 사용자 정보 &lt;b&gt;&lt;code&gt;/users&lt;/code&gt;&lt;/b&gt;, 제품 정보 &lt;b&gt;&lt;code&gt;/products&lt;/code&gt;&lt;/b&gt; 등 각각이 자원입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;resource.png&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQrBCm/btsppHPizVi/S06xkRw7gCZFTGDJB3HhE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQrBCm/btsppHPizVi/S06xkRw7gCZFTGDJB3HhE1/img.png&quot; data-alt=&quot;https://news.blizzard.com/en-us/starcraft2/673124/the-price-of-victory-managing-resources&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQrBCm/btsppHPizVi/S06xkRw7gCZFTGDJB3HhE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQrBCm%2FbtsppHPizVi%2FS06xkRw7gCZFTGDJB3HhE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;resource&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;184&quot; data-filename=&quot;resource.png&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://news.blizzard.com/en-us/starcraft2/673124/the-price-of-victory-managing-resources&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;표현 (Representation)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 자원은 클라이언트가 원하는 형식으로 표현되어야 합니다. 흔히 사용하는 형식은 JSON, XML, 텍스트 등이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1084&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebpPeQ/btsplO9sBs6/aqD7DnMJEeAxJ03St59kq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebpPeQ/btsplO9sBs6/aqD7DnMJEeAxJ03St59kq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebpPeQ/btsplO9sBs6/aqD7DnMJEeAxJ03St59kq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebpPeQ%2FbtsplO9sBs6%2FaqD7DnMJEeAxJ03St59kq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;json&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;198&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1084&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;상태 없음 (Stateless)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 클라이언트의 상태를 기억하지 않습니다. 각각의 요청은 독립적으로 처리되며, 클라이언트는 모든 정보를 요청에 포함시켜야 합니다. 서버는 각각의 요청을 독립적으로 이해하고 처리합니다. 이를 통해 서버의 확장성과 클라이언트와 서버 간의 느슨한 결합을 지원합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;연결 (Uniform Interface)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통일된 방식으로 자원을 조작할 수 있어야 합니다. 자원에 대한 조회, 생성, 수정, 삭제 등의 기능은 각각의 HTTP 메소드(GET, POST, PUT, DELETE)를 사용하여 처리합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx8gzN/btspl7nD4xU/Jxryrur7mNvun1uiWjZT50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx8gzN/btspl7nD4xU/Jxryrur7mNvun1uiWjZT50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx8gzN/btspl7nD4xU/Jxryrur7mNvun1uiWjZT50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx8gzN%2Fbtspl7nD4xU%2FJxryrur7mNvun1uiWjZT50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Uniform Interface&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;172&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요청 메소드&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GET ⭐&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 리소스를 조회하기 위해 사용하는 메소드입니다. 요청된 URI의 정보를 가져와서 응답으로 보냅니다. 데이터를 조회하는 용도로 사용되며, 요청에 별도의 데이터를 담지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &lt;b&gt;&lt;code&gt;GET https://example.com/users&lt;/code&gt;&lt;/b&gt; (모든 사용자 정보 조회)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;POST &lt;b&gt;⭐&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 새로운 자원을 생성하기 위해 사용하는 메소드입니다. 요청에 생성하고자 하는 자원의 데이터를 함께 담아서 보냅니다. 일반적으로 데이터를 서버에 제출하는 용도로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &lt;b&gt;&lt;code&gt;POST https://example.com/users&lt;/code&gt;&lt;/b&gt; (새로운 사용자 생성)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;PUT &lt;b&gt;⭐&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 자원을 수정하기 위해 사용하는 메소드입니다. 요청에 수정하고자 하는 자원의 데이터를 함께 담아서 보냅니다. 기존 자원을 완전히 대체하는 방식으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &lt;b&gt;&lt;code&gt;PUT https://example.com/users/123&lt;/code&gt;&lt;/b&gt; (ID가 123인 사용자 정보를 수정)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;PATCH&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 자원을 부분적으로 수정하기 위해 사용하는 메소드입니다. 요청에 수정하고자 하는 자원의 일부 데이터만 담아서 보냅니다. PUT과 달리, 일부 데이터만 업데이트하고 나머지 데이터는 그대로 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &lt;b&gt;&lt;code&gt;PATCH https://example.com/users/123&lt;/code&gt;&lt;/b&gt; (ID가 123인 사용자 정보에서 일부 내용 수정)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DELETE &lt;b&gt;⭐&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 자원을 삭제하기 위해 사용하는 메소드입니다. 요청에 삭제하고자 하는 자원의 데이터를 함께 담아서 보냅니다. 해당 자원을 삭제하고 응답으로 성공 여부를 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &lt;b&gt;&lt;code&gt;DELETE https://example.com/users/123&lt;/code&gt;&lt;/b&gt; (ID가 123인 사용자 삭제)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HEAD&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET 메소드와 유사하지만, 실제 데이터를 반환하지 않고 응답 헤더만 가져오는 메소드입니다. 주로 자원의 메타데이터를 확인하는 용도로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &lt;b&gt;&lt;code&gt;HEAD https://example.com/users&lt;/code&gt;&lt;/b&gt; (모든 사용자 정보의 헤더 확인)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OPTIONS&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 지원하는 메소드들을 확인하기 위해 사용하는 메소드입니다. 주로 CORS(Cross-Origin Resource Sharing) 요청에서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: &lt;b&gt;&lt;code&gt;OPTIONS https://example.com/users&lt;/code&gt;&lt;/b&gt; (서버가 /users 자원에 지원하는 메소드 확인)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TRACE&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 목적으로 사용되며, 클라이언트가 보낸 요청이 서버에 도달하는 경로를 확인하기 위해 사용됩니다. 보안상의 이유로 대부분 비활성화되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;RESTful 웹 서비스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful 웹 서비스는 REST 원칙을 준수하는 웹 서비스를 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 만들 웹사이트가 RESTful 서비스가 되려면 아까 위에 있던 &lt;u&gt;&lt;b&gt;REST의 기본 원칙&lt;/b&gt;&lt;/u&gt;을 잘 준수해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예시 (example)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 블로그 웹 사이트를 만들어본다고 상상해봅시다. 이때 RESTful 웹 서비스를 활용하여 사용자 정보와 블로그 글을 관리하는 방법을 살펴봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;glenn-carstens-peters-npxXWgQ33ZQ-unsplash.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K1tGS/btsplaLJRL8/j4rNS4ugoGFPXRmpKKmzU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K1tGS/btsplaLJRL8/j4rNS4ugoGFPXRmpKKmzU1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K1tGS/btsplaLJRL8/j4rNS4ugoGFPXRmpKKmzU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK1tGS%2FbtsplaLJRL8%2Fj4rNS4ugoGFPXRmpKKmzU1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;blog&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;233&quot; data-filename=&quot;glenn-carstens-peters-npxXWgQ33ZQ-unsplash.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자원 (Resource)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자 정보&lt;/b&gt;와 &lt;b&gt;블로그 글&lt;/b&gt;을 각각 다른 자원으로 생각합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;표현 (Representation)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정보는 &lt;b&gt;JSON 형식&lt;/b&gt;으로 표현하고, 블로그 글은 &lt;b&gt;HTML 형식&lt;/b&gt;으로 표현합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;상태 없음 (Stateless)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 요청은 클라이언트가 필요한 모든 정보를 함께 보내야 합니다. 예를 들어, 새로운 블로그 글을 작성할 때, 작성자 이름과 글 내용을 요청에 함께 담아서 보내야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;연결 (Uniform Interface)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정보를 조회할 때는 GET 메소드를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시: &lt;b&gt;&lt;code&gt;GET https://example.com/users/123&lt;/code&gt;&lt;/b&gt; (123은 사용자 고유의 식별자)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 블로그 글을 작성할 때는 POST 메소드를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시: &lt;b&gt;&lt;code&gt;POST https://example.com/blogs&lt;/code&gt;&lt;/b&gt; (요청에 작성자 이름과 글 내용을 함께 담아서 보냄)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 수정할 때는 PUT 메소드를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시: &lt;b&gt;&lt;code&gt;PUT https://example.com/blogs/456&lt;/code&gt;&lt;/b&gt; (456은 글의 고유 식별자, 요청에 수정된 글 내용을 함께 담아서 보냄)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 삭제할 때는 DELETE 메소드를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시: &lt;b&gt;&lt;code&gt;DELETE https://example.com/blogs/456&lt;/code&gt;&lt;/b&gt; (456은 삭제할 글의 고유 식별자)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RESTful URI 설계 규칙&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful한 웹서비스에서 URI 설계는 중요합니다. RESTful한 웹 서비스를 위해 일관적이고 직관적인 URI 구조를 갖추도록 잘 구성해야 합니다. 아래는 일반적으로 사용되는 RESTful URI 설계 규칙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;명사 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자원(리소스)을 나타내는 URI는 명사로 표현합니다. 동사는 자원의 행위(조회, 생성, 수정, 삭제 등)를 나타내는 것이므로 URI에 포함하지 않습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 예:&amp;nbsp;&lt;code&gt;/users, /posts, /products&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;잘못된 예:&amp;nbsp;&lt;code&gt;/getUsers, /createPost, /deleteProduct&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;복수형 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 자원을 나타내는 경우에는 복수형으로 표현합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 예:&amp;nbsp;&lt;code&gt;/users, /posts, /products&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;잘못된 예:&amp;nbsp;&lt;code&gt;/user, /post, /product&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자원 계층 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자원의 계층 구조를 적절히 반영하여 URI를 설계합니다. 예를 들어, 사용자와 사용자의 게시글을 나타내는 경우 계층 구조로 표현할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 예:&amp;nbsp;&lt;code&gt;/users/{userId}/posts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;잘못된 예:&amp;nbsp;&lt;code&gt;/usersAndPosts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;동사 사용은 피하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자원의 행위(동작)는 HTTP 메서드를 사용하여 나타냅니다. URI에는 동사를 사용하지 않습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 예:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;GET /users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /posts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /products/{productId}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;잘못된 예:&amp;nbsp;&lt;code&gt;/getUsers, /createPost, /updateProduct/{productId}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;소문자 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URI는 소문자로 표기하며, 가독성을 위해 하이픈(-)을 사용하여 단어를 구분합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 예:&amp;nbsp;&lt;code&gt;/users, /user-posts, /product-categories&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;잘못된 예:&amp;nbsp;&lt;code&gt;/Users, /UserPosts, /ProductCategories&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;URI 파라미터 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자원 식별에 필요한 정보는 URI 파라미터로 표현합니다. 예를 들어, 특정 사용자의 정보를 조회하는 경우 사용자 ID를 파라미터로 받습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예:&amp;nbsp;&lt;code&gt;/users/{userId}, /posts/{postId}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;가능한 단순한 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URI는 가능한 단순하고 직관적인 구조를 가지도록 설계합니다. 너무 복잡하거나 긴 URI는 이해하기 어렵고 유지보수가 어려울 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 예:&amp;nbsp;&lt;code&gt;/users/{userId}/posts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;비추천 예:&amp;nbsp;&lt;code&gt;/users/{userId}/categories/{categoryId}/posts/{postId}/comments&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;슬래시(/) 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층적인 구조를 표현할 때 슬래시(/)를 사용하여 각각의 계층을 구분합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 예:&amp;nbsp;&lt;code&gt;/users/{userId}/posts/{postId}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;비추천 예:&amp;nbsp;&lt;code&gt;/users-{userId}-posts-{postId}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 간단하면서도 확장 가능하며 유지 보수가 쉬운 웹 서비스를 만들기 위한 강력한 아키텍처 스타일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful 웹 서비스를 구축하면..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 서버 간의 상호작용이 &lt;b&gt;&lt;u&gt;원활&lt;/u&gt;&lt;/b&gt;! &lt;b&gt;&lt;u&gt;일관적&lt;/u&gt;&lt;/b&gt;! 그리고 다양한 플랫폼과 언어에 제약받지 않고 &lt;b&gt;&lt;u&gt;호환성&lt;/u&gt;&lt;/b&gt;을 보장할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/개발 관련 지식</category>
      <category>Rest</category>
      <category>RESTful</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/139</guid>
      <comments>https://wooncloud.tistory.com/139#entry139comment</comments>
      <pubDate>Mon, 31 Jul 2023 00:40:03 +0900</pubDate>
    </item>
    <item>
      <title>REPL(Read Eval Print Loop) 이란?</title>
      <link>https://wooncloud.tistory.com/138</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0U6W0/btspcvhR8yv/TqfVh5JQzTyrdpH6EcRLcK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0U6W0/btspcvhR8yv/TqfVh5JQzTyrdpH6EcRLcK/img.webp&quot; data-alt=&quot;https://www.scaler.com/topics/nodejs/node-js-repl/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0U6W0/btspcvhR8yv/TqfVh5JQzTyrdpH6EcRLcK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0U6W0%2FbtspcvhR8yv%2FTqfVh5JQzTyrdpH6EcRLcK%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;REPL&quot; loading=&quot;lazy&quot; width=&quot;540&quot; height=&quot;498&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.scaler.com/topics/nodejs/node-js-repl/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REPL은 &quot;Read Eval Print Loop&quot;의 약자로, 인터프리터 기반의 프로그래밍 언어에서 사용되는 대화식 환경을 제공하는 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REPL은 사용자가 코드를 입력하면 해당 코드를 읽어들이고(Read), 평가하여 실행합니다(Eval), 그리고 실행 결과를 출력합니다(Print). 이후 다시 새로운 코드를 입력할 수 있도록 반복적으로 동작합니다(Loop).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;REPL의 동작&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REPL은&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;동작합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;Read&amp;nbsp;(읽기):&amp;nbsp;사용자가&amp;nbsp;입력한&amp;nbsp;JavaScript&amp;nbsp;코드를&amp;nbsp;읽습니다. &lt;br /&gt;2.&amp;nbsp;Eval&amp;nbsp;(평가):&amp;nbsp;읽은&amp;nbsp;코드를&amp;nbsp;평가하고&amp;nbsp;실행합니다. &lt;br /&gt;3.&amp;nbsp;Print&amp;nbsp;(출력):&amp;nbsp;코드의&amp;nbsp;결과&amp;nbsp;값을&amp;nbsp;출력합니다. &lt;br /&gt;4.&amp;nbsp;Loop&amp;nbsp;(반복):&amp;nbsp;이후&amp;nbsp;사용자가&amp;nbsp;더&amp;nbsp;많은&amp;nbsp;코드를&amp;nbsp;입력할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;반복합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EEzt8/btspgoBRpjI/BBl6XgjGfsQGHPfBZESQLk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EEzt8/btspgoBRpjI/BBl6XgjGfsQGHPfBZESQLk/img.webp&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1315&quot; data-is-animation=&quot;false&quot; width=&quot;300&quot; height=&quot;329&quot; style=&quot;width: 30.603%; margin-right: 10px;&quot; data-widthpercent=&quot;31.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EEzt8/btspgoBRpjI/BBl6XgjGfsQGHPfBZESQLk/img.webp&quot; alt=&quot;파이썬&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEEzt8%2FbtspgoBRpjI%2FBBl6XgjGfsQGHPfBZESQLk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;1315&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRKWRq/btspg1NhTiE/Y1Xp0KkqjvlGOghMNtFOW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRKWRq/btspg1NhTiE/Y1Xp0KkqjvlGOghMNtFOW1/img.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1200&quot; data-is-animation=&quot;false&quot; style=&quot;width: 33.5357%; margin-right: 10px;&quot; data-widthpercent=&quot;34.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRKWRq/btspg1NhTiE/Y1Xp0KkqjvlGOghMNtFOW1/img.png&quot; alt=&quot;루비&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRKWRq%2Fbtspg1NhTiE%2FY1Xp0KkqjvlGOghMNtFOW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DzEMD/btso6SSwiWj/XUUC1KLVNthzrXkLxm9sc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DzEMD/btso6SSwiWj/XUUC1KLVNthzrXkLxm9sc0/img.png&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&quot; data-is-animation=&quot;false&quot; style=&quot;width: 33.5357%;&quot; data-widthpercent=&quot;34.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DzEMD/btso6SSwiWj/XUUC1KLVNthzrXkLxm9sc0/img.png&quot; alt=&quot;js&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDzEMD%2Fbtso6SSwiWj%2FXUUC1KLVNthzrXkLxm9sc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;225&quot; height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;REPL을 사용하는 언어들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REPL은 여러 프로그래밍 언어와 환경에서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 스크립트 언어나 인터프리터 언어에서 많이 사용되며, 대표적으로 Python, Ruby, JavaScript 등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 언어들은 REPL을 통해 개발자들이 간단한 코드를 빠르게 작성하고 테스트할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Python&lt;/b&gt;: &quot;Python Shell&quot; 또는 &quot;Python REPL&quot;이라고도 불리는 대화식 셸을 제공합니다. python이라고 입력하면 Python REPL이 시작됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ruby&lt;/b&gt;: Ruby에서도 &quot;IRB&quot; (Interactive Ruby)라고 불리는 REPL 환경이 있습니다. irb 명령어를 실행하면 Ruby REPL이 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JavaScript (브라우저 콘솔)&lt;/b&gt;: 웹 브라우저의 콘솔창도 JavaScript 코드 REPL입니다.&lt;br /&gt;개발자 도구(F12 또는 Ctrl + Shift + I를 눌러 엽니다.)에서 &quot;Console&quot; 탭을 사용하면 JavaScript 코드를 입력하고 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JavaScript (Node.js)&lt;/b&gt;: Node.js의 REPL은 JavaScript 코드를 대화식으로 실행할 수 있는 환경을 제공합니다. 브라우저 콘솔과는 다르게 Node.js REPL은 서버 측에서 JavaScript 코드를 실행하며, &lt;u&gt;파일 시스템 접근 등 브라우저에서 사용할 수 없는 기능도 사용할 수 있습니다.&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;REPL의&amp;nbsp;장점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REPL은 테스트가 쉽고 빠른 피드백을 얻을 수 있는 것이 장점입니다. 그래서 개발자가 실시간으로 코드를 테스트하고 디버깅할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외 언어나 기능을 학습하기 좋고 라이브러리를 실험하기도 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;노드(Node.js)의&amp;nbsp;REPL&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드에서 REPL을 실행하려면 다음과 같이 Node.js를 설치하고 명령줄에서 실행하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 Node.js가 설치된 상태여야 합니다.&lt;br /&gt;&lt;br /&gt;1.&amp;nbsp;터미널(혹은&amp;nbsp;명령&amp;nbsp;프롬프트)을&amp;nbsp;엽니다. &lt;br /&gt;2. 'node'라고 입력하고 Enter 키를 누릅니다. &lt;br /&gt;3. 끝! REPL이 실행 되었습니다. 브라우저 콘솔처럼 바로 JavaScript 코드를 입력하고 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;예시 (example)&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1690474331274&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ node
&amp;gt; let x = 10;
undefined
&amp;gt; let y = 20;
undefined
&amp;gt; x + y;
30
&amp;gt; function add(a, b) { return a + b; }
undefined
&amp;gt; add(5, 3);
8
&amp;gt; exit&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널이나 프롬프트에서 `node` 명령어를 실행하면 REPL이 시작됩니다.&lt;/li&gt;
&lt;li&gt;위 예제에서 `&amp;gt;`는 사용자 입력을 받는 프롬프트를 나타냅니다.&lt;/li&gt;
&lt;li&gt;REPL에서는 변수를 선언하고 계산을 수행하거나 함수를 정의하고 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;종료하려면 `exit`를 입력하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 간단하게 파일시스템 모듈을 사용하여 txt를 쓰는 예시입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1690474446842&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ node
&amp;gt; const message = &quot;Hello, Node.js!&quot;;
undefined
&amp;gt; console.log(message);
Hello, Node.js!
undefined
&amp;gt; const fs = require('fs');
undefined
&amp;gt; fs.writeFileSync('example.txt', '이것은 Node.js의 REPL에서 만든 파일입니다.');
undefined
&amp;gt; exit&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5k5JT/btspfOUWXEB/CIe0KNbNp4PJzTVzi5iDzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5k5JT/btspfOUWXEB/CIe0KNbNp4PJzTVzi5iDzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5k5JT/btspfOUWXEB/CIe0KNbNp4PJzTVzi5iDzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5k5JT%2FbtspfOUWXEB%2FCIe0KNbNp4PJzTVzi5iDzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;172&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하면 터미널을 오픈한 위치에 파일이 하나 생겼을 겁니다.&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>javascript</category>
      <category>nodejs</category>
      <category>REPL</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/138</guid>
      <comments>https://wooncloud.tistory.com/138#entry138comment</comments>
      <pubDate>Fri, 28 Jul 2023 01:30:18 +0900</pubDate>
    </item>
    <item>
      <title>2023 상반기 회고 1편 (플로우, 도시부엉)</title>
      <link>https://wooncloud.tistory.com/137</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;23년 상반기 회고.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehHivi/btsnLbEmuZS/NS9MbLsquU4D1qif2wmACk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehHivi/btsnLbEmuZS/NS9MbLsquU4D1qif2wmACk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehHivi/btsnLbEmuZS/NS9MbLsquU4D1qif2wmACk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FehHivi%2FbtsnLbEmuZS%2FNS9MbLsquU4D1qif2wmACk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;title image&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;23년 상반기 회고.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늘 메모와 기록을 중요시 생각해 왔지만, 요즘 들어 더더욱 기록이 중요하다고 느끼고 있다. 내가 늘 성장을 하고 싶고, 성장을 하려면 나를 돌아보는 시간을 가지는 것이 필요하다. 기록할 때가 바로 그때이다. 요즘은 예전에 비해 기록하는 습관이 적어진 것 같다. 최근에 하는 일이 많아서 그렇다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 이 생각을 하게 되었으니 지금 당장 상반기 회고를 통해 기록을 하는 습관을 길러보려고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;플로우 (flow)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UqN8K/btsnOet0ucT/QCtx9zQdipK6WpzWKtGKDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UqN8K/btsnOet0ucT/QCtx9zQdipK6WpzWKtGKDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UqN8K/btsnOet0ucT/QCtx9zQdipK6WpzWKtGKDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUqN8K%2FbtsnOet0ucT%2FQCtx9zQdipK6WpzWKtGKDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;flow&quot; loading=&quot;lazy&quot; width=&quot;403&quot; height=&quot;116&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째로 우리 회사 서비스인 플로우를 개발하고 유지보수를 어떤 것을 해왔는지, 어떤 생각을 했는지 적어보자!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;올해 상반기 내가 맡은 일&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;플로우 플랜과 프로 버전&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1044&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IiAys/btsnFLzVXra/QkqbB9PanKtPZ1edjq4JZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IiAys/btsnFLzVXra/QkqbB9PanKtPZ1edjq4JZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IiAys/btsnFLzVXra/QkqbB9PanKtPZ1edjq4JZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIiAys%2FbtsnFLzVXra%2FQkqbB9PanKtPZ1edjq4JZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;flow plans&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;305&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1044&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플로우 플랜과 프로버전은 작년 12월부터 시작하여 올해 1월쯤에 개발이 끝난 기능이다. 플랜 기능은 짧은 시간 내 만들어야 했던 힘든 일이었다. 솔직히 기능 자체 개발 난이도는 높진 않았지만, 일이 꽤 힘들었다. 짧은 워크타임이 이 일의 난이도를 극강으로 높여준 것이 아닐까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 일이 날 힘들게 만든 이유를 생각해보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;첫번째로 짧은 기간에 빨리 만들어달라고 하는 압박&lt;/li&gt;
&lt;li&gt;개발자도 기획자도 어떻게 어디까지 만들고 정책이 어떻게 되는지 모르는 막막함.&lt;/li&gt;
&lt;li&gt;유연하고 탄탄한 개발을 하고 싶지만, 시간 때문에 어쩔 수 없이 하드코딩하며 현탐에 빠짐.&lt;/li&gt;
&lt;li&gt;그럼에도 이 코드를 정리할 시간을 주지 않는다는 현실을 아는 나.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용은 무계획이 만들어낸 결과들이 아닐까 생각이 든다. 나는 역시 J인 것인가?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;&gt;&lt;a href=&quot;https://kr.freepik.com/free-vector/isometric-time-management-concept_12303817.htm#query=3d&quot; target=&quot;_blank&quot; title=&quot;Freepik&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wjwgM/btsnFfuAgYF/7U7xjKSfvODtrAz7Y3xGO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwjwgM%2FbtsnFfuAgYF%2F7U7xjKSfvODtrAz7Y3xGO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;time is gold&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;240&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 우리의 머찐 팀장님께서 어느 정도 개발을 어떻게 할 지 길을 잘 설명해줘서 어느정도 감을 잡을 수 있었다. 급할수록 하드코딩이 난무할 수 있는데, 나름 유연하게 만들어져서 좋은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랜 기능은 우리 개발자와 운영자에게 전반적으로 이해가 안 되는 어려운 기능이라고 생각했다. 그래서 문서 작업이 무조건 필요하다고 생각했는데, 사실상 시간이 너무 없고 정신없어서 문서를 작성할 겨를이 없었다. 그래도 이 기능은 문서작업이 필수지 않을까? 어느 정도 적어놓긴 했는데.. 지금이라도 당장 해야 하나? 아마 플랜 기능만 가지고 회고를 써도 긴 글이 나올 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 이 플랜 기능의 레거시들을 조금 정리했다. 초반에 해외 사용자들을 위한 플랜을 만들어서 제공했지만, 이제는 더 이상 사용되지 않아 쓰지 않는 플랜들을 정리하고 제거하는 작업을 거쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 하나씩 더 정리를 해야 한다고 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;보안 기능&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xlbXF/btsnE9ukGhp/qhQxGmwVklkN74gXmVkCl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xlbXF/btsnE9ukGhp/qhQxGmwVklkN74gXmVkCl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xlbXF/btsnE9ukGhp/qhQxGmwVklkN74gXmVkCl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxlbXF%2FbtsnE9ukGhp%2FqhQxGmwVklkN74gXmVkCl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;admin security&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;265&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 플랜과 다르게 탄탄하게 만들어졌다고 생각하는 보안기능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안기능은 사내 IP를 등록함으로써, 지정한 IP 외 다른 IP에서 로그인, 파일 조회, 다운로드를 막는 기능이다. 이 기능은 난이도가 좀 있다고 생각한다. 그러나 개발하면서 힘들거나 스트레스받지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 시간적 여유가 있고 테스트가 잘 이루어져 정책대로 기능이 동작하는 깔끔하게 끝난 프로젝트라고 생각한다. 그래서 이 기능을 좀 더 고도화하고 리펙토링 하고 싶다는 미련도 없다. 그리고 생각보다 버그도 많이 보이지 않는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 기능을 만들면서 최적화에 대한 생각을 하지 못했다. 그 부분을 팀장님이 잘 캐치해 주시고 코드리뷰를 해주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 대량의 파일이 있다면 사용자가 각각의 파일에 접근 권한이 있는지 체크해야 한다. 그러나 그 과정에서 파일 하나하나마다 DB를 조회하는 비효율적인 방식으로 개발했고 그것이 문제인지 인지하지 못했다. 이번 계기로 반복문을 돌 때, 무의미한 호출이 많은지 확인하고 캐싱하여 최적화하는 습관을 길러야겠다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그 외의 기능&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;31164759_7744163.jpg&quot; data-origin-width=&quot;3550&quot; data-origin-height=&quot;2160&quot;&gt;&lt;a href=&quot;https://kr.freepik.com/free-psd/3d-rendering-of-graphic-design_31164759.htm#page=5&amp;amp;query=3d&amp;amp;position=16&amp;amp;from_view=search&amp;amp;track=sph&quot; target=&quot;_blank&quot; title=&quot;Freepik&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beKADh/btsnF38aFv7/wi6cNnTrktFmcnk0lrWxNK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeKADh%2FbtsnF38aFv7%2Fwi6cNnTrktFmcnk0lrWxNK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;etc&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;146&quot; data-filename=&quot;31164759_7744163.jpg&quot; data-origin-width=&quot;3550&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 기능들은 큰 기능을 다뤘지만, 작은 기능들도 많이 개발했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일 (스티비), 텍스트 배너, 채널 비즈니스 프로, 마스터어드민.. 등등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 특별히 언급할 내용이 없어서 패스!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스스로 개선한 일&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;팝업 Queue&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cry3Mt/btsnFD2XagH/1eKCxBlNk15NhTuXtBLjA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cry3Mt/btsnFD2XagH/1eKCxBlNk15NhTuXtBLjA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cry3Mt/btsnFD2XagH/1eKCxBlNk15NhTuXtBLjA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcry3Mt%2FbtsnFD2XagH%2F1eKCxBlNk15NhTuXtBLjA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;popups&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;194&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플로우에는 수많은 기능이 있다 보니, 다양한 경우에 다양한 팝업이 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 특별한 경우 팝업 위에 팝업, 위에 팝업&amp;hellip; 이 띄워져서 불편한 경우가 생겨버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하고 싶은데 어떻게 해결할까 생각하다가 Queue를 만들어서 해결하자는 생각을 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxxvF/btsnF7bFbq6/rXH0xgGuFITyphknbrhKrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxxvF/btsnF7bFbq6/rXH0xgGuFITyphknbrhKrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxxvF/btsnF7bFbq6/rXH0xgGuFITyphknbrhKrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxxvF%2FbtsnF7bFbq6%2FrXH0xgGuFITyphknbrhKrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;queue&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;188&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 팝업을 띄우는 이벤트를 Queue에 담아서, 선행 팝업이 닫히면 다음 팝업을 호출하도록 만들었다. 유지보수하면서 개인 프로젝트로 개발했는데, 결과적으로 잘 만들어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 팝업이 동시에 뜰 경우, 각 팝업의 우선순위가 있어 그 부분도 고려하여 만들어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나름 간단하면서 아무도 신경 쓰지 않는 부분을 개선하면서 그 기능이 잘 돌아가는 모습을 보면 나름 뿌듯하기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;데이터센터&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 완전 개선하진 않았고, 당장 필요한 것 같아서 행동부터 옮긴 일이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BDB7x/btsnF4MNR1N/x7oBxeA3ezxICbqsbxxkCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BDB7x/btsnF4MNR1N/x7oBxeA3ezxICbqsbxxkCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BDB7x/btsnF4MNR1N/x7oBxeA3ezxICbqsbxxkCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBDB7x%2FbtsnF4MNR1N%2Fx7oBxeA3ezxICbqsbxxkCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;data center&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;183&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 추출 업무는 우리 파트에서 맡고 있는데, 파트원 중 한 명씩 돌아가며 데이터 추출 업무를 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 데이터 업무와 개발 및 유지보수를 하는 것이 쉽지가 않다. 데이터 추출 자체가 시간이 오래 걸리기 때문이다. 물론 쿼리가 잘 만들어져 있지만, 추출하는데 1시간 정도 걸리는 큰 추출 작업도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 일을 자동화해서 우리 파트의 리소스를 확보하고 싶었다. 그래서 처음 직관적으로 생각한 것이 내부 마스터 페이지에서 원클릭으로 데이터를 추출하는 기능을 만들어야겠다고 생각했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 바로 계획 착수! 어찌어찌 만들어져서 조금 편해지긴 했다. 그러나, 이 기능을 보신 팀장님은 더 멀리 내다보고 아이디어를 제시하셨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpNvu1/btsnSW0WNNh/aalqQ7zQJhlkhVuzYkTm00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpNvu1/btsnSW0WNNh/aalqQ7zQJhlkhVuzYkTm00/img.png&quot; data-alt=&quot;https://www.datapine.com/dashboard-examples-and-templates/google-analytics&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpNvu1/btsnSW0WNNh/aalqQ7zQJhlkhVuzYkTm00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpNvu1%2FbtsnSW0WNNh%2FaalqQ7zQJhlkhVuzYkTm00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;dashboard&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;392&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1447&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.datapine.com/dashboard-examples-and-templates/google-analytics&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 통계 테이블이 없기 때문에, 매일 사용자의 사용 정보를 담는 테이블을 만들어서 빠르게 추출할 수 있는 기능을 만들자는 것. 그런 테이블을 만들면 데이터 추출 속도도 빠르고 다방면으로 통계를 위한 기능도 많이 만들 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현재 들어오는 기능개발과 QA가 우선순위가 높아서 지금은 보류된 상황.. 언제 만들 수 있을까..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;플로우 테크 세미나&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 회사에서는 매달 1번씩 테크 세미나라는 자리가 있다. 여기서 기술적인 이야기를 풀어나가는데, 이 세미나의 주최하고 계신 우리 팀장님 다음으로 최다 참여자이다. 팀장님 빼면 넘버원?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 회고에서도 얘기했지만, 이 플로우 테크 세미나를 많이 참여했다는 말을 했었고, 지금도 많이 참여하고 있다. 한번 참여하는데 작은 상금도 있지만, 이 세미나를 통해 공부하면서 성장하는 것도 크다. 이 세미나 덕분에 나는 정규식 장인이 되어버리기도 했다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 이 테크 세미나를 통해서 느낀 점이 있다면, 말을 좀 더 잘하고 싶다는 생각을 했다. 물론 여러 번 하면서 경험 쌓는 것도 중요하다. 하지만 매달 참여하면서 다른 분들이 참여하는 모습을 보곤 하는데, 다들 어찌 그렇게 발표를 잘하던지.. 많이 참여하는 나로서는 발전이 있어야 하는데, 아직 많이 부족한 모습이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PJL5S/btsnLbYDMMw/gdS6Aw51ovDaSK3PghN0X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PJL5S/btsnLbYDMMw/gdS6Aw51ovDaSK3PghN0X1/img.png&quot; data-alt=&quot;한참 V8에 대해 설명했던 지난 테크세미나 발표자료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PJL5S/btsnLbYDMMw/gdS6Aw51ovDaSK3PghN0X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPJL5S%2FbtsnLbYDMMw%2FgdS6Aw51ovDaSK3PghN0X1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;270&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;한참 V8에 대해 설명했던 지난 테크세미나 발표자료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 NodeJs나 PostgreSQL에 관심이 많은데, 앞으로 이쪽으로 발표해 보면서 공부하면 어떨까 생각하곤 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;도시부엉&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wl6Zy/btsnFdXNRvy/eQcXXckfTXuZ3IKVXyVDl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wl6Zy/btsnFdXNRvy/eQcXXckfTXuZ3IKVXyVDl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wl6Zy/btsnFdXNRvy/eQcXXckfTXuZ3IKVXyVDl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwl6Zy%2FbtsnFdXNRvy%2FeQcXXckfTXuZ3IKVXyVDl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;dosiowl google search&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;716&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8qVIe/btsnEMMTbay/Kn5vOrhujWvNIZdfQdChmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8qVIe/btsnEMMTbay/Kn5vOrhujWvNIZdfQdChmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8qVIe/btsnEMMTbay/Kn5vOrhujWvNIZdfQdChmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8qVIe%2FbtsnEMMTbay%2FKn5vOrhujWvNIZdfQdChmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;dosiowl profile&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;220&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도시부엉 시작한 지 1년이 되었다. 그동안 750 팔로우 정도 모았는데, 요즘 들어 예전과는 다르게 팔로우가 별로 모이지 않는다고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 팔로우들은 금방 1천 팔로우를 모으고 유명세를 타기 시작했는데, 어떻게 하면 저렇게 될까 고민을 많이 하곤 한다. 그래도 역시 정답은 자주 올리고 재미있는 콘텐츠를 제작해야 하는 것이었다. 이미 답을 알고 있는데 실천하지 못하고 있는 것이다. 나는 회사를 다니는 것이 주직업이기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXaaae/btsnEX1WOm0/ZOWeY5WhWRxfhVT8kXmV00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXaaae/btsnEX1WOm0/ZOWeY5WhWRxfhVT8kXmV00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXaaae/btsnEX1WOm0/ZOWeY5WhWRxfhVT8kXmV00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXaaae%2FbtsnEX1WOm0%2FZOWeY5WhWRxfhVT8kXmV00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;dosiowl contents&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;480&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림처럼 최근에 재미있는 이야기를 사람들에게 제공을 많이 하려고 노력하고 있다. 그래서 최근 내가 있었던 재미있는 사건들을 만화로 연재하고 있다. 첫 시도이기도 하고 내가 그린 거라서 재미있는지 잘 모르겠지만, 어느 정도 사람들에게 좋은 반응을 얻고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시리즈를 한 8개 정도 만들어놨는데, 한동안 콘텐츠 고민을 할 필요가 없을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마플샵&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2063&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba4sbe/btsnFdQ2BLm/TvmKaaZKIC2gthh3PlUou0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba4sbe/btsnFdQ2BLm/TvmKaaZKIC2gthh3PlUou0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba4sbe/btsnFdQ2BLm/TvmKaaZKIC2gthh3PlUou0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba4sbe%2FbtsnFdQ2BLm%2FTvmKaaZKIC2gthh3PlUou0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;774&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2063&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 아마 3,4월쯤 마플샵 진출을 하기 시작했다. 마플샵은 상품 디자인을 해주면, 그 상품을 제작, 판매, 유통을 도와주는 플랫폼이다. 상품이 팔리면 나는 디자인 값만 받고 사람들에게 상품을 팔 수 있는 것이다. 그래서 사업자 등록도 할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마플샵을 진출하면서 정말 좋았지만, 의외로 상품 디자인이 쉽지 않다는 것을 알게 되었다. 이쯤 되면 이 캐릭터 디자인이 업이 될 정도로 일이 많아지지 않나 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스타에 만화를 연재하면서 캐릭터 브랜딩도 해야 하는데, 상품을 만들기 위해 또 그림을 그리고 편집하면서 상품 디자인을 해야 할 시간도 필요하다. 그리고 생각보다 만화 그리기와 상품 디자인은 다른 분류이다. 만화 연재는 그냥 내가 들려주고 싶은 재미있는 스토리를 그리면 되지만, 상품디자인은 사람들이 살 수 있도록 사람들이 가지고 싶게 디자인해야 하기 때문에 다른 방면으로 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 어느 정도 상품을 올려놨지만, 아직 부족함이 많고 더 예쁜 디자인을 많이 만들어야 하지 않을까..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;리틀리&lt;/b&gt;&lt;/h3&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/439607021&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/SzY8b/hyTlaU6S22/yyMKyBjcWIpqkyOTzevKik/img.jpg?width=1440&amp;amp;height=1080&amp;amp;face=1183_348_1232_402,https://scrap.kakaocdn.net/dn/xEyu0/hyTk7RAGQG/oyos3kLRE1iseAyy8P77C1/img.jpg?width=1440&amp;amp;height=1080&amp;amp;face=1183_348_1232_402&quot; data-video-width=&quot;860&quot; data-video-height=&quot;645&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;645&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;'Wooncloud Blog'에서 업로드한 동영상&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/439607021?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;645&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;도시부엉 채널을 소개해주시는 리틀리 대표님&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 이야기지만, 리틀리 기업 대표님이 리틀리 사용사례를 설명하면서 도시부엉을 소개해 준 적이 있다. 잠깐 놀랐고 기분이 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리틀리에는 후원 시스템이 있어서, 최근에 리틀리를 통해 후원을 많이 받았다. 사실 대부분 지인들이나 가족들이다. 앞으로 팬이 많이 생겨서 후원을 받을 수 있는 날이 왔으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도시부엉은 앞으로&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UqPL3/btsnGZxHAMe/jYTQfJtBiPtD6HjF80lmH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UqPL3/btsnGZxHAMe/jYTQfJtBiPtD6HjF80lmH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UqPL3/btsnGZxHAMe/jYTQfJtBiPtD6HjF80lmH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUqPL3%2FbtsnGZxHAMe%2FjYTQfJtBiPtD6HjF80lmH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;kakao emoticon&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;254&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 하반기에는 다시 이모티콘 제작을 도전할 것 같다. 순수 인스타로 브랜딩 하는 것보다, 타 플랫폼으로 유입을 얻는 것이 나름 효과적일 것 같다고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 인스타에서 돈 받고 홍보하는 비중이 늘어서, 웬만하면 일반적인 유입이 잘 안 될 것으로 예상한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음으로 위에서 말한 마플샵은 상품 제작의 폭과 질이 제한적이기 때문에, 통신 판매업을 고민해보고 있다. 굿즈 판매는 스티커를 주로 판매하고 싶은데, 마플샵에서의 스티커 재질 선택은 제한적이다. 심지어 방수가 안돼서 상품으로써 가치가 많이 떨어진달까.. 그래서 여유와 기회가 된다면, 사업을 시작해보지 않을까 싶다. 하지만 아직 계획이 없다. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 회고에는 내가 주로 관심을 가지는 주제로 한번 이야기 해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 더 얘기하고 싶은 것들이 있는데, 그것은 다음 이야기로 쓰려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 이야기는 짧아질 것 같은데.. 글또, 결혼, 기타 등등 이야기 일 것 같다.&lt;/p&gt;</description>
      <category>이야기/개발일지</category>
      <category>회고</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/137</guid>
      <comments>https://wooncloud.tistory.com/137#entry137comment</comments>
      <pubDate>Sun, 16 Jul 2023 22:34:12 +0900</pubDate>
    </item>
    <item>
      <title>PostgreSQL 사용자 정의 함수 (User-Defined Function)</title>
      <link>https://wooncloud.tistory.com/136</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VgV4W/btsl1JjHYaa/slrdjrEeZFm0M4b5Xw5SB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VgV4W/btsl1JjHYaa/slrdjrEeZFm0M4b5Xw5SB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VgV4W/btsl1JjHYaa/slrdjrEeZFm0M4b5Xw5SB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVgV4W%2Fbtsl1JjHYaa%2FslrdjrEeZFm0M4b5Xw5SB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;PostgreSQL LOGO&quot; loading=&quot;lazy&quot; width=&quot;770&quot; height=&quot;353&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;881&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 PostgreSQL의 저장 프로시저(Stored Procedure)와 사용자 정의 함수(User-Defined Function)에 대한 차이점을 설명하려고 했으나, 먼저 사용자 정의 함수와 저장 프로시저에 대한 설명을 먼저 작성해야겠다고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 사용자 정의 함수에 대한 글을 작성해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사용자 정의 함수란? (User-Defined Function)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정의 함수는 일부 작업을 수행하도록 지정된 임의의 SQL 문 그룹입니다. 보통 &amp;ldquo;function&amp;rdquo;이나 &amp;ldquo;함수&amp;rdquo;라고 부르고 때로는 &amp;ldquo;UDF&amp;rdquo;라고도 합니다. 일반적으로 여러 SQL들 또는 로직을 정의해 일련의 과정으로 데이터를 처리하기 위해 생성됩니다. 함수 내에서 &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; 등의 다양한 쿼리 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL에서 사용자 정의 함수는 0개 이상의 입력 인자를 받고 결과를 반환하는 일련의 작업을 수행합니다. 보통 반환 값이 있는 계산을 처리하기 위해 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;사용자 정의 함수 사용 예&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날짜를 특정 포맷의 문자열로 변환&lt;/li&gt;
&lt;li&gt;암호화, 복호화와 같은 복잡한 계산을 통해 결과를 내는 계산&lt;/li&gt;
&lt;li&gt;어떤 문자열을 특정 형식으로 만들어 반환하거나, 어떤 문자열에서 특정 단어 및 문자열 추출&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용자 정의 함수의 장점&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캡슐화와 재사용성: 사용자 정의 함수는 자주 사용되는 SQL 및 연산들을 캡슐화하고 사용성을 높입니다. 이는 쿼리 개발의 생산성을 높이고 유지 보수의 장점이 있습니다.&lt;/li&gt;
&lt;li&gt;성능 최적화: 사용자 정의 함수는 데이터베이스 서버 내부에서 실행됩니다. 그래서 서버와 서버간의 네트워크 왕복 및 데이터 오버헤드를 줄일 수 있습니다.&lt;/li&gt;
&lt;li&gt;보안: 적절한 권한 및 권한을 정의하여 중요한 데이터 또는 작업에 대한 액세스를 제어할 수 있습니다. 사용자의 권한을 부여하거나 취소해서 인증된 사용자만 함수를 사용할 수 있도록 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이식성: 사용자 정의 함수는 다른 RDB 환경에서도 사용하기 때문에, 서비스의 로직 변경 없이 쉽게 마이그레이션 할 수 있는 것이 장점입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기본 문법&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE [OR REPLACE] FUNCTION function_name (arguments) 
RETURNS return_datatype AS $variable_name$
   DECLARE
      declaration;
      [...]
   BEGIN
      &amp;lt; function_body &amp;gt;
      [.. logic]
      RETURN { variable_name | value }
   END; 
LANGUAGE language_name;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;CREATE [OR REPLACE] FUNCTION&lt;/code&gt;: 함수를 생성합니다. &lt;code&gt;[OR REPLACE]&lt;/code&gt; 는 기존 함수를 업데이트합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;function_name&lt;/code&gt;: 함수의 이름을 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;arguments&lt;/code&gt;: 함수의 입력 인수를 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RETURNS return_datatype&lt;/code&gt;: 함수의 반환 데이터 유형을 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECLARE&lt;/code&gt;: 함수에서 사용할 변수를 선언합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BEGIN, END&lt;/code&gt;: 함수의 로직을 포함합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LANGUAGE language_name&lt;/code&gt;: 함수의 언어를 지정합니다. 아래에서 어떤 언어를 지원하고 어떻게 사용하는지 설명드리겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시는 현재 timestamp를 가져오는 함수의 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;CREATE FUNCTION getTimestamp() RETURNS timestamp AS $$
BEGIN
RETURN CURRENT_TIMESTAMP;
END; $$
LANGUAGE PLPGSQL;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cb3bLu/btsl0Tz7aqM/KrA1WxT1tSFWWujOjbtYv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cb3bLu/btsl0Tz7aqM/KrA1WxT1tSFWWujOjbtYv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cb3bLu/btsl0Tz7aqM/KrA1WxT1tSFWWujOjbtYv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcb3bLu%2Fbtsl0Tz7aqM%2FKrA1WxT1tSFWWujOjbtYv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;433&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실행&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;SELECT getTimestamp()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDhKEG/btsl0jsdISS/2B4AXzzIOk8CfgjR9iklmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDhKEG/btsl0jsdISS/2B4AXzzIOk8CfgjR9iklmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDhKEG/btsl0jsdISS/2B4AXzzIOk8CfgjR9iklmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDhKEG%2Fbtsl0jsdISS%2F2B4AXzzIOk8CfgjR9iklmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;624&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;RETURN 키워드의 &lt;b&gt;다양한&lt;span&gt; 예&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'&lt;code&gt;RETURNS&lt;/code&gt;' 키워드에서 다양한 return 타입을 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예시는 각 리턴 타입에 따라 다른 함수의 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- VARCHAR를 RETURN 타입으로 설정한 풀네임 반환 함수
CREATE FUNCTION get_full_name(first_name VARCHAR, last_name VARCHAR)
  RETURNS VARCHAR AS $$
BEGIN
  RETURN first_name || ' ' || last_name;
END;
$$ LANGUAGE plpgsql;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- DATE를 RETURN 타입으로 설정한 다음 평일을 계산하는 함수
CREATE FUNCTION get_next_weekday(start_date DATE)
  RETURNS DATE AS $$
DECLARE
  next_date DATE;
BEGIN
  next_date := start_date + INTERVAL '1 day';

  WHILE EXTRACT(ISODOW FROM next_date) IN (6, 7) LOOP
    next_date := next_date + INTERVAL '1 day';
  END LOOP;

  RETURN next_date;
END;
$$ LANGUAGE plpgsql;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- RETURN 타입을 BOOLEAN으로 설정한 짝수 여부를 반환하는 함수
CREATE FUNCTION is_even_number(number INTEGER)
  RETURNS BOOLEAN AS $$
BEGIN
  RETURN number % 2 = 0;
END;
$$ LANGUAGE plpgsql;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DECLARE 사용의 예&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 SQL은 &lt;code&gt;DECLARE&lt;/code&gt;를 사용하여 다양한 타입의 변수를 선언하는 예시입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력받은 정수 배열들의 평균을 구하는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에서는 변수로 총합, 배열의 수, 평균 값 등을 구하여 변수에 저장하는 모습을 볼 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE FUNCTION calculate_average(numbers INTEGER[])
  RETURNS NUMERIC AS $$
DECLARE
  sum NUMERIC := 0;
  count INTEGER := 0;
  average NUMERIC;
BEGIN
  FOREACH num IN ARRAY numbers LOOP
    sum := sum + num;
    count := count + 1;
  END LOOP;

  average := sum / count;
  RETURN average;
END;
$$ LANGUAGE plpgsql;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;LANGUAGE 키워드의 다양한 언어 지원&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 사용자 정의 함수에 다양한 언어를 제공하고 있습니다. 일반적으로 사용되는 언어는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;PL/pgSQL&lt;/code&gt;: &lt;code&gt;PL/pgSQL&lt;/code&gt;은 PostgreSQL에서 사용자 정의 함수를 작성하기 위해 특별히 설계되었는데, 이 언어는 SQL과 유사하고, 제어 구조, 변수 및 예외 처리가 있는 함수를 작성하는 데 적합합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SQL&lt;/code&gt;: 표준 &lt;code&gt;SQL&lt;/code&gt; 문 및 표현식으로 함수를 작성할 수 있습니다. 간단한 계산 및 데이터 조작에 사용하기 좋습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PL/Python&lt;/code&gt;: &lt;code&gt;Python&lt;/code&gt;으로 함수를 작성할 때 이 &lt;code&gt;PL/Python&lt;/code&gt; 키워드를 사용합니다. 파이썬의 기능과 파이썬 라이브러리를 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PL/v8(JavaScript)&lt;/code&gt;: &lt;code&gt;PL/v8&lt;/code&gt;을 사용하면 JavaScript를 사용하여 함수를 작성할 수 있습니다. PostgreSQL 환경 내에서 JavaScript의 라이브러리를 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;기타 언어: 그 외 PostgreSQL에서 &lt;code&gt;PL/Perl&lt;/code&gt;, &lt;code&gt;PL/Tcl&lt;/code&gt;, &lt;code&gt;PL/Java&lt;/code&gt; 등과 같은 추가 언어도 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LANGUAGE 키워드 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;PL/pgSQL&lt;/code&gt; 예시는 위에 있으므로, 다른 언어의 예시를 보여드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SQL&lt;/code&gt;, &lt;code&gt;PL/Python&lt;/code&gt;, &lt;code&gt;PL/v8(JavaScript)&lt;/code&gt;의 예시를 준비해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SQL&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;CREATE FUNCTION get_product_price(product_id INTEGER)
  RETURNS NUMERIC AS $$
SELECT price FROM products WHERE id = product_id;
$$ LANGUAGE sql;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;파이썬&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE FUNCTION calculate_factorial(n INTEGER)
  RETURNS INTEGER AS $$
DECLARE
  result INTEGER;
BEGIN
  EXECUTE 'SELECT factorial(' || n || ')'
  INTO result;

  RETURN result;
END;
$$ LANGUAGE plpythonu;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자바스크립트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 예시를 사용하려면 먼저 plv8 extension이 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;CREATE EXTENSION IF NOT EXISTS plv8;&lt;/code&gt; 를 먼저 사용하여 &lt;code&gt;plv8&lt;/code&gt;이 없으면 설치하여 자바스크립트로 사용자 정의 함수를 만들어 보도록 하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;CREATE EXTENSION IF NOT EXISTS plv8;

CREATE OR REPLACE FUNCTION calculate_total_amount(items JSONB)
  RETURNS NUMERIC AS $$
var totalAmount = 0;

for (var i = 0; i &amp;lt; items.length; i++) {
  var item = items[i];

  // Perform complex calculation based on item properties
  var quantity = item.quantity;
  var price = item.price;
  var discount = item.discount || 0;

  var itemTotal = quantity * price * (1 - discount);
  totalAmount += itemTotal;
}

return totalAmount;
$$ LANGUAGE plv8;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;SELECT calculate_total_amount('[{&quot;quantity&quot;: 2, &quot;price&quot;: 10, &quot;discount&quot;: 0.1}, {&quot;quantity&quot;: 3, &quot;price&quot;: 15}]');
-- Returns 43.5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하자면 PostgreSQL에서 사용자 정의 함수는 복잡한 로직을 캡슐화 하고 재사용성을 높이는데 좋은 장점이 있습니다. 그리고 함수 접근 권한을 사용자마다 다르게 줄 수 있는 장점도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 사용하는 쿼리에 변수 선언과 여러 SQL문을 일괄 처리하는데도 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 다양한 언어를 지원하여 익숙한 언어를 사용할 수 있다는 장점도 가지고 있습니다.&lt;/p&gt;</description>
      <category>개발 아카이브/DATABASE</category>
      <category>Database</category>
      <category>PostgreSQL</category>
      <category>User-Defined Function</category>
      <category>사용자 정의 함수</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/136</guid>
      <comments>https://wooncloud.tistory.com/136#entry136comment</comments>
      <pubDate>Sun, 2 Jul 2023 18:18:34 +0900</pubDate>
    </item>
    <item>
      <title>Sveltekit + Vercel + Supabase 삼위일체 사용 후기</title>
      <link>https://wooncloud.tistory.com/135</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUKn8p/btsitSYUjJ9/hwVPINetxj9NNMq09pzLIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUKn8p/btsitSYUjJ9/hwVPINetxj9NNMq09pzLIK/img.png&quot; data-alt=&quot;https://supabase.com/blog/supabase-beta-june-2021&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUKn8p/btsitSYUjJ9/hwVPINetxj9NNMq09pzLIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUKn8p%2FbtsitSYUjJ9%2FhwVPINetxj9NNMq09pzLIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;supabase x vercel&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;600&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://supabase.com/blog/supabase-beta-june-2021&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;sveltekit + vercel + supabase 사용 후기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소에 토이 프로젝트를 만드는 것을 좋아하기 때문에, 가볍고 쉽게 배포할 수 있는 방법 없을까 하며 고민을 많이 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 이번에 sveltekit, vercel, supabase라는 것을 알게 되고 간단한 토이 프로젝트를 이 서비스에 적용해 보았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;삼대장을 만나게 된 계기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;삼대장.png&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcBVYL/btsiFP62fyv/nkztBgRydtqSK5gkqWyIU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcBVYL/btsiFP62fyv/nkztBgRydtqSK5gkqWyIU1/img.png&quot; data-alt=&quot;삼대장! Sveltekit + Vercel + Supabase&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcBVYL/btsiFP62fyv/nkztBgRydtqSK5gkqWyIU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcBVYL%2FbtsiFP62fyv%2FnkztBgRydtqSK5gkqWyIU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;three captains&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;326&quot; data-filename=&quot;삼대장.png&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;삼대장! Sveltekit + Vercel + Supabase&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 만들어본 프로젝트는 예전에 만들었던 회사 근처 식당 추천기이다. 그냥 간단히 회사 근처의 식당 중 어떤 것을 먹을까 고민하다, 대신 골라주는 &amp;ldquo;메뉴 추천기&amp;rdquo;가 있으면 좋겠다는 생각을 하면서 만들게 되었었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24년 03월 21일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 메뉴추천기 사이트는 현재 작동하지 않습니다. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 그런게 있었다. 라고 생각해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://flow-merecom.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://flow-merecom.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685883345921&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;flow 메뉴 추천기&quot; data-og-description=&quot;&quot; data-og-host=&quot;flow-merecom.vercel.app&quot; data-og-source-url=&quot;https://flow-merecom.vercel.app/&quot; data-og-url=&quot;https://flow-merecom.vercel.app/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://flow-merecom.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flow-merecom.vercel.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;flow 메뉴 추천기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flow-merecom.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rRSJ5/btsiFBVhA5l/AuP00nNIptVP0quC89SpO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rRSJ5/btsiFBVhA5l/AuP00nNIptVP0quC89SpO0/img.png&quot; data-alt=&quot;처음 메뉴추천기에 사용했던 희한한 조합&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rRSJ5/btsiFBVhA5l/AuP00nNIptVP0quC89SpO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrRSJ5%2FbtsiFBVhA5l%2FAuP00nNIptVP0quC89SpO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;strange combination&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;207&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;처음 메뉴추천기에 사용했던 희한한 조합&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 메뉴 추천기는 vanilla js, firebase hosting, notion db를 이용하여 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chatGPT를 이용해 간단히 vanilla js로 만들었고, 당시 사용량이 적으면 무료로 호스팅 해주는 서비스인 firebase를 사용하여 배포했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dbmia.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxAun/btsiuLyacqf/HSq7ykaWlM8igjoMB67qf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxAun/btsiuLyacqf/HSq7ykaWlM8igjoMB67qf1/img.png&quot; data-alt=&quot;디비미아&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxAun/btsiuLyacqf/HSq7ykaWlM8igjoMB67qf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxAun%2FbtsiuLyacqf%2FHSq7ykaWlM8igjoMB67qf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;db mia&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;400&quot; data-filename=&quot;dbmia.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디비미아&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문제는 db였다. 메뉴 추천기에 메뉴 데이터는 필수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메뉴 데이터를 json으로 하드코딩하거나 json 파일로 저장해서 배포해도 되지만, 중간에 메뉴가 수정되면 다시 배포해야 하는 불편함이 이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 notion api를 이용하여 notion db를 POST로 호출해 데이터를 받아올 수 있는 법을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법을 쓰면 토이 프로젝트는 notion db로 쓰면 좋겠다는 생각을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실제로 크롬 익스텐션 토이 프로젝트는 notion db를 사용해서 데이터를 관리하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 서버에 배포한 토이 프로젝트인 경우, CORS 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 익스텐션은 CORS를 해결해 주는 config가 존재하기 때문에 문제가 없었던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 메뉴 추천기는 이 문제를 해결하기 위해 heroku를 이용하여 프록시서버를 개설했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도는 느리지만 프록시 서버를 이용하면 cors 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bktnqf/btsitGKZSg3/P8wzpMC0o84QzmonHfVUiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bktnqf/btsitGKZSg3/P8wzpMC0o84QzmonHfVUiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bktnqf/btsitGKZSg3/P8wzpMC0o84QzmonHfVUiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbktnqf%2FbtsitGKZSg3%2FP8wzpMC0o84QzmonHfVUiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;heroku&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;200&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 프록시 서버를 사용하면 2개의 단점이 생긴다. 첫 번째는 과금이 생긴다. 실제로 한 15000원 정도 뜯겼고, 헤로쿠가 무료인 줄 알고 있었는데 백엔드 서버를 돌리면 과금이 발생할 수 있다. 두 번째는 서버 region이 유럽과 미국밖에 없기 때문에 엄청나게 느리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 이유로 결국 notion db를 사용하는 방법을 과감히 포기했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 위의 서비스들을 대체할 수 있는 것이 없을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방랑하며 찾아다니던 도중, 차츰 vercel, supabase, sveltekit에 대해 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Sveltekit&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eH3Nf4/btsitHwf0KC/IDXnPLHUhMqj95PgpRt36K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eH3Nf4/btsitHwf0KC/IDXnPLHUhMqj95PgpRt36K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eH3Nf4/btsitHwf0KC/IDXnPLHUhMqj95PgpRt36K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeH3Nf4%2FbtsitHwf0KC%2FIDXnPLHUhMqj95PgpRt36K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;sveltekit&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;168&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kit.svelte.dev/&quot;&gt;https://kit.svelte.dev/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685884558344&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SvelteKit &amp;bull; Web development, streamlined&quot; data-og-description=&quot;SvelteKit is the official Svelte application framework&quot; data-og-host=&quot;kit.svelte.dev&quot; data-og-source-url=&quot;https://kit.svelte.dev/&quot; data-og-url=&quot;https://kit.svelte.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Bel4J/hySR5f9JS1/Dj48p3r1Kqd6daccZ63Hm0/img.png?width=1440&amp;amp;height=1440&amp;amp;face=0_0_1440_1440&quot;&gt;&lt;a href=&quot;https://kit.svelte.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kit.svelte.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Bel4J/hySR5f9JS1/Dj48p3r1Kqd6daccZ63Hm0/img.png?width=1440&amp;amp;height=1440&amp;amp;face=0_0_1440_1440');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SvelteKit &amp;bull; Web development, streamlined&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SvelteKit is the official Svelte application framework&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kit.svelte.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;svelte에서 만든 풀스택 프레임워크이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 firebase 호스팅 서비스에 배포를 해봤는데, 파이어베이스에서 백엔드 로직을 넣는 즉시 과금이 발생했다. 그래서 그동안 vanilla js로만 이용해서 api 호출 정도만 사용하여 토이 프로젝트를 개발했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 vanilla js만 사용하여 서버리스 웹 앱을 개발하는 것보다 sveltekit을 이용해 웹 앱을 만들면 확실한 장점이 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서버개발이 가능했다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;js뿐만 아니라 react, vue를 사용하면 클라이언트 서버만 개발이 가능했고, 서버 개발을 위해 nodejs express를 사용하는 방식으로 다른 api 서버를 만들어줘야 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한때 예전에 만들었던 &amp;ldquo;운쿠&amp;rdquo; 프로젝트가 그런 식으로 만들어졌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 sveltekit을 사용하면서 서버 개발이 가능하고, 클라이언트 서버와 api 서버를 다 같이 만들 수 있다. 규모가 커지면 나누는 게 용이할 수 있다고 생각하지만 프로토타입이나 토이 프로젝트 개발에 정말 최적이라고 생각 들었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;라우팅이 용이하다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vanilla js만 사용하면 라우팅에 한정적이다. 그래서 그동안 SPA로만 개발했어야 했는데, 이번에 sveltekit을 사용하면서 다양한 페이지를 만들고 쉽고 자유로운 라우팅이 좋았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SSR을 할 수 있으면서, svelte를 사용한 클라이언트 사이드 랜더링(CSR)에도 강하다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서 설명하겠지만, supabase를 이용해 db에 있는 데이터를 가지고 오기 위해서 클라이언트에서 보이면 안 될 데이터 호출, 가공에 대한 로직을 숨겨야 한다. 그러나 vanilla js만 사용하면 이러한 로직을 숨길 수 없기 때문에 보안의 위험이 있을 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;vercel&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1003&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4E79J/btsiurzVRIt/9AKNS0koKM3Byo9wfiBsL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4E79J/btsiurzVRIt/9AKNS0koKM3Byo9wfiBsL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4E79J/btsiurzVRIt/9AKNS0koKM3Byo9wfiBsL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4E79J%2FbtsiurzVRIt%2F9AKNS0koKM3Byo9wfiBsL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;vercel&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;209&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1003&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vercel.com/&quot;&gt;https://vercel.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685884556357&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Vercel: Develop. Preview. Ship. For the best frontend teams&quot; data-og-description=&quot;Vercel is the platform for frontend developers, providing the speed and reliability innovators need to create at the moment of inspiration.&quot; data-og-host=&quot;vercel.com&quot; data-og-source-url=&quot;https://vercel.com/&quot; data-og-url=&quot;https://vercel.com/index&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dvPnZn/hySR6e5k8i/LQ3CD7cHFRGerNEzDhTMJ0/img.png?width=1686&amp;amp;height=882&amp;amp;face=0_0_1686_882,https://scrap.kakaocdn.net/dn/jrCwn/hySR0snZPj/gUqPlSJaIIrNoDR745ftD1/img.png?width=1686&amp;amp;height=882&amp;amp;face=0_0_1686_882&quot;&gt;&lt;a href=&quot;https://vercel.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://vercel.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dvPnZn/hySR6e5k8i/LQ3CD7cHFRGerNEzDhTMJ0/img.png?width=1686&amp;amp;height=882&amp;amp;face=0_0_1686_882,https://scrap.kakaocdn.net/dn/jrCwn/hySR0snZPj/gUqPlSJaIIrNoDR745ftD1/img.png?width=1686&amp;amp;height=882&amp;amp;face=0_0_1686_882');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Vercel: Develop. Preview. Ship. For the best frontend teams&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Vercel is the platform for frontend developers, providing the speed and reliability innovators need to create at the moment of inspiration.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;vercel.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vercel은 내가 만든 웹 애플리케이션을 쉽게 배포할 수 있도록 서비스되고 있는 PaaS(Platform as a Service)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vercel을 만나기전에 firebase와 heroku를 만났는데, 둘 다 아쉬움이 많았다. firebase는 일단 웹 호스팅만 무료이고, 서버 호스팅은 무료로 들어가며 AWS lamda와 비슷한 functions로만 백엔드 지원이 가능했기 때문에 아쉬웠다. heroku는 나름 가격도 싸고 무료로도 어느 정도 사용할 수 있지만, 서버 region이 미국과 유럽밖에 없기 때문에 속도가 너무 느렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 vercel을 만나면서 앞서 말한 두 서비스보다 빠르고, 배포도 쉽고, 가격도 저렴했기 때문에 vercel의 매력에 빠지면서 애용하게 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;무료!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요했다. 물론 firebase도 무료지만, 서버를 배포하게되면 과금을 하게 된다. 그리고 기존에 notion db로부터 데이터를 가져오기 위해 헤로쿠로 프록시 서버를 만들었었다. 그래서 이 점심 추천기를 쓰기 위해 헤로쿠 프록시 서버도 배포했는데, 이 것도 마찬가지로 서버 배포가 이루어져 과금이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 vercel은 대부분 무료이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CCeEe/btsiJzCSXYW/XjlKfr6vw69Tp20NuQLMT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CCeEe/btsiJzCSXYW/XjlKfr6vw69Tp20NuQLMT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CCeEe/btsiJzCSXYW/XjlKfr6vw69Tp20NuQLMT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCCeEe%2FbtsiJzCSXYW%2FXjlKfr6vw69Tp20NuQLMT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;vercel payment&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;384&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 디테일한 사용량에 따라 유무료가 아니라 그냥 무료이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;다양한 프레임워크를 지원한다.&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1751&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPxxDf/btsiuMDPFU7/owzKEc5SMTn3IM9EnoBK5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPxxDf/btsiuMDPFU7/owzKEc5SMTn3IM9EnoBK5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPxxDf/btsiuMDPFU7/owzKEc5SMTn3IM9EnoBK5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPxxDf%2FbtsiuMDPFU7%2FowzKEc5SMTn3IM9EnoBK5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;vercel supported framework&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;365&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1751&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 js 프레임워크에 최적화 되어 있고, 이 프레임워크에 대한 템플릿과 개발 및 배포하는 방법에 대한 문서들이 아주 잘 작성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 최근 프론트 SSR 프레임워크로 Next.js가 인기가 많은데, Next.js는 Vercel에서 만들었기 때문에, 그만큼 잘 지원해주고 그래서 요즘 vercel의 인기가 엄청 많은 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문서가 잘 만들어져 있다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vercel.com/docs&quot;&gt;https://vercel.com/docs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685884554264&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Vercel Documentation&quot; data-og-description=&quot;Vercel's frontend cloud gives developers frameworks, workflows, and infrastructure to build a faster, more personalized web&quot; data-og-host=&quot;vercel.com&quot; data-og-source-url=&quot;https://vercel.com/docs&quot; data-og-url=&quot;https://vercel.com/docs&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jw5qZ/hySRXvD2zV/nxhW7iVNhZKUSKBEzZwVe0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://vercel.com/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://vercel.com/docs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jw5qZ/hySRXvD2zV/nxhW7iVNhZKUSKBEzZwVe0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Vercel Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Vercel's frontend cloud gives developers frameworks, workflows, and infrastructure to build a faster, more personalized web&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;vercel.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 vercel의 대부분 기능을 사용해본것은 아니지만, 각각의 프레임워크와 인프라, 보안, API, 모니터링, 스토리지, 워크플로우 등 문서가 준비되어 있다. 확인해보고 필요한 기능을 사용할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;supabase&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;supabase-logo-wordmark--light.svg&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk2NYu/btsiu1U3DyE/fHd04vPG59vtXW2m4Ga3Z1/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk2NYu/btsiu1U3DyE/fHd04vPG59vtXW2m4Ga3Z1/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk2NYu/btsiu1U3DyE/fHd04vPG59vtXW2m4Ga3Z1/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk2NYu%2Fbtsiu1U3DyE%2FfHd04vPG59vtXW2m4Ga3Z1%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;supabase&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;78&quot; data-filename=&quot;supabase-logo-wordmark--light.svg&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://supabase.com/&quot;&gt;https://supabase.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685884550720&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;The Open Source Firebase Alternative | Supabase&quot; data-og-description=&quot;The Open Source Alternative to Firebase.&quot; data-og-host=&quot;supabase.com&quot; data-og-source-url=&quot;https://supabase.com/&quot; data-og-url=&quot;https://supabase.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/If2Qt/hySRYBjQov/9IP8okvgsRYZCTSkKBxQzk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://supabase.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://supabase.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/If2Qt/hySRYBjQov/9IP8okvgsRYZCTSkKBxQzk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The Open Source Firebase Alternative | Supabase&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Open Source Alternative to Firebase.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;supabase.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료로 사용할 수 있는 DB 서비스를 찾으러 다니다가 처음에 firebase를 알게되고, 그 후 mongoDB를 알게 되면서 noSQL을 체험하다 supabase를 만났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 firebase나 mongoDB처럼 noSQL방식이 아닌 RDBMS를 찾던 중 supabase를 알게 되어 너무 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷에서는 supabase를 firebase의 오픈소스버전이라고 이야기하지만, 직접 겪은 이미지는 조금 다른 느낌이었다. firebase보다 사용하기 쉽고 RDBMS를 사용하며, 문서도 아주 훌륭했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 supabase를 경험하면서 좋았던 기억을 나열해보고자 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;postgreSQL을 사용한다.&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Lr8t/btsiusMp5sv/BZYNtQ5P0ULMWmMLow1U0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Lr8t/btsiusMp5sv/BZYNtQ5P0ULMWmMLow1U0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Lr8t/btsiusMp5sv/BZYNtQ5P0ULMWmMLow1U0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Lr8t%2FbtsiusMp5sv%2FBZYNtQ5P0ULMWmMLow1U0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;postgreSQL&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;225&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase는 postgreSQL을 사용합니다. 이미 회사에서 postgreSQL을 사용하기 때문에, 아주 친숙한 환경이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소에 사용하는 DB툴과 연결할 수 있도록 환경도 마련해두었고, 웹에서 스키마나 쿼리를 입력하고 DB 업무를 할 수 있도록 UI/UX도 친숙했다. 그래서 supabase로 만든 프로젝트의 DB를 언제 어디서나 사용할 수 있다는 게 큰 매력으로 다가왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase DB를 접속하기 위해 &lt;a href=&quot;https://github.com/supabase/supabase-js&quot;&gt;@supabase/supabase-js&lt;/a&gt;를 사용했는데, 이 라이브러리도 사용하기 쉽고 DX가 좋았다. 차후 이 라이브러리 소개나 사용하는 방법에 대해 포스팅하면 좋을 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프리티어에서 왠만한 기능을 사용할 수 있다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase의 프리티어는 아래만큼의 리소스를 사용할 수 있습니다. 토이프로젝트로 사용해 보기 딱 좋은 양의 리소스이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소셜 OAuth 공급자&lt;/li&gt;
&lt;li&gt;최대 500MB 데이터베이스 및 1GB 파일 저장&lt;/li&gt;
&lt;li&gt;최대 2GB 대역폭&lt;/li&gt;
&lt;li&gt;최대 50MB 파일 업로드&lt;/li&gt;
&lt;li&gt;월 활성 사용자 50,000명&lt;/li&gt;
&lt;li&gt;최대 500K Edge functions 호출&lt;/li&gt;
&lt;li&gt;200개의 동시 실시간 연결&lt;/li&gt;
&lt;li&gt;200만 실시간 메시지&lt;/li&gt;
&lt;li&gt;1일 로그 보존&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2p3v4/btsiJ8rH2sT/Go2rkN2UVjRrHQjTjIrOl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2p3v4/btsiJ8rH2sT/Go2rkN2UVjRrHQjTjIrOl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2p3v4/btsiJ8rH2sT/Go2rkN2UVjRrHQjTjIrOl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2p3v4%2FbtsiJ8rH2sT%2FGo2rkN2UVjRrHQjTjIrOl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;supabase payment&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1650&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문서가 잘 만들어져 있다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://supabase.com/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://supabase.com/docs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685884644292&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Supabase Docs&quot; data-og-description=&quot;Supabase Documentation Learn how to get up and running with Supabase through tutorials, APIs and platform resources.&quot; data-og-host=&quot;supabase.com&quot; data-og-source-url=&quot;https://supabase.com/docs&quot; data-og-url=&quot;https://supabase.com/docs/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/m2pCl/hySR3ij31j/1axu7C78E50rYfEwkXVcsK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/WslnN/hySRXJbYLM/oRAeh2wtF05XJaA824T7EK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://supabase.com/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://supabase.com/docs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/m2pCl/hySR3ij31j/1axu7C78E50rYfEwkXVcsK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/WslnN/hySRXJbYLM/oRAeh2wtF05XJaA824T7EK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Supabase Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Supabase Documentation Learn how to get up and running with Supabase through tutorials, APIs and platform resources.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;supabase.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase가 정말 좋았던 이유는, 단순하고 다양한 템플릿을 제공하는 것이었다. 많은 프레임워크에서 사용하는 방법에 대한 예시가 거의 다 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스뿐만 아니라, Auth, Edge Function, Realtime, Storage, AI 등등 다양한 서비스를 제공하고 이에 맞는 문서가 아주 잘 되어 있어서 나중에 한 번씩 사용해 보면 좋겠다고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 친절한 문서가 나름 사용자를 모아주는 큰 역할을 한다는걸 새삼 느낄 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;827&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/urRXz/btsitS5FUVP/ZqX2oDV74ovah59yDub9wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/urRXz/btsitS5FUVP/ZqX2oDV74ovah59yDub9wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/urRXz/btsitS5FUVP/ZqX2oDV74ovah59yDub9wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FurRXz%2FbtsitS5FUVP%2FZqX2oDV74ovah59yDub9wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;supabase document&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;827&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;827&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3가지의 기술 스택은 서로 스벨트를 사용하기 아주 좋은 환경을 구성하고 있다. vercel과 supabase 둘 다 sveltekit을 사용하기 쉽도록 프레임워크 맞춤형 문서가 아주 잘 작성되어 있다. 그래서 블로그 글이나 강의를 보지 않고도 문서를 통해 대부분 프로젝트를 구현할 수 있을 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 이 조합을 사용하여 사이드 프로젝트를 간간히 해볼 생각이다.&lt;/p&gt;</description>
      <category>이야기/개발일지</category>
      <category>supabase</category>
      <category>sveltekit</category>
      <category>vercel</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/135</guid>
      <comments>https://wooncloud.tistory.com/135#entry135comment</comments>
      <pubDate>Sun, 4 Jun 2023 22:20:10 +0900</pubDate>
    </item>
    <item>
      <title>rel 속성 - noopener, noreferrer, nofollow</title>
      <link>https://wooncloud.tistory.com/134</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rel 속성-001.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwdXL/btsgGkoaFJb/3gdJouKnOi0IeozSndKx5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwdXL/btsgGkoaFJb/3gdJouKnOi0IeozSndKx5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwdXL/btsgGkoaFJb/3gdJouKnOi0IeozSndKx5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmwdXL%2FbtsgGkoaFJb%2F3gdJouKnOi0IeozSndKx5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;rel 속성&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;rel 속성-001.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML의 a tag에 &lt;code&gt;rel&lt;/code&gt;이라는 속성을 보신적이 있으실 겁니다. a tag는 보통 &lt;code&gt;href&lt;/code&gt;에 &lt;code&gt;target=&amp;rdquo;_blank&amp;rdquo;&lt;/code&gt; 같은 속성만 사용해보고 &lt;code&gt;rel&lt;/code&gt; 속성에 대해 잘 모르는 경우가 대부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 이번에 HTML을 작성하면서 &amp;ldquo;이 &lt;code&gt;rel&lt;/code&gt; 속성의 역할은 뭘까?&amp;rdquo; 라는 생각이 들어 찾아봤습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;rel 속성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;rel&lt;/code&gt; 속성은 현재 HTML 문서와 링크된 두 문서 사이의 관계를 명시하는 데 사용됩니다. 이 속성은 &lt;code&gt;href&lt;/code&gt; 속성과 함께 사용되며, 검색 엔진은 이를 통해 링크에 대한 추가 정보를 파악할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rel 속성은 주로 &lt;b&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;&lt;/b&gt;(하이퍼링크), &lt;b&gt;&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;&lt;/b&gt;(외부 스타일시트 연결), &lt;b&gt;&lt;code&gt;&amp;lt;area&amp;gt;&lt;/code&gt;&lt;/b&gt;(이미지 맵 영역), &lt;b&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;&lt;/b&gt;(폼 제출) 태그와 함께 사용됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;&lt;/b&gt; : 링크를 표현하는 태그입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;a href=&quot;&amp;lt;https://www.example.com&amp;gt;&quot; rel=&quot;nofollow&quot;&amp;gt;예시 링크&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;&lt;/b&gt; : 외부 스타일시트를 연결하는 태그입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;&amp;lt;area&amp;gt;&lt;/code&gt;&lt;/b&gt; : 이미지 맵 영역을 정의하는 태그입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;area href=&quot;&amp;lt;https://www.example.com&amp;gt;&quot; rel=&quot;nofollow&quot; shape=&quot;rect&quot; coords=&quot;0,0,100,100&quot; alt=&quot;예시 링크 영역&quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;&lt;/b&gt; : 폼 제출에 대한 관련 정보를 명시하는 태그입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;form action=&quot;./submit&quot; method=&quot;post&quot; rel=&quot;nofollow&quot;&amp;gt;...&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이 rel 속성에 들어가는 값인 &lt;code&gt;noopener&lt;/code&gt;, &lt;code&gt;noreferrer&lt;/code&gt;, &lt;code&gt;nofollow&lt;/code&gt;은 각자 무슨 역할을 할까요?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. noopener&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;noopener&lt;/code&gt; 속성은 하이퍼링크(a 태그)를 통해 새 창이나 탭을 열 때 사용됩니다. 이 속성을 사용하면 &lt;u&gt;&lt;b&gt;새 창이나 탭에서 열린 문서가 원래 페이지에 대한 참조를 갖지 않도록 방지&lt;/b&gt;&lt;/u&gt;합니다. 이는 보안상의 이유로 매우 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 다음과 같은 HTML 코드를 살펴보겠습니다:&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;&amp;lt;https://www.example.com&amp;gt;&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&amp;gt;예시 링크&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 &quot;target&quot; 속성은 링크가 새 창이나 탭에서 열리도록 지정합니다. 그리고 &quot;rel&quot; 속성에 &lt;code&gt;noopener&lt;/code&gt; 값을 설정하여 새로 열린 창에서 원래 페이지에 대한 참조를 차단합니다. 이를 통해 악의적인 페이지에서 원래 페이지에 접근하는 것을 방지할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. noreferrer&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;noreferrer&lt;/code&gt; 속성은 &lt;code&gt;noopener&lt;/code&gt;과 유사한 목적으로 사용됩니다. 이 속성은 링크를 통해 다른 페이지로 이동할 때, HTTP Referer Header를 전송하지 않도록 설정합니다. Referer Header는 웹 페이지에서 링크를 클릭할 때, 원래 페이지의 URL이 전송되는 정보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 예시 코드를 통해 &lt;code&gt;noreferrer&lt;/code&gt; 속성을 확인해보겠습니다:&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;&amp;lt;https://www.example.com&amp;gt;&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&amp;gt;예시 링크&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 &lt;code&gt;rel&lt;/code&gt; 속성에 &lt;code&gt;noreferrer&lt;/code&gt; 값을 설정함으로써, 웹 브라우저가 링크를 클릭할 때 HTTP Referer Header를 전송하지 않습니다. &lt;u&gt;&lt;b&gt;이를 통해 사용자의 개인 정보를 보호하고, 웹사이트 간의 데이터 유출을 방지할 수 있습니다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. nofollow&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;nofollow&lt;/code&gt; 속성은 &lt;u&gt;&lt;b&gt;검색 엔진이 해당 링크를 따라가지 않도록 지시하는 역할&lt;/b&gt;&lt;/u&gt;을 합니다. 이 속성은 링크의 신뢰도와 검색 엔진 최적화(SEO 최적화)에 영향을 미칩니다. 일반적으로 웹 페이지의 링크는 검색 엔진 크롤러에 의해 따라가며, 해당 페이지의 랭킹 및 신뢰도에 영향을 줍니다. 그러나 &lt;code&gt;nofollow&lt;/code&gt; 속성이 설정된 링크는 크롤러가 따라가지 않으므로 랭킹에 영향을 미치지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;code&gt;nofollow&lt;/code&gt; 속성을 사용한 예시 코드입니다:&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;&amp;lt;https://www.example.com&amp;gt;&quot; rel=&quot;nofollow&quot;&amp;gt;예시 링크&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 &lt;code&gt;rel&lt;/code&gt; 속성에 &lt;code&gt;nofollow&lt;/code&gt; 값을 설정함으로써, 검색 엔진이 해당 링크를 따라가지 않도록 지시합니다. 이를 통해 웹 페이지의 랭킹을 조절하고, 검색 엔진 크롤러가 신뢰할 수 없는 링크로 인해 부정적인 영향을 받는 것을 방지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;code&gt;noopener&lt;/code&gt;, &lt;code&gt;noreferrer&lt;/code&gt;, 그리고 &lt;code&gt;nofollow&lt;/code&gt; 속성은 HTML 태그에서 링크와 관련된 보안과 SEO(검색 엔진 최적화) 측면에서 중요한 역할을 합니다. 이러한 속성을 적절히 활용하여 웹 페이지의 보안을 강화하고, 검색 엔진에 대한 컨트롤을 유지할 수 있습니다.&lt;/p&gt;</description>
      <category>개발 아카이브/HTML, CSS</category>
      <category>a tag</category>
      <category>Area</category>
      <category>form</category>
      <category>html</category>
      <category>Link</category>
      <category>nofollow</category>
      <category>noopener</category>
      <category>noreferrer</category>
      <category>REL</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/134</guid>
      <comments>https://wooncloud.tistory.com/134#entry134comment</comments>
      <pubDate>Sun, 21 May 2023 19:50:43 +0900</pubDate>
    </item>
    <item>
      <title>Shadow DOM - DOM을 캡슐화 하자!</title>
      <link>https://wooncloud.tistory.com/133</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Shadow DOM이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지의 DOM 트리 안에 또 다른 독립되는 DOM 트리를 생성하는 방식입니다. Shadow DOM을 이용하면 웹 페이지 내의 요소들을 잘 캡슐화하여 외부에서의 접근 제한하고, 컴포넌트 간의 의존성을 관리할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/99Raf/btsfXeVOG3k/KCLNVzixjpxOWstjcgh99k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/99Raf/btsfXeVOG3k/KCLNVzixjpxOWstjcgh99k/img.png&quot; data-alt=&quot;&amp;amp;lt;input&amp;amp;gt; 의 date와 range 타입&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/99Raf/btsfXeVOG3k/KCLNVzixjpxOWstjcgh99k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F99Raf%2FbtsfXeVOG3k%2FKCLNVzixjpxOWstjcgh99k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;&amp;amp;lt;input&amp;amp;gt; 의 date와 range 타입&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;40&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&amp;lt;input&amp;gt; 의 date와 range 타입&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 많이 사용하는 input, button, video.. 등등 html 요소들이 shadow DOM으로 만들어진 요소들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 요소는 이미 기능, 동작, 모양, 스타일 등등 이미 만들어져 있는 템플릿이라고 볼 수 있습니다. 많은 div, span 등 그리고 내부 스크립트와 스타일 등을 결합하여 캡슐화하면 이렇게 shadow DOM을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본으로 제공하는 이런 html 요소 뿐만 아니라, 우리도 shadow DOM을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 shadow DOM을 만드는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Shadow DOM을 만드는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개발자 도구에서 shadow DOM을 까보기&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnzJzy/btsfeAGUCgB/v64Ag89W2npk2kufkEX8kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnzJzy/btsfeAGUCgB/v64Ag89W2npk2kufkEX8kk/img.png&quot; data-alt=&quot;개발자 도구 열기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnzJzy/btsfeAGUCgB/v64Ag89W2npk2kufkEX8kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnzJzy%2FbtsfeAGUCgB%2Fv64Ag89W2npk2kufkEX8kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;개발자 도구 열기&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1271&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1271&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발자 도구 열기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 그림과 같이 F12을 눌러서 개발자 도구를 열면 오른쪽 상단에 Preferences 아이콘이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZuBtg/btsftJCFx68/MKzkfbxEBl0jPyD1zaEbsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZuBtg/btsftJCFx68/MKzkfbxEBl0jPyD1zaEbsk/img.png&quot; data-alt=&quot;Shadow DOM 보기 활성화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZuBtg/btsftJCFx68/MKzkfbxEBl0jPyD1zaEbsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZuBtg%2FbtsftJCFx68%2FMKzkfbxEBl0jPyD1zaEbsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Shadow DOM 보기 활성화&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1451&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1451&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Shadow DOM 보기 활성화&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 아이콘을 누르고 중간지점 Elements 설정의 첫 번째 &amp;ldquo;Show user agent shadow DOM&amp;rdquo;라는 체크박스를 활성화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bommnk/btsfpe32vhk/6B54r1KdkLdobHAlNfQP8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bommnk/btsfpe32vhk/6B54r1KdkLdobHAlNfQP8k/img.png&quot; data-alt=&quot;Shadow DOM이 보이는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bommnk/btsfpe32vhk/6B54r1KdkLdobHAlNfQP8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbommnk%2Fbtsfpe32vhk%2F6B54r1KdkLdobHAlNfQP8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Shadow DOM이 보이는 모습&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;2852&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Shadow DOM이 보이는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그럼 위의 그림과 같이 요소 내부 Shadow DOM을 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Shadow DOM 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shadow DOM을 만들기 전에 html에 다음과 같은 요소가 있다고 가정합니다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;.shadow-dom-element&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 요소 선택: Shadow DOM을 생성할 요소를 선택합니다. 저는 &amp;ldquo;.shadow-dom-element&amp;rdquo; 라는 클래스를 포함시킨 div를 선택하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1684072709529&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const shadowDomElement = document.querySelector(&quot;.shadow-dom-element&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. shadowRoot 생성: 선택한 요소의 shadowRoot 프로퍼티를 사용하여 shadowRoot 객체를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1684072743320&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const shadowRoot = shadowDomElement.attachShadow({mode: 'open'});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 내부 HTML 삽입: shadowRoot 객체의 innerHTML 프로퍼티를 사용하여 Shadow DOM 내부에 HTML을 삽입합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1684072763422&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;shadowRoot.innerHTML = `
  &amp;lt;style&amp;gt;
    /* Shadow DOM에 적용할 스타일 작성 */
  &amp;lt;/style&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;!-- Shadow DOM에 포함될 HTML 작성 --&amp;gt;
  &amp;lt;/div&amp;gt;
`;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;심화 - shadow DOM 생성 함수&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function createShadowDOM(selector, html) {
  const element = document.querySelector(selector);
  const shadowRoot = element.attachShadow({mode: 'open'});
  shadowRoot.innerHTML = html;
}

createShadowDOM('div', `
  &amp;lt;style&amp;gt;
		div {
		  background-color: #f5f5f5;
		  padding: 10px;
		  border-radius: 5px;
		}
		p {
		  font-size: 16px;
		  color: #333;
		}
	&amp;lt;/style&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;Shadow DOM을 만드는 방법을 알아보자!&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
`);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;slot 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shadow DOM을 사용하면 HTML 요소의 스타일과 동작을 캡슐화하고 격리된 스타일을 적용할 수 있습니다. 이를 통해 모듈화된 컴포넌트를 만들고 재사용할 수 있습니다. 이때, &lt;b&gt;slot&lt;/b&gt; 요소를 사용하면 Shadow DOM 안에서 외부에서 전달한 콘텐츠를 삽입할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;slot 요소&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;slot&lt;/b&gt; 요소는 Shadow DOM 안에서 외부에서 전달한 콘텐츠를 삽입하는 데 사용됩니다. &lt;b&gt;slot&lt;/b&gt; 요소는 Shadow DOM에서 &lt;b&gt;slot&lt;/b&gt; 요소를 포함하는 요소에 대응됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;b&gt;slot&lt;/b&gt; 요소를 사용하여 Shadow DOM 안에 외부에서 전달한 콘텐츠를 삽입하는 예제입니다.&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;template id=&quot;my-component&quot;&amp;gt;
  &amp;lt;style&amp;gt;
    .container {
      border: 1px solid black;
      padding: 10px;
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;my-component&amp;gt;
  &amp;lt;p&amp;gt;Hello World!&amp;lt;/p&amp;gt;
&amp;lt;/my-component&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서는 **my-component**라는 사용자 정의 요소를 정의하고, &lt;b&gt;slot&lt;/b&gt; 요소를 사용하여 외부에서 전달한 콘텐츠를 삽입합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;slot 이름 붙이기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;slot&lt;/b&gt; 요소에는 이름을 지정할 수 있습니다. 이를 통해 여러 개의 &lt;b&gt;slot&lt;/b&gt; 요소를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;template id=&quot;my-component&quot;&amp;gt;
  &amp;lt;style&amp;gt;
    .container {
      border: 1px solid black;
      padding: 10px;
    }
    .title {
      font-weight: bold;
      font-size: 20px;
      margin-bottom: 10px;
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;div class=&quot;container&quot;&amp;gt;
    &amp;lt;h2 class=&quot;title&quot;&amp;gt;&amp;lt;slot name=&quot;title&quot;&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;/h2&amp;gt;
    &amp;lt;div class=&quot;content&quot;&amp;gt;&amp;lt;slot name=&quot;content&quot;&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;my-component&amp;gt;
  &amp;lt;div slot=&quot;title&quot;&amp;gt;Hello World&amp;lt;/div&amp;gt;
  &amp;lt;p slot=&quot;content&quot;&amp;gt;This is the content of the component.&amp;lt;/p&amp;gt;
&amp;lt;/my-component&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서는 &lt;b&gt;slot&lt;/b&gt; 요소에 이름을 지정하여 여러 개의 &lt;b&gt;slot&lt;/b&gt; 요소를 사용합니다. &lt;b&gt;name&lt;/b&gt; 속성을 사용하여 &lt;b&gt;slot&lt;/b&gt; 요소의 이름을 지정할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Shadow DOM쓰면 뭐가 좋은가?&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;캡슐화&lt;/b&gt;: Shadow DOM은 캡슐화된 공간 안에서 동작하기 때문에, 외부에서의 접근을 제한할 수 있습니다. 이를 통해 개발자는 특정 요소의 스타일과 동작을 보호할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;격리된 스타일&lt;/b&gt;: Shadow DOM 내부에서 정의된 CSS는 기존의 CSS와 격리되어 적용됩니다. 이를 통해, 외부에서의 CSS가 Shadow DOM 안에 있는 요소에 영향을 주는 것을 막을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모듈화&lt;/b&gt;: Shadow DOM을 사용하면 개발자는 각각의 요소를 모듈화 할 수 있습니다. 이를 통해, 코드의 가독성과 유지 보수성이 향상됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 지정 요소&lt;/b&gt;: Shadow DOM은 사용자 지정 요소(custom element)를 구현하는 데 매우 유용합니다. 이를 통해, 개발자는 HTML 요소에 존재하지 않는 새로운 기능을 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발자 간의 협업&lt;/b&gt;: Shadow DOM을 사용하면 개발자 간의 협업이 용이해집니다. 각각의 요소가 독립적으로 존재하기 때문에, 개발자들은 서로의 코드를 침범하지 않고 각각의 요소를 동시에 작업할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;브라우저별 Shadow DOM 지원 버전&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFLVwc/btsfe7qVfEu/FBkAeWji8avOkV3AhzixgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFLVwc/btsfe7qVfEu/FBkAeWji8avOkV3AhzixgK/img.png&quot; data-alt=&quot;브라우저별 Shadow DOM 지원 버전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFLVwc/btsfe7qVfEu/FBkAeWji8avOkV3AhzixgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFLVwc%2Fbtsfe7qVfEu%2FFBkAeWji8avOkV3AhzixgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;브라우저별 Shadow DOM 지원 버전&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1288&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브라우저별 Shadow DOM 지원 버전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shadow DOM은 크롬 53버전, Edge 79 버전, safari 10 버전, firefox 63 버전 등등부터 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 다 지원한다고 볼 수 있지만, IE에서는 지원하지 않습니다.&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>CSS</category>
      <category>DOM</category>
      <category>html</category>
      <category>javascript</category>
      <category>Shadow DOM</category>
      <category>자바스크립트</category>
      <category>캡슐화</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/133</guid>
      <comments>https://wooncloud.tistory.com/133#entry133comment</comments>
      <pubDate>Sun, 14 May 2023 23:16:36 +0900</pubDate>
    </item>
    <item>
      <title>[노션 API] 노션 API 연동으로 데이터베이스 사용하기</title>
      <link>https://wooncloud.tistory.com/131</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;thumbnail.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRmF2V/btr8OWIXg0h/oLw3ktDP2y4YGxkOnT0EFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRmF2V/btr8OWIXg0h/oLw3ktDP2y4YGxkOnT0EFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRmF2V/btr8OWIXg0h/oLw3ktDP2y4YGxkOnT0EFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRmF2V%2Fbtr8OWIXg0h%2FoLw3ktDP2y4YGxkOnT0EFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Using database with Notion API integration&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;thumbnail.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 한 번씩 노션을 사용해 보셨을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노션 안에는 표나 갤러리, 리스트 등 노션 데이터베이스 기능이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMqFSp/btr8KI56Tyv/EpIC5eJKuWkJjyYad8Mh21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMqFSp/btr8KI56Tyv/EpIC5eJKuWkJjyYad8Mh21/img.png&quot; data-alt=&quot;임시로 만든 노션 데이터베이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMqFSp/btr8KI56Tyv/EpIC5eJKuWkJjyYad8Mh21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMqFSp%2Fbtr8KI56Tyv%2FEpIC5eJKuWkJjyYad8Mh21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Temporarily created Notion database&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;566&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;임시로 만든 노션 데이터베이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 잘 쓰면 사용자나 팀원에 좋은 데이터베이스가 되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 노션에 있는 데이터가 정말 유용한 데이터라면, 이 데이터를 이용해 서비스를 구축할 수 있습니다. 노션의 데이터베이스를 그대로 사용하면 되기 때문에 따로 데이터베이스를 만들 필요 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 서비스가 커진다면 DB를 구축해야겠지만, 프로토타입을 위해 빠른 개발이 필요하다면, 노션 데이터베이스를 사용하는 것이 좋은 선택이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가벼운 사이드 프로젝트를 만드는데도 좋은 선택이 될 수 있습니다. 따로 DB를 구축하지 않고 그 과정에서 생기는 과금도 생길 일이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 노션 데이터베이스로 몇몇 사이드 프로젝트에 적용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;노션 DB를 이용한 개발이 필요한 사람&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아주 가볍고 간단한 사이드 프로젝트를 만드는데 DB는 필요한 사람&lt;/li&gt;
&lt;li&gt;나는 백엔드를 개발해 본 경험이 없는 &amp;lsquo;Only 프론트엔드 개발자&amp;rsquo;&lt;/li&gt;
&lt;li&gt;가벼운 사이드 프로젝트를 만드는데 DB환경을 구축하기 번거롭고, 그 과정에서 과금이 없었으면 하는 사람.&lt;/li&gt;
&lt;li&gt;빠르게 프로토타입을 만들어야 하는 사람.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 노션 DB를 어떻게 내 서비스와 연동할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노션 API를 이용하여 내 노션 DB를 가져올 수 있습니다. 노션 API를 개발하기 위한 문서는 아래 링크에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.notion.com/&quot;&gt;https://developers.notion.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1681044612416&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Notion API&quot; data-og-description=&quot; &quot; data-og-host=&quot;developers.notion.com&quot; data-og-source-url=&quot;https://developers.notion.com/&quot; data-og-url=&quot;https://developers.notion.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b0pZCb/hySdxjTwf9/45mKOkN9z0Y5D5HGngYhwK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dzUu0y/hySdtaH0hq/pqPmuQPLljrhQ7fQ6Lrcbk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/i2Jyc/hySdpsCcuQ/jY1gfg9BX0KerPiBTtvEzK/img.png?width=744&amp;amp;height=920&amp;amp;face=0_0_744_920&quot;&gt;&lt;a href=&quot;https://developers.notion.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.notion.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b0pZCb/hySdxjTwf9/45mKOkN9z0Y5D5HGngYhwK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dzUu0y/hySdtaH0hq/pqPmuQPLljrhQ7fQ6Lrcbk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/i2Jyc/hySdpsCcuQ/jY1gfg9BX0KerPiBTtvEzK/img.png?width=744&amp;amp;height=920&amp;amp;face=0_0_744_920');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Notion API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.notion.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;노션 DB 연동 방법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 노션 계정이 있어야 한다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노션 계정이 있어야 하는 것은 당연합니다. 노션 계정이 없다면 만들어야겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노션 계정이 없다면 만드시고, 있다면 다음으로 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 데이터베이스 생성하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노션 계정이 있다면 데이터베이스를 만들어봅니다. 이미 연결할 데이터베이스를 만들어 두었다면, 그 DB가 있는 워크스페이스가 뭔지 기억하고 다음으로 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;863&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PQonZ/btr821ij2bh/iYxSwePzEqgC9mjXs41uJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PQonZ/btr821ij2bh/iYxSwePzEqgC9mjXs41uJk/img.png&quot; data-alt=&quot;노션의 워크스페이스, 페이지, 데이터베이스 에 대한 설명&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PQonZ/btr821ij2bh/iYxSwePzEqgC9mjXs41uJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPQonZ%2Fbtr821ij2bh%2FiYxSwePzEqgC9mjXs41uJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Description of Notion&amp;amp;#39;s workspace&amp;amp;#44; page&amp;amp;#44; and database&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;863&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;863&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;노션의 워크스페이스, 페이지, 데이터베이스 에 대한 설명&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터베이스가 없다면 먼저 페이지를 만들고 그 안에 데이터베이스를 추가해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 안에서 &lt;code&gt;/&lt;/code&gt; 를 입력하고 &lt;code&gt;데이터베이스&lt;/code&gt; 라고 치면 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 노션 API키를 발급받는다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 노션 API 키를 발급받아야 합니다. 본인 계정으로 아래의 링크를 통해 새로운 API를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.notion.so/my-integrations&quot;&gt;https://www.notion.so/my-integrations&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1681044688255&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;내 위키, 문서, 프로젝트를 모두 한 곳에&quot; data-og-description=&quot;사용하는 모든 업무 앱을 Notion 하나에 담아 팀원들과 함께하는 올인원 워크스페이스를 꾸려 보세요.&quot; data-og-host=&quot;www.notion.so&quot; data-og-source-url=&quot;https://www.notion.so/my-integrations&quot; data-og-url=&quot;https://www.notion.so&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mxNS2/hySdrDXnNM/zldTzODonWmHuhPdLESyy1/img.png?width=1200&amp;amp;height=630&amp;amp;face=907_290_1002_395,https://scrap.kakaocdn.net/dn/bfY0Oo/hySdyiPcHu/qJCSNwn3HEFH4dL4FoXrs0/img.png?width=1200&amp;amp;height=630&amp;amp;face=907_290_1002_395,https://scrap.kakaocdn.net/dn/byeM7d/hySdu1J3Wz/ZOOcUCh3H09EPPJyCgkG9k/img.png?width=1920&amp;amp;height=1200&amp;amp;face=0_0_1920_1200&quot;&gt;&lt;a href=&quot;https://www.notion.so/my-integrations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.notion.so/my-integrations&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mxNS2/hySdrDXnNM/zldTzODonWmHuhPdLESyy1/img.png?width=1200&amp;amp;height=630&amp;amp;face=907_290_1002_395,https://scrap.kakaocdn.net/dn/bfY0Oo/hySdyiPcHu/qJCSNwn3HEFH4dL4FoXrs0/img.png?width=1200&amp;amp;height=630&amp;amp;face=907_290_1002_395,https://scrap.kakaocdn.net/dn/byeM7d/hySdu1J3Wz/ZOOcUCh3H09EPPJyCgkG9k/img.png?width=1920&amp;amp;height=1200&amp;amp;face=0_0_1920_1200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;내 위키, 문서, 프로젝트를 모두 한 곳에&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;사용하는 모든 업무 앱을 Notion 하나에 담아 팀원들과 함께하는 올인원 워크스페이스를 꾸려 보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.notion.so&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1073&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bprY18/btr821Jnsk1/7ADXgwQqFWASKGBz0vVBE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bprY18/btr821Jnsk1/7ADXgwQqFWASKGBz0vVBE0/img.png&quot; data-alt=&quot;개인 API integrations 관리 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bprY18/btr821Jnsk1/7ADXgwQqFWASKGBz0vVBE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbprY18%2Fbtr821Jnsk1%2F7ADXgwQqFWASKGBz0vVBE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Private API integrations management page&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1073&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1073&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개인 API integrations 관리 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크를 들어가면 위와 같은 화면을 볼 수 있습니다. 여기서 새로운 API를 만들어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1999&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cK9I0q/btr820KrV6t/I4aRmYu8ZzYfAhtcwXqgk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cK9I0q/btr820KrV6t/I4aRmYu8ZzYfAhtcwXqgk1/img.png&quot; data-alt=&quot;새로운 API 만드는 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cK9I0q/btr820KrV6t/I4aRmYu8ZzYfAhtcwXqgk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcK9I0q%2Fbtr820KrV6t%2FI4aRmYu8ZzYfAhtcwXqgk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Page to create a new API&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;625&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1999&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;새로운 API 만드는 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용하실 API의 이름을 작성해줘야 합니다. 특별하게 형식이 없기 때문에 마음대로 작성하시면 됩니다.&lt;/li&gt;
&lt;li&gt;로고도 맘대로 하셔도 되지만, 저 로고가 만드신 API를 식별하기 위한 수단이 될 수 있습니다.&lt;br /&gt;그래서 API를 많이 만드시게 된다면, API의 로고들을 모두 같게 하지 않고 다르게 하는 게 보기 좋습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;821&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T2vIh/btr8LqYBiBf/2Q1k95G5u8R8c3QK48Gktk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T2vIh/btr8LqYBiBf/2Q1k95G5u8R8c3QK48Gktk/img.png&quot; data-alt=&quot;노션에서 생성한 API의 로고와 이름이 나타나는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T2vIh/btr8LqYBiBf/2Q1k95G5u8R8c3QK48Gktk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT2vIh%2Fbtr8LqYBiBf%2F2Q1k95G5u8R8c3QK48Gktk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Appearance of API logo and name created in Notion&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;205&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;821&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;노션에서 생성한 API의 로고와 이름이 나타나는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 연결할 데이터베이스가 있는 워크스페이스를 선택해 주세요. 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qcHHF/btr8Mn1nj47/9XQJ1ON9I4Df0kuXJLtvW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qcHHF/btr8Mn1nj47/9XQJ1ON9I4Df0kuXJLtvW0/img.png&quot; data-alt=&quot;API를 만들면 생성되는 시크릿 키&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qcHHF/btr8Mn1nj47/9XQJ1ON9I4Df0kuXJLtvW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqcHHF%2Fbtr8Mn1nj47%2F9XQJ1ON9I4Df0kuXJLtvW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;A secret key generated when you create an API&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;284&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;974&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;API를 만들면 생성되는 시크릿 키&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 노션 API가 만들어지면 시크릿 KEY를 발급받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 KEY를 이용하여 노션 DB에 연결할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 만들어진 API를 페이지와 연결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 만들어진 API를 데이터베이스가 있는 페이지에 연결해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mgFCg/btr8LsoAJd8/0VYSYRLUbsFYHFkKeTPfv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mgFCg/btr8LsoAJd8/0VYSYRLUbsFYHFkKeTPfv0/img.png&quot; data-alt=&quot;만들어진 API를 적용하는 방&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mgFCg/btr8LsoAJd8/0VYSYRLUbsFYHFkKeTPfv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmgFCg%2Fbtr8LsoAJd8%2F0VYSYRLUbsFYHFkKeTPfv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;How to apply the created API&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1471&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1471&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;만들어진 API를 적용하는 방&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw4omA/btr8Mr3BjsQ/2DyzMxaGFpI1QZgP6FAQaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw4omA/btr8Mr3BjsQ/2DyzMxaGFpI1QZgP6FAQaK/img.png&quot; data-alt=&quot;검색하면 내가 만든 API가 보인다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw4omA/btr8Mr3BjsQ/2DyzMxaGFpI1QZgP6FAQaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw4omA%2Fbtr8Mr3BjsQ%2F2DyzMxaGFpI1QZgP6FAQaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;If you search&amp;amp;#44; you can see the API I created.&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;265&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;2122&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;검색하면 내가 만든 API가 보인다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 연결하면 페이지 하위의 페이지까지 모두 연결이 되니 상단에 연결하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 노션 데이터베이스 아이디 얻기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 데이터베이스의 정보를 받아보려면 먼저 데이터베이스 아이디를 얻어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노션 데이터베이스 아이디는 링크 복사를 통해서 얻을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CfUsD/btr8T5lizQk/xTdTziT7jKQN02PSsGa6w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CfUsD/btr8T5lizQk/xTdTziT7jKQN02PSsGa6w1/img.png&quot; data-alt=&quot;노션 데이터베이스 링크 얻는 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CfUsD/btr8T5lizQk/xTdTziT7jKQN02PSsGa6w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCfUsD%2Fbtr8T5lizQk%2FxTdTziT7jKQN02PSsGa6w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;How to get a link to the Notion database&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;492&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;노션 데이터베이스 링크 얻는 방법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 링크 복사하면 아래와 같은 링크를 얻을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XnfAA/btr8LgVN0cN/C2cMmKn1HK1FmqCtMJbsPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XnfAA/btr8LgVN0cN/C2cMmKn1HK1FmqCtMJbsPk/img.png&quot; data-alt=&quot;데이터베이스 아이디&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XnfAA/btr8LgVN0cN/C2cMmKn1HK1FmqCtMJbsPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXnfAA%2Fbtr8LgVN0cN%2FC2cMmKn1HK1FmqCtMJbsPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;database id&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;83&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터베이스 아이디&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복사한 링크의 저 부분이 데이터베이스 아이디입니다. 저 링크를 이용하여 관련 데이터베이스를 연결하여 API를 호출할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. API 호출하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모든 게 준비되었습니다. 이제 API를 호출하여 DB에 있는 정보를 얻어보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;943&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh7F0i/btr8UVit7FG/WJcyKMQBnxgSJsC7UvRFP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh7F0i/btr8UVit7FG/WJcyKMQBnxgSJsC7UvRFP0/img.png&quot; data-alt=&quot;노션 API로 데이터베이스 정보를 얻 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh7F0i/btr8UVit7FG/WJcyKMQBnxgSJsC7UvRFP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh7F0i%2Fbtr8UVit7FG%2FWJcyKMQBnxgSJsC7UvRFP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;How to get database information with Notion API&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;943&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;943&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;노션 API로 데이터베이스 정보를 얻 방법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API 호출 메소드는 POST여야 합니다.&lt;/li&gt;
&lt;li&gt;호출할 API의 url은 다음과 같습니다.&lt;br /&gt;&lt;code&gt;https://api.notion.com/v1/databases/{{데이터베이스 아이디}}/query&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;header는 다음과 같이 3개를 입력해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.2326%;&quot;&gt;&lt;b&gt;Authorization&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.7674%;&quot;&gt;api 시크릿 키를 입력해야 합니다.&lt;br /&gt;&lt;br /&gt;Bearer {{api 시크릿 키}}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.2326%;&quot;&gt;&lt;b&gt;Notion-Version&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.7674%;&quot;&gt;노션 api 버전입니다. 최신버전으로 하시면 됩니다.&lt;br /&gt;버전은 아래 링크에서 확인할 수 있습니다.&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://developers.notion.com/reference/changes-by-version&quot;&gt;https://developers.notion.com/reference/changes-by-version&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.2326%;&quot;&gt;&lt;b&gt;Content-Type&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 79.7674%;&quot;&gt;application/json&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 Notion-Version을 잘못 입력하면 아래와 같이 응답이 옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baNtVO/btr8LNzfMKI/s1r6sC1wfoZbvwTx1Nta20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baNtVO/btr8LNzfMKI/s1r6sC1wfoZbvwTx1Nta20/img.png&quot; data-alt=&quot;잘못된 버전을 받을 경우, 응답으로 버전 가이드를 해주는 노션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baNtVO/btr8LNzfMKI/s1r6sC1wfoZbvwTx1Nta20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaNtVO%2Fbtr8LNzfMKI%2Fs1r6sC1wfoZbvwTx1Nta20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Notion that gives a version guide in response if you receive the wrong version&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;201&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;잘못된 버전을 받을 경우, 응답으로 버전 가이드를 해주는 노션&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 결과가 json으로 옵니다. DB에 있는 모든 정보들이 내려오기 때문에, 필요한 데이터를 잘 뽑아 쓸 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwaLaU/btr8NdRKnAQ/tBLu0mCQFpxYWY0fzTkraK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwaLaU/btr8NdRKnAQ/tBLu0mCQFpxYWY0fzTkraK/img.png&quot; data-alt=&quot;결과가 json으로 잘 내려오는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwaLaU/btr8NdRKnAQ/tBLu0mCQFpxYWY0fzTkraK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwaLaU%2Fbtr8NdRKnAQ%2FtBLu0mCQFpxYWY0fzTkraK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;The result looks like coming down as json&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1405&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1405&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과가 json으로 잘 내려오는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이대로 끝인가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅으로 노션 API로부터 DB 정보를 얻는 방법을 알려드렸지만, 이 외 CRUD가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프론트만 개발한다면 CORS 문제를 직면하게 되겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 CORS 해결과 다방면으로 노션 DB API를 활용하는 방법에 대해 알아보겠습니다.&lt;/p&gt;</description>
      <category>개발 아카이브/개발 관련 지식</category>
      <category>API</category>
      <category>노션</category>
      <category>노션 데이터베이스</category>
      <category>노션DB</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/131</guid>
      <comments>https://wooncloud.tistory.com/131#entry131comment</comments>
      <pubDate>Sun, 9 Apr 2023 22:14:51 +0900</pubDate>
    </item>
    <item>
      <title>포토샵 vcruntime140_1.dll 오류 해결 방법</title>
      <link>https://wooncloud.tistory.com/130</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uUwjy/btr2NkifxCf/irTQWJN8UJLtkbuTy4JHL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uUwjy/btr2NkifxCf/irTQWJN8UJLtkbuTy4JHL0/img.png&quot; data-alt=&quot;이게 4번 뜨면서 포토샵을 안열어준다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uUwjy/btr2NkifxCf/irTQWJN8UJLtkbuTy4JHL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuUwjy%2Fbtr2NkifxCf%2FirTQWJN8UJLtkbuTy4JHL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;vcruntime140_1.dll problem&quot; loading=&quot;lazy&quot; width=&quot;411&quot; height=&quot;165&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이게 4번 뜨면서 포토샵을 안열어준다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vcruntime140_1.dll이(가) 없어 코드 실행을 진행할 수 없습니다. 프로그램을 다시 설치하면 이 문제가 해결될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 문구가 뜨면서 포토샵을 열 수 없는 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 다시 지웠다가 깔아서 잘 되면 문제 없겠지만, 지웠다 깔아도 이 팝업이 계속 뜰 경우가 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크를 통해서 본인이 사용중인 컴퓨터의 운영체제와 프로세서에 맞게 패키지를 다운받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170&quot;&gt;https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1678291280613&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Latest supported Visual C++ Redistributable downloads&quot; data-og-description=&quot;This article lists the download links for the latest versions of Visual C++ Redistributable packages.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170&quot; data-og-url=&quot;https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JTtzR/hyRSM8PLnx/jE1rwrUinSx5Syp89cJLQ0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JTtzR/hyRSM8PLnx/jE1rwrUinSx5Syp89cJLQ0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Latest supported Visual C++ Redistributable downloads&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This article lists the download links for the latest versions of Visual C++ Redistributable packages.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;948&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byHwXH/btr2D9opRDy/qd0pJpOMvJUWE63fyHzdU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byHwXH/btr2D9opRDy/qd0pJpOMvJUWE63fyHzdU0/img.png&quot; data-alt=&quot;마이크로소프트에서 배포한 Permalink 패키지를 다운받습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byHwXH/btr2D9opRDy/qd0pJpOMvJUWE63fyHzdU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyHwXH%2Fbtr2D9opRDy%2Fqd0pJpOMvJUWE63fyHzdU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Download the Permalink package distributed by Microsoft.&quot; loading=&quot;lazy&quot; width=&quot;1357&quot; height=&quot;948&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;948&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마이크로소프트에서 배포한 Permalink 패키지를 다운받습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 네모친 구간에 있는 패키지를 둘 중에 자신의 컴퓨터에 맞는 패키지를 다운받아 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 어떤걸 골라야 할지 알기 위해선, 아래 동작대로 들어가서 알아볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 x64로 받습니다. 요즘 x86은 잘 없어서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #000000;&quot;&gt;&lt;b&gt;폴더 하나 열기 &amp;gt; 내컴퓨터 오른쪽 클릭 &amp;gt; 속성 클릭&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkAE1d/btr2RXMTWm5/YjZmwIYWzvruBsbJrMa7Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkAE1d/btr2RXMTWm5/YjZmwIYWzvruBsbJrMa7Bk/img.png&quot; data-alt=&quot;폴더 하나 열기 &amp;amp;gt; 내컴퓨터 오른쪽 클릭 &amp;amp;gt; 속성 클릭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkAE1d/btr2RXMTWm5/YjZmwIYWzvruBsbJrMa7Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkAE1d%2Fbtr2RXMTWm5%2FYjZmwIYWzvruBsbJrMa7Bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Open a folder &amp;amp;gt; right click on my computer &amp;amp;gt; click on properties&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;409&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;폴더 하나 열기 &amp;gt; 내컴퓨터 오른쪽 클릭 &amp;gt; 속성 클릭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 윈도우 11이지만, 윈도우7, 윈도우10.. 모두 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QkO9G/btr2RWtGHrq/n7vWXeJVmDjWCCh2XNcgs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QkO9G/btr2RWtGHrq/n7vWXeJVmDjWCCh2XNcgs0/img.png&quot; data-alt=&quot;?? 비트 운영 체제, x?? 기반 프로세서 라는 부분을 보면 된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QkO9G/btr2RWtGHrq/n7vWXeJVmDjWCCh2XNcgs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQkO9G%2Fbtr2RWtGHrq%2Fn7vWXeJVmDjWCCh2XNcgs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;system type&quot; loading=&quot;lazy&quot; width=&quot;830&quot; height=&quot;650&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;?? 비트 운영 체제, x?? 기반 프로세서 라는 부분을 보면 된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위와 같은 창이 나오는데, 창에서 ?? 비트 운영 체제, x?? 기반 프로세서 라는 부분을 찾아 보시면 자신이 어떤 패키지를 다운받아야 하는지 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn9vmT/btr2D7Ysshf/gnKrhaIifzwaHrgMDm7yo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn9vmT/btr2D7Ysshf/gnKrhaIifzwaHrgMDm7yo0/img.png&quot; data-alt=&quot;설치&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn9vmT/btr2D7Ysshf/gnKrhaIifzwaHrgMDm7yo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn9vmT%2Fbtr2D7Ysshf%2FgnKrhaIifzwaHrgMDm7yo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;installation&quot; loading=&quot;lazy&quot; width=&quot;485&quot; height=&quot;301&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;설치&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운 받은 패키지를 실행하시면 위와 같은 설치 창이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이미 설치해서 복구창이 나오지만 여러분은 설치 화면이 나올 것입니다. 안내대로 설치를 하신 후, 포토샵을 켜면 잘 실행되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;503&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZYEE2/btr2RXF7y3a/F3pQFr1e9PjsXgcx1wbrx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZYEE2/btr2RXF7y3a/F3pQFr1e9PjsXgcx1wbrx1/img.png&quot; data-alt=&quot;잘 실행이 되는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZYEE2/btr2RXF7y3a/F3pQFr1e9PjsXgcx1wbrx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZYEE2%2Fbtr2RXF7y3a%2FF3pQFr1e9PjsXgcx1wbrx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;photoshop works well&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;503&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;잘 실행이 되는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스트는 2023.03.09.에 작성된 내용이며 오래된 경우, 정확한 정보가 아닐 수 있습니다.&lt;/p&gt;</description>
      <category>정보</category>
      <category>VCRUNTIME140_1.dll</category>
      <category>오류</category>
      <category>포토샵</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/130</guid>
      <comments>https://wooncloud.tistory.com/130#entry130comment</comments>
      <pubDate>Thu, 9 Mar 2023 01:19:52 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 엔진인 V8에 대해 알아보자</title>
      <link>https://wooncloud.tistory.com/128</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d439tN/btr0NNMMkiZ/GOePf2fkCu7fQf7ujswgX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d439tN/btr0NNMMkiZ/GOePf2fkCu7fQf7ujswgX1/img.png&quot; data-alt=&quot;V8!V8!V8!V8!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d439tN/btr0NNMMkiZ/GOePf2fkCu7fQf7ujswgX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd439tN%2Fbtr0NNMMkiZ%2FGOePf2fkCu7fQf7ujswgX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;V8&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;400&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;V8!V8!V8!V8!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8 엔진은 구글이 개발한 고성능 자바스크립트 엔진으로, Google Chrome과 Node.js에서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8은 ECMAScript 및 Web Assembly를 표준에 맞게 구현하였으며, JavaScript 코드를 컴파일하여 매우 빠른 실행 속도를 보장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Just-In-Time(JIT)이라는 컴파일링 기술을 사용하여, JavaScript 코드를 실행할 때 실시간으로 컴파일합니다. 그리고 안정적이고 안전한 구조를 가지고 있어, 코드가 비정상적으로 동작하거나 메모리 누수가 발생하는 경우를 막습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 가볍고, 빠르고, 강력한 자바스크립트 엔진이기 때문에, 현재도 여러 응용 프로그램에서 사용되고 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;V8엔진의 구조&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhG5rH/btr0KRPs051/RjoR9cabTkqf9mJRuKHE80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhG5rH/btr0KRPs051/RjoR9cabTkqf9mJRuKHE80/img.png&quot; data-alt=&quot;V8의 구조 (https://medium.com/@stepheng1492/googles-v8-engine-718c15ead06a)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhG5rH/btr0KRPs051/RjoR9cabTkqf9mJRuKHE80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhG5rH%2Fbtr0KRPs051%2FRjoR9cabTkqf9mJRuKHE80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;V8엔진의 구조&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;V8의 구조 (https://medium.com/@stepheng1492/googles-v8-engine-718c15ead06a)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8은 다양한 구성 모듈을 포함하고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Ignition&lt;/b&gt;: 자바스크립트 언어를 한줄씩 코드를 바이트코드(Bytecode)로 변환해 주는 인터프리터입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TurboFan&lt;/b&gt;: ignition을 통해 만들어진 바이트코드 중, 프로파일러를 통해 자주 사용하는 코드를 찾아 최적화해주는 최적화 컴파일러입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Liftoff&lt;/b&gt;: V8이 WebAssembly를 사용할 수 있도록. WASM을 컴파일해주는 One-pass compiler입니다. 이렇게 V8엔진은 Liftoff 덕분에 javascript 이외의 C나 C++과 같은 언어를 WebAssembly를 통해서 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Orinoco&lt;/b&gt;: Orinoco는 가비지 컬렉션에 병렬(parallel), 인크리멘탈(incremental) 및 동시(concurrent) 기술을 사용하여 메인 스레드를 방해하지 않도록 하는 V8 GC 프로젝트의 코드명입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;V8엔진의 동작방식&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JUCwi/btr0HuueNgI/78NA0WtbubDibpLAFoEjO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JUCwi/btr0HuueNgI/78NA0WtbubDibpLAFoEjO0/img.png&quot; data-alt=&quot;V8 동작 방식 (https://medium.com/@ramez.aijaz/intro-to-webassembly-how-it-works-trade-offs-demo-6d5c0feb1b0c)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JUCwi/btr0HuueNgI/78NA0WtbubDibpLAFoEjO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJUCwi%2Fbtr0HuueNgI%2F78NA0WtbubDibpLAFoEjO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;V8엔진의 동작방식&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;411&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;657&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;V8 동작 방식 (https://medium.com/@ramez.aijaz/intro-to-webassembly-how-it-works-trade-offs-demo-6d5c0feb1b0c)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8엔진의 동작 방식은 여러 가지가 있지만 그중 가장 대표적인 동작방식을 설명하려 합니다. 기본적으로 javascript가 V8엔진을 통해 어떻게 실행이 되는지 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Parser와 AST&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl0Cii/btr1Y9tMIA4/Inu5PLiHgQHLmA1uWxBnKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl0Cii/btr1Y9tMIA4/Inu5PLiHgQHLmA1uWxBnKk/img.png&quot; data-alt=&quot;소스를 산산조각 낼테다!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl0Cii/btr1Y9tMIA4/Inu5PLiHgQHLmA1uWxBnKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl0Cii%2Fbtr1Y9tMIA4%2FInu5PLiHgQHLmA1uWxBnKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;소스를 산산조각 낼테다!&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;203&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;소스를 산산조각 낼테다!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8엔진은 javascript 소스코드를 받으면 먼저 Lexical Analysis (낱말 분석) 과정을 통해 코드를 토큰으로 분해합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 분해된 토큰들은 인터프리터가 읽기 쉽도록 AST(Abstract Syntax Tree)라는 추상 구문 트리로 변환됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HSxbT/btr1QyuVCDl/sA5Z44NZ2djEjKxrqrVts0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HSxbT/btr1QyuVCDl/sA5Z44NZ2djEjKxrqrVts0/img.png&quot; data-alt=&quot;비슷한 예시로 이렇게 산산 조각 내버린다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HSxbT/btr1QyuVCDl/sA5Z44NZ2djEjKxrqrVts0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHSxbT%2Fbtr1QyuVCDl%2FsA5Z44NZ2djEjKxrqrVts0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Abstract Syntax Tree&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;274&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비슷한 예시로 이렇게 산산 조각 내버린다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Ignition&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zailz/btr0GctPPXv/rHJZGAzqvV5BcUUEu1Hoz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zailz/btr0GctPPXv/rHJZGAzqvV5BcUUEu1Hoz1/img.png&quot; data-alt=&quot;ignition&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zailz/btr0GctPPXv/rHJZGAzqvV5BcUUEu1Hoz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZailz%2Fbtr0GctPPXv%2FrHJZGAzqvV5BcUUEu1Hoz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Ignition&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;238&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1521&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ignition&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 만들어진 AST를 Ignition이라는 인터프리터에 넘겨지고 바이트 코드(Bytecode)로 변환됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8lWzB/btr0NMG6Jnp/kDy2sRXO5cPsLvQEBqwtA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8lWzB/btr0NMG6Jnp/kDy2sRXO5cPsLvQEBqwtA0/img.png&quot; data-alt=&quot;ignition은 AST로 만들어진 자바스크립트 토큰들을 읽고 Bytecode로 변환합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8lWzB/btr0NMG6Jnp/kDy2sRXO5cPsLvQEBqwtA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8lWzB%2Fbtr0NMG6Jnp%2FkDy2sRXO5cPsLvQEBqwtA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;ignition은 AST로 만들어진 자바스크립트 토큰들을 읽고 Bytecode로 변환합니다.&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;238&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ignition은 AST로 만들어진 자바스크립트 토큰들을 읽고 Bytecode로 변환합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 가상머신이 이 바이트 코드를 실행함으로써 우리가 작성한 코드가 동작하게 되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;TurboFan&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CkTLc/btr0IuHcMZY/SIFselt7hmj6Y2eklK3ROk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CkTLc/btr0IuHcMZY/SIFselt7hmj6Y2eklK3ROk/img.png&quot; data-alt=&quot;TurboFan&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CkTLc/btr0IuHcMZY/SIFselt7hmj6Y2eklK3ROk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCkTLc%2Fbtr0IuHcMZY%2FSIFselt7hmj6Y2eklK3ROk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;TurboFan&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;167&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TurboFan&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ignition으로 만들어진 코드가 실행되면서 프로파일러가 자주 사용되는 코드를 찾아 TurboFan에 전달합니다. TurboFan은 이런 자주 사용되는 바이트 코드를 최적화하여 머신 코드로 컴파일합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDsGTt/btr0HRo83pE/RHYdC198As0qKKoKVUZSvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDsGTt/btr0HRo83pE/RHYdC198As0qKKoKVUZSvk/img.png&quot; data-alt=&quot;TurboFan은 자주 사용되는 바이트 코드를 최적화하여 머신 코드로 컴파일합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDsGTt/btr0HRo83pE/RHYdC198As0qKKoKVUZSvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDsGTt%2Fbtr0HRo83pE%2FRHYdC198As0qKKoKVUZSvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;TurboFan은 자주 사용되는 바이트 코드를 최적화하여 머신 코드로 컴파일합니다.&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;203&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TurboFan은 자주 사용되는 바이트 코드를 최적화하여 머신 코드로 컴파일합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이후 바이트 코드를 사용하지 않고 TurboFan이 만든 머신 코드를 사용하게 되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이후 최적화된 코드가 다시 자주 사용하지 않거나 중간에 코드가 변경될 경우, 디옵티마이징(Deoptimizing)이 되어 다시 바이트코드로 변환할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;옛날과 현재의 V8엔진&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8엔진은 5.9 버전 이전과 이후로 파이프라인의 변화가 크게 바뀌었습니다. 위에서 설명한 Ignition과 TurboFan은 5.9버전 이후로 등장했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 5.9버전 이전의 V8엔진은 어떻게 생겼고, 왜 이렇게 바뀌었을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;과거의 V8엔진 Full-codegen과 Crankshaft&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8은 독일 구글 개발 센터에서 만들어진 JavaScript 엔진으로, 웹 브라우저 안에서 실행되는 JavaScript의 성능을 높이기 위해 만들어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript는 인터프리터 언어입니다. 인터프리터 언어는 코드를 실행할 때에 인터프리터가 머신 코드로 번역하고 실행하기 때문에 컴파일 언어에 비해 비교적 느립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에선 속도가 빠른 JavaScript 엔진을 만들기 위해 인터프리터를 이용하는 대신, JavaScript 코드를 좀 더 효율적인 머신 코드로 번역하는 방법을 선택했습니다. 즉, 바이트코드 또는 다른 중간 코드를 생성하지 않는다는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 V8과 탄생한 것이 Full-codegen과 Crankshaft입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;V8 엔진의 Full-codegen 컴파일러&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3232&quot; data-origin-height=&quot;1171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/soHM2/btr1Mo0GTE4/wx0guL95WATTH70iGCFiN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/soHM2/btr1Mo0GTE4/wx0guL95WATTH70iGCFiN0/img.png&quot; data-alt=&quot;재미로 가져온 공식 문서의 full-codegen.cc dependency 그래프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/soHM2/btr1Mo0GTE4/wx0guL95WATTH70iGCFiN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsoHM2%2Fbtr1Mo0GTE4%2Fwx0guL95WATTH70iGCFiN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;재미로 가져온 공식 문서의 full-codegen.cc dependency 그래프&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;290&quot; data-origin-width=&quot;3232&quot; data-origin-height=&quot;1171&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;재미로 가져온 공식 문서의 full-codegen.cc dependency 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8 엔진의 Full-codegen 컴파일러는 명령어를 실행하기 위해 자바스크립트 코드를 머신 코드로 번역하는 과정에서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계에서, 자바스크립트 코드는 초기화되고 분석됩니다. 그리고, 이를 위해 각각 수행해야 할 각 명령어들이 계산됩니다. 명령어들의 세부 사항은 레지스터들과 메모리 주소들에 따라 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 생성된 머신 코드는 실행 방법과 메모리 관리 방법에 따라 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;V8 엔진의 Crankshaft 옵티마이저&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V8 엔진의 Crankshaft는 두 번째 단계인 옵티마이저입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵티마이저는 이전 단계에서 만들어진 머신 코드를 수정하고 최적화하기 위해 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Crankshaft 옵티마이저는 Javascript 코드의 성능을 향상시키기 위해 사용됩니다. Crankshaft은 변수의 값이 정해져 있거나 반복되는 경우에 사용되는 기계 코드의 재사용을 통해 명령어를 최적화합니다. 이는 코드의 실행 속도를 높이며, 메모리 관리를 더욱 효율적으로 할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 Full-codegen과 Crankshaft는 대체되었는가?&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;465&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qKNzi/btr0RPDvpKo/Jo88w9fBMsj4pho6L2ZMFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qKNzi/btr0RPDvpKo/Jo88w9fBMsj4pho6L2ZMFk/img.png&quot; data-alt=&quot;크롬은 메모리를 머거&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qKNzi/btr0RPDvpKo/Jo88w9fBMsj4pho6L2ZMFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqKNzi%2Fbtr0RPDvpKo%2FJo88w9fBMsj4pho6L2ZMFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;크롬은 메모리를 머거&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;291&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;465&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크롬은 메모리를 머거&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 크롬이 메모리를 어마어마하게 많이 먹어서 생겨난 밈입니다. 크롬은 왜 저런 밈이 생겼을까요?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Full-codegen에서 Ignition으로!&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬은 V8을 사용합니다. 옛날의 Full-codegen을 사용하던 V8은 전체 javascript 코드를 한꺼번에 머신 코드로 컴파일을 했습니다. 그렇기 때문에 메모리 사용량이 많았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문제는 스마트폰이 탄생하면서 크롬도 모바일을 지원해야 하게 되었습니다. 과거의 메모리 사양이 부족했던 시절, Full-codegen은 모바일에 문제가 크게 부각되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;541&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhlzqw/btr1Wy117hk/wF7sy5Kfd2P97pdLU8Bj2K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhlzqw/btr1Wy117hk/wF7sy5Kfd2P97pdLU8Bj2K/img.jpg&quot; data-alt=&quot;크롬을 폰에..? ㅈ..죽여..줘..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhlzqw/btr1Wy117hk/wF7sy5Kfd2P97pdLU8Bj2K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdhlzqw%2Fbtr1Wy117hk%2FwF7sy5Kfd2P97pdLU8Bj2K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;phone die&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;169&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;541&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크롬을 폰에..? ㅈ..죽여..줘..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 전체 코드를 컴파일하는 것이 아닌 다시 인터프리터 방식으로 돌아와 메모리를 아끼고 바이트 코드의 장점을 더욱 살려 현재의 ignition이 만들어지게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Crankshaft는 TurboFan으로!&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Crankshaft는 javascript의 지속적인 발전과 다양한 아키텍처의 등장으로 계속 새로운 사양에 맞춰 최적화를 해나가야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 Crankshaft는 몸집이 불어나고 확장되었습니다. 구글은 계속 Crankshaft를 계속 확장하는 것은 무리라고 판단했다 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btWpuS/btr1YJBXyhX/zJDexqlykagJrlWAxarCyk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btWpuS/btr1YJBXyhX/zJDexqlykagJrlWAxarCyk/img.jpg&quot; data-alt=&quot;구글도 레거시 지옥..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btWpuS/btr1YJBXyhX/zJDexqlykagJrlWAxarCyk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtWpuS%2Fbtr1YJBXyhX%2FzJDexqlykagJrlWAxarCyk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;legacy hell&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;272&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구글도 레거시 지옥..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 파이프라인을 개선하면서 여러 레이어로 계층화되고, 좀 더 유연하게 확장에 용이하도록 설계하여 현재의 TurboFan이 만들어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 현재의 TurboFan은 확장에 용이해졌기 때문에, Ignition을 통한 바이트코드를 최적화하는 것뿐만 아니라, LiftOff로 만들어진 코드를 최적화하는 등 다양한 방면으로 사용되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다음 이야기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 TurboFan의 최적화 이야기에 대해 알려드리고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TurboFan은 다양한 최적화 방법을 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;691&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwl6zk/btr1YJvcKsq/767aQ7egCOhGh4OEwclw41/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwl6zk/btr1YJvcKsq/767aQ7egCOhGh4OEwclw41/img.jpg&quot; data-alt=&quot;이렇게 TurboFan을 슥 들여다보자.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwl6zk/btr1YJvcKsq/767aQ7egCOhGh4OEwclw41/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwl6zk%2Fbtr1YJvcKsq%2F767aQ7egCOhGh4OEwclw41%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;TurboFan&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;162&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;691&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게 TurboFan을 슥 들여다보자.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 V8은 웹 개발이나 Nodejs를 이용한 서버 개발, electron 개발 등 다양한 방면에서 사용되고 있는 만큼 최적화 방식에 대한 중요성을 높다고 생각됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 내용이 있는 경우 댓글로 지적해 주시면 검토 후 반영하겠습니다.&lt;/p&gt;</description>
      <category>개발 아카이브/개발 관련 지식</category>
      <category>Crankshaft</category>
      <category>Full-codegen</category>
      <category>Ignition</category>
      <category>javascript</category>
      <category>liftoff</category>
      <category>Orinoco</category>
      <category>TurboFan</category>
      <category>V8</category>
      <category>자바스크립트</category>
      <category>크롬</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/128</guid>
      <comments>https://wooncloud.tistory.com/128#entry128comment</comments>
      <pubDate>Sun, 26 Feb 2023 17:05:56 +0900</pubDate>
    </item>
    <item>
      <title>[자바스크립트] String에 대해 알아보자.</title>
      <link>https://wooncloud.tistory.com/127</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;js string&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글또를 위한 포스팅을 위해 자바스크립트에 대한 지식을 공부 겸 포스팅해보려고 합니다. 이번에 알아볼 내용은 자바스크립트 String에 대한 내용입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서는 리터럴인 문자열을 다룰 때 사용하는 String이라는 객체가 있습니다. 이 String 객체는 문자열을 생성하거나, 검색, 치환, 추출, 분할 등의 작업을 수행할 수 있는 다양한 메소드를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 전체적으로 어떤 메소드가 있는지 간략하게 설명하고, 차후 몇몇 메소드에 대한 디테일한 내용을 다뤄볼까 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;javascript Property&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;length&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;length&lt;/code&gt;&lt;/b&gt; : 문자열의 길이를 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;'hello world'.length // 11&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;javascript Method&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;at&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&lt;b&gt;at(index)&lt;/b&gt;&lt;/code&gt; : 문자열 내의 특정 인덱스에 있는 문자를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열의 인덱스는 0부터 시작하며, 문자열의 길이보다 큰 숫자를 입력하면 undefined가 반환됩니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;'Hello World'.at(0); // 'H'
'Hello World'.at(6); // 'W'
'Hello World'.at(11); // undefined&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;charAt&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;charAt(index)&lt;/code&gt;&lt;/b&gt; : 주어진 인덱스에 해당하는 문자를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 입력하는 index가 문자열 범위를 넘어가는 경우 빈 문자열을 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.charAt(1); // 'e'
str.charAt(99); // ''
str.charAt(-1); // ''&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;charCodeAt&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;charCodeAt(index)&lt;/code&gt; :&lt;/b&gt; 주어진 인덱스에 해당하는 문자의 UTF-16 코드를 반환합니다. charCodeAt을 통해서 문자열에서 특정 위치의 문자에 대한 Unicode 값을 얻을 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;'hello'.charCodeAt(1) // 101
'와우'.charCodeAt(0); // 50752
'a'.charCodeAt(-1) // NaN
'b'.charCodeAt(10) // NaN&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;codePointAt&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;codePointAt(pos)&lt;/code&gt;&lt;/b&gt; : 문자열의 pos 위치에 있는 문자의 유니코드 코드 포인트 값을 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;&quot;Hello, World!&quot;.codePointAt(0); // 72&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;fromCharCode&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;fromCharCode(unicode)&lt;/code&gt;&lt;/b&gt; : ASCII 코드 값으로 구성된 숫자 배열을 통해 문자열을 생성하는 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fromCharCode는 적절한 경우에는 간단하고 편리한 방법으로 문자열을 생성할 수 있습니다. 하지만 유니코드 문자를 처리해야 한다면, String.fromCodePoint() 메소드를 사용하는 것이 더 좋은 방법입니다.&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;// 65는 A의 ASCII 코드 값
let char = String.fromCharCode(65);
console.log(char); // &quot;A&quot;

// A~Z의 ASCII 코드 값을 가진 문자열 만들기
let str = '';
for (let i = 65; i &amp;lt;= 90; i++) {
  str += String.fromCharCode(i);
}
console.log(str); // &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;fromCodePoint&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;fromCodePoint(num1[, ...[, numN]])&lt;/code&gt;&lt;/b&gt; : 자바스크립트에서 문자열을 생성할 때, 유니코드 코드 포인트 값으로 구성된 숫자 배열을 사용하여 문자열을 생성할 수 있는 기능을 제공하며, ES6 이상에서 사용 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해 UTF-16의 모든 값을 지원하기 때문에, 다양한 언어의 문자와 이모지 등을 표현하는데 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;console.log(String.fromCodePoint(72, 101, 108, 108, 111)); // &quot;Hello&quot;
console.log(String.fromCodePoint(128516)); // &quot; &quot;
console.log(String.fromCodePoint(0x1F600)); // &quot; &quot;
console.log(String.fromCodePoint(119989)); // &quot; &quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;concat&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;concat(string1, string2, ...)&lt;/code&gt;&lt;/b&gt; : 여러 개의 문자열을 합쳐서 하나의 문자열로 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;let str1 = &quot;Hello&quot;;
let str2 = &quot; World&quot;;
str1.concat(str2); // &quot;Hello World&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;indexOf&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;indexOf(searchValue, start)&lt;/code&gt;&lt;/b&gt; : 주어진 값을 찾아 첫 번째로 발견한 인덱스를 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.indexOf(&quot;World&quot;); // 6
str.indexOf(&quot;world&quot;); // -1&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;lastIndexOf&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;lastIndexOf(searchValue, start)&lt;/code&gt;&lt;/b&gt; : 주어진 값을 찾아 마지막으로 발견한 인덱스를 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World, Hello World&quot;;
str.lastIndexOf(&quot;World&quot;); // 13
str.lastIndexOf(&quot;world&quot;); // -1&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;localeCompare&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;localeCompare(compareString, locales, options)&lt;/code&gt;&lt;/b&gt; : 두 개의 문자열을 비교하여 먼저 오는 문자열이 뭔지 -1, 0, 1로 반환합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 문자열이 두 번째 문자열보다 앞에 오는 경우 -1을 반환&lt;/li&gt;
&lt;li&gt;두 번째 문자열이 첫 번째 문자열보다 앞에 오는 경우 1을 반환&lt;/li&gt;
&lt;li&gt;두 번째 문자열과 첫 번째 문자열이 같은 경우 0을 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;&quot;apple&quot;.localeCompare(&quot;banana&quot;); // -1
&quot;banana&quot;.localeCompare(&quot;apple&quot;); // 1
&quot;apple&quot;.localeCompare(&quot;apple&quot;); // 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localeCompare()는 문자열 정렬에 유용하게 사용하고 locale을 고려하여 문자열을 비교합니다. locale은 국가, 언어, 타자기, 날짜 형식, 숫자 형식 등의 정보들을 포함하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 미국과 영국의 영어는 각각 다른 지역 정보를 갖습니다. 그래서 같은 문자열이라도 localeCompare() 메서드를 사용하여 비교할 때 결과가 달라질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미국 영어와 영국 영어의 단어 정렬 순서는 다릅니다. &quot;colour&quot;와 &quot;color&quot;는 미국 영어에서는 같은 단어지만, 영국 영어에서는 다릅니다. localeCompare()를 사용하여 비교할 때, &quot;colour&quot;가 &quot;color&quot;보다 앞에 오는 경우가 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 미국은 소문자가 정렬순서 앞, 영국은 대문자가 정렬순서 앞.
var str1 = &quot;civic&quot;;
var str2 = &quot;Civic&quot;;

str1.localeCompare(str2, 'en-US'); // -1
str1.localeCompare(str2, 'en-GB'); // 1&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;slice&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;slice(start, end)&lt;/code&gt;&lt;/b&gt; : 문자열의 start 위치부터 end 위치 전까지의 문자열을 잘라내어 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello, World!&quot;;
str.slice(7, 13); // &quot;World&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;substring&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;substring(start, end)&lt;/code&gt;&lt;/b&gt; : 주어진 시작 인덱스와 끝 인덱스 사이의 문자열을 추출하여 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.substring(3,7); // &quot;lo W&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;replace&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;replace(str, replaceStr)&lt;/code&gt;&lt;/b&gt; : 문자열에서 searchValue를 replaceValue로 대체하여 새로운 문자열을 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.replace(&quot;World&quot;, &quot;javascript&quot;); // &quot;Hello javascript&quot;

let str = &quot;Hello World&quot;;
str.replace(/world/i, &quot;javascript&quot;); // &quot;Hello javascript&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;replaceAll&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;replaceAll(str, replaceStr)&lt;/code&gt;&lt;/b&gt; : 주어진 정규표현식 또는 텍스트에 해당하는 모든 문자열을 새 문자열로 교체합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;The quick brown fox jumps over the lazy dog.&quot;;
let replaced = str.replaceAll(&quot;o&quot;, &quot;*&quot;);
console.log(replaced);
// &quot;The quick br*wn f*x jumps *ver the lazy d*g.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;toLowerCase&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;toLowerCase()&lt;/code&gt;&lt;/b&gt; : 문자열을 소문자로 변환하여 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.toLowerCase(); // &quot;hello world&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;toLocaleLowerCase&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;toLocaleLowerCase()&lt;/code&gt;&lt;/b&gt; : 각 문자열에 해당하는 문자의 locale에 맞는 소문자로 변환하여 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toLowerCase()와의 차이점은, toLowerCase()는 문자열의 모든 문자를 영어의 소문자로 변환하여 반환하는 반면, toLocaleLowerCase()는 문자열의 문자를 각 문자에 해당하는 locale의 소문자로 변환하여 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let str = &quot;HELLO WORLD&quot;;

str.toLocaleLowerCase();  // &quot;hello world&quot;
str.toLocaleLowerCase('de-DE');  // &quot;hello world&quot;
str.toLocaleLowerCase('tr-TR');  // &quot;merhaba d&amp;uuml;nya&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;toUpperCase&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;toUpperCase()&lt;/code&gt;&lt;/b&gt; : 문자열을 대문자로 변환하여 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.toUpperCase(); // &quot;HELLO WORLD&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;toLocaleUpperCase&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;toLocaleUpperCase()&lt;/code&gt;&lt;/b&gt; : 각 문자열에 해당하는 문자의 locale에 맞는 대문자로 변환하여 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toUpperCase()와의 차이점은, toUpperCase()는 문자열의 모든 문자를 영어의 대문자로 변환하여 반환하는 반면, toLocaleUpperCase()는 문자열의 문자를 각 문자에 해당하는 locale의 대문자로 변환하여 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let string = &quot;Hello World&quot;;
string.toUpperCase(); // HELLO WORLD
string.toLocaleUpperCase(&quot;en-GB&quot;); // HELLO WORLD&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;trim&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;trim()&lt;/code&gt;&lt;/b&gt; : 문자열의 앞뒤 공백을 제거하여 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;    Hello World    &quot;;
str.trim(); // &quot;Hello World&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;trimStart&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;trimStart()&lt;/code&gt;&lt;/b&gt; : 문자열 앞의 공백을 제거합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;   Hello World   &quot;;
str.trimStart(); // &quot;Hello World   &quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;trimEnd&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;trimEnd()&lt;/code&gt;&lt;/b&gt; : 문자열 뒤의 공백을 제거합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;   Hello World   &quot;;
str.trimEnd(); // &quot;   Hello World&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;split&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;split(separator, limit)&lt;/code&gt;&lt;/b&gt; : 문자열을 separator를 기준으로 분리하여 배열로 반환합니다. limit를 지정하면, 분리할 최대 개수를 지정할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.split(&quot; &quot;); // [&quot;Hello&quot;, &quot;World&quot;]
str.split(&quot;&quot;, 3); // [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;repeat&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;repeat(count)&lt;/code&gt;&lt;/b&gt; : 문자열을 입력받은 count 만큼 반복하여 새로운 문자열을 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello&quot;;
str.repeat(3); // &quot;HelloHelloHello&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;includes&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;includes(searchString, start)&lt;/code&gt;&lt;/b&gt; : 문자열에 searchString이 포함되어 있는지 검사하여 포함되어 있으면 true, 아니면 false를 반환합니다. start를 지정하면, 검색을 시작할 인덱스를 지정 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.includes(&quot;World&quot;); // true
str.includes(&quot;world&quot;); // false
str.includes(&quot;World&quot;,6); // true&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;startsWith&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;startsWith(searchString, length)&lt;/code&gt;&lt;/b&gt; : 문자열이 지정한 문자열로 시작하는지 확인합니다. 문자열이 지정한 문자열로 시작하면 true를, 아니면 false를 반환합니다. length를 지정하면, 검색을 시작할 인덱스를 지정 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let string = &quot;Hello World&quot;;
string.startsWith(&quot;Hello&quot;); // true
string.startsWith(&quot;HELLO&quot;); // false
string.startsWith(&quot;o&quot;, 4);  // true
string.startsWith(&quot;o&quot;, 5);  // false&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;endsWith&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;endsWith(searchString, length)&lt;/code&gt;&lt;/b&gt; : 문자열이 searchString으로 끝나는지 검사하여 끝나면 true, 아니면 false를 반환합니다. length를 지정하면, 검색을 시작할 인덱스를 지정 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.endsWith(&quot;World&quot;); // true
str.endsWith(&quot;world&quot;); // false
str.endsWith(&quot;o&quot;, 8); // true&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;match&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;match(regex)&lt;/code&gt;&lt;/b&gt; : 문자열에서 정규식과 일치하는 부분을 배열로 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.match(/[a-z]/g); // [&quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;, &quot;o&quot;, &quot;r&quot;, &quot;l&quot;, &quot;d&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;matchAll&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;matchAll(regex)&lt;/code&gt;&lt;/b&gt; : 문자열에서 주어진 정규식에 대한 모든 결과를 찾아 배열로 반환합니다. 이 객체의 각 요소는 매치 결과 객체이며, 패턴과 그에 대한 인덱스, 원본 문자열에 대한 정보를 포함합니다.&lt;/p&gt;
&lt;pre class=&quot;smalltalk&quot;&gt;&lt;code&gt;let str = &quot;Hello World. How are you today?&quot;;
let regex = /\b\w+\b/g;
let matchArray = [...str.matchAll(regex)];
console.log(matchArray);
/*
[
  Array(3) [&quot;Hello&quot;, index: 0, input: &quot;Hello World. How are you today?&quot;],
  Array(3) [ &quot;World&quot;, index: 6, input: &quot;Hello World. How are you today?&quot;],
  Array(3) [ &quot;How&quot;, index: 12, input: &quot;Hello World. How are you today?&quot;],
  Array(3) [ &quot;are&quot;, index: 16, input: &quot;Hello World. How are you today?&quot;],
  Array(3) [ &quot;you&quot;, index: 20, input: &quot;Hello World. How are you today?&quot;],
  Array(3) [ &quot;today&quot;, index: 24, input: &quot;Hello World. How are you today?&quot;]
]
*/&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;padStart&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;padStart(targetLength, padString)&lt;/code&gt;&lt;/b&gt; : 문자열의 시작 부분에 padString을 추가하여 targetLength만큼의 길이를 갖도록 합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.padStart(15, &quot;hi &quot;); // &quot;hi hi Hello World&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;padEnd&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;padEnd(targetLength, padString)&lt;/code&gt;&lt;/b&gt; : 문자열의 끝부분에 padString을 추가하여 targetLength만큼의 길이를 갖도록 합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;Hello World&quot;;
str.padEnd(15, &quot; hi&quot;); // &quot;Hello World hi hi&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;normalize&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;normalize()&lt;/code&gt;&lt;/b&gt; : 문자열을 Unicode 정규화 형식으로 변환합니다.&lt;/p&gt;
&lt;pre class=&quot;openscad&quot;&gt;&lt;code&gt;let str = &quot;&amp;Aring;&quot;;
str.normalize(); // &quot;A\\u030a&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/Javascript</category>
      <category>javascript</category>
      <category>string</category>
      <category>문자열</category>
      <category>자바스크립트</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/127</guid>
      <comments>https://wooncloud.tistory.com/127#entry127comment</comments>
      <pubDate>Sun, 12 Feb 2023 00:14:15 +0900</pubDate>
    </item>
    <item>
      <title>이제야 쓰는 22년 회고회고회고</title>
      <link>https://wooncloud.tistory.com/126</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;22년의 시작&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;21년 10월에 마드라스체크에 입사하면서 플로우 SaaS 개발자가 되었습니다. 플로우를 개발하면서 플로우에 대해 많이 배워가고 22년이 되면서 수습을 마무리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 플로우 찐가족!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일을 하면서 모르는 것이 많았지만, 그중 저에게 가장 필요한 게 뭘까 고민했습니다. 그러니 가장 먼저 나온 생각이 하나 있었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나는 자바스크립트를 잘 모르는구나! 공부해야겠다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 플로우에서 gulp.js를 도입하여 자바스크립트 ES5를 지양하고 ES6를 사용하고 있던 중이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이전 회사에서 ES6를 조금이나마 썼지만, 도대체 어디까지가 ES5이고 어디까지 ES6인지도 몰랐죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;플로우 개발자는 어차피 풀스택인 거, 제대로 알고 써야겠다!&quot; 그래서 공부하기 시작했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0JwU0/btrW387UGxA/DmREcsVReYP2k0a8XjsGnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0JwU0/btrW387UGxA/DmREcsVReYP2k0a8XjsGnK/img.png&quot; data-alt=&quot;공부하면서 적었던 포스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0JwU0/btrW387UGxA/DmREcsVReYP2k0a8XjsGnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0JwU0%2FbtrW387UGxA%2FDmREcsVReYP2k0a8XjsGnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;공부하면서 적었던 포스트&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;352&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공부하면서 적었던 포스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음은 뭐부터 시작할지 모르겠고, 습관도 안 들어있어서 &quot;노마드코더&quot; 강의를 보면서 조금씩 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 강의를 들으니, 조금 JS를 알게 되면서 더욱 모르는 게 많다고 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 JS에 익숙하지 않으니, 실전으로 익혀야겠다는 생각이 들기도 했습니다. 그래서 플로우에 개발 중인 하나의 기능을 제이쿼리를 사용하지 않고 오직 바닐라 JS를 사용해 개발해 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 JS에 조금씩 익숙해져 갔습니다. 이제는 자바보다 자바스크립트가 저의 주력 언어가 되었다고 과언이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;플로우 해커톤 2등!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22년 초, 회사에서 상금을 건 해커톤이 개최되어 주말 시간을 내서 열심히 플로우의 기능을 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;(해커톤이라 쓰고 기능현상금이라고 읽는다.)&amp;nbsp;&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 만든 기능은 GIPHY 연동입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/95DRB/btrWU7JdM3k/aftF694unEYxBGEFxPflFK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/95DRB/btrWU7JdM3k/aftF694unEYxBGEFxPflFK/img.jpg&quot; data-alt=&quot;그 당시 만들었던 기능 소개 슬로건&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/95DRB/btrWU7JdM3k/aftF694unEYxBGEFxPflFK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F95DRB%2FbtrWU7JdM3k%2FaftF694unEYxBGEFxPflFK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;그 당시 만들었던 기능 소개 슬로건&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;360&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그 당시 만들었던 기능 소개 슬로건&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nCYJz/btrW4FdBkGn/cpoMiD57N6GdJHgPljPes0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nCYJz/btrW4FdBkGn/cpoMiD57N6GdJHgPljPes0/img.png&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;623&quot; data-is-animation=&quot;false&quot; width=&quot;360&quot; height=&quot;274&quot; style=&quot;width: 32.1543%; margin-right: 10px;&quot; data-widthpercent=&quot;32.92&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nCYJz/btrW4FdBkGn/cpoMiD57N6GdJHgPljPes0/img.png&quot; alt=&quot;flow Giphy 1&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnCYJz%2FbtrW4FdBkGn%2FcpoMiD57N6GdJHgPljPes0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;623&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba4mUE/btrW0P16owY/ph0LJEsajyPDgg4WkBA2pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba4mUE/btrW0P16owY/ph0LJEsajyPDgg4WkBA2pK/img.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;618&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.0191%; margin-right: 10px;&quot; data-widthpercent=&quot;32.78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba4mUE/btrW0P16owY/ph0LJEsajyPDgg4WkBA2pK/img.png&quot; alt=&quot;flow Giphy 2&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba4mUE%2FbtrW0P16owY%2Fph0LJEsajyPDgg4WkBA2pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu6rhP/btrW37HYpf3/z6ImmHQAteEnc6inKxrD2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu6rhP/btrW37HYpf3/z6ImmHQAteEnc6inKxrD2k/img.png&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;649&quot; data-is-animation=&quot;false&quot; style=&quot;width: 33.501%;&quot; data-widthpercent=&quot;34.3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu6rhP/btrW37HYpf3/z6ImmHQAteEnc6inKxrD2k/img.png&quot; alt=&quot;flow Giphy 3&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu6rhP%2FbtrW37HYpf3%2Fz6ImmHQAteEnc6inKxrD2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;890&quot; height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;슬랙이나 디스코드에서 봤던 Giphy 기능&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 기능을 만들어서 직원들의 투표를 받았는데, 결과는 2등!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들어진 기능들은 플로우 실험실에 등록이 되는데, 사람들이 가장 많이 사용하는 사랑받는 기능이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤나 이모티콘이나 표현에 니즈가 필요했던 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;팀원들 캐리커쳐 그려주기!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 그림 그리는 개발자입니다. 오히려 개발경력보다 그림 경력이 더 길다고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22년이 되면서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;팀원들의 캐리커쳐&lt;/b&gt;&lt;/span&gt; (사실은 SD캐릭화)를 그려주게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그려줬는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22년이 되며 팀장님과 면담이 있었는데, 팀장님이 저에게 뭔가 톡톡 튀는 아이디어가 있을 것 같다고 했습니다. 하지만 뭔가 그것을 들어내는 것을 꺼려하는 것 같은데 그 아이디어를 꺼내줬으면 좋겠다고 하셨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 살면서 괜히 나서면 피곤하다는 사실을 뼈저리게 느끼고 있지만, 그 말을 듣자 마음속 상자에서 &quot;&lt;b&gt;팀원들을 위한 캐리커쳐&lt;/b&gt;&quot;를 꺼내게 된 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b24kxT/btrW2tYLqlL/vvlvYFHDfaKrzruMZhmz7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b24kxT/btrW2tYLqlL/vvlvYFHDfaKrzruMZhmz7k/img.png&quot; data-alt=&quot;팀원들 캐리커쳐 중 본인을 그린 것.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b24kxT/btrW2tYLqlL/vvlvYFHDfaKrzruMZhmz7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb24kxT%2FbtrW2tYLqlL%2FvvlvYFHDfaKrzruMZhmz7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;내 캐리커쳐&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;360&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팀원들 캐리커쳐 중 본인을 그린 것.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 팀 한정해서 그림을 그려주고 있습니다. 팀원들을 사랑하는 마음이 크기 때문에 그림으로 모두가 돈독해졌으면 하는 마음을 가지고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;플로우 지식이 쌓여가고 있다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22년 동안 1년 계속 플로우를 개발하면서 내부 코드와 도메인 지식이 쌓여가고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여태 많은 프로젝트를 봤지만, 플로우는 엄청난 기능들이 쌓이고 모이면서 거대한 프로젝트가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 올인원 협업툴이라 수많은 기능이 탑재되어 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 플로우는 코드량이 많다고 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;The Little Things - Working.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xM6XR/btrWU8H9ulL/qPkRTqZDuVwXkpPROr9TKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xM6XR/btrWU8H9ulL/qPkRTqZDuVwXkpPROr9TKK/img.png&quot; data-alt=&quot;홀리.. 일하기 쉽지 않구만..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xM6XR/btrWU8H9ulL/qPkRTqZDuVwXkpPROr9TKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxM6XR%2FbtrWU8H9ulL%2FqPkRTqZDuVwXkpPROr9TKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;work pic 1&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;392&quot; data-filename=&quot;The Little Things - Working.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;홀리.. 일하기 쉽지 않구만..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나, 코드가 잘 정리되어 있는 편은 아닙니다. 레거시도 많이 있어서 수많은 코드와 데이터들을 알려면 경험을 쌓으면서 도메인지식을 늘려가야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱 1년 정도 개발하니 이제야 조금 알겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 이런 것 보면 이 회사는 직원들 케어를 잘해줘야 할 것 같다는 생각이 많이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(입사와 퇴사가 잦으면 레거시를 알려주는 사람이 없어져서 그런 생각이 들었습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;운쿠 개발&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22년 초기 js를 공부하면서 SPA에 대해 알아가고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 Vue를 공부하게 되면서 작은 토이프로젝트를 하고 싶다는 생각을 하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 눈여겨본 쿠팡파트너스를 이용하여 작은 용돈도 벌어보며 서비스를 하나 운영하고 싶다는 생각에 냅다 시작해 버렸죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 만들어진 사이트가 &lt;b&gt;운쿠&lt;/b&gt;였습니다. (자세한 내용은 아래 링크 참조)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://wooncloud.tistory.com/95&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1674571406225&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;운쿠 회고 - Vue, Nodejs로 만들어본 작은 쿠팡 파트너스 사이트&quot; data-og-description=&quot;1달간 작은 쿠팡 파트너스 사이트인 '운쿠'를 만들고 간단한 소개와 회고를 적어보려고 합니다. 회고 적는 것은 사실상 처음이라서 부족한 글이지만, 한번 적어보았습니다. 운쿠! 앞으로 제 역할&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/95&quot; data-og-url=&quot;https://wooncloud.tistory.com/95&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/blAmjz/hyRm5bCU8s/UUlKtYGXBZNDRkZz9TXy9K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/qq8Xu/hyRoFWGaSN/VOwXpPOqGK352ht0QpuIsk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/kAwLK/hyRnaxcYFi/7mLikxVmCYTWOgu1uLhbhk/img.png?width=1200&amp;amp;height=734&amp;amp;face=0_0_1200_734&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/95&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/blAmjz/hyRm5bCU8s/UUlKtYGXBZNDRkZz9TXy9K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/qq8Xu/hyRoFWGaSN/VOwXpPOqGK352ht0QpuIsk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/kAwLK/hyRnaxcYFi/7mLikxVmCYTWOgu1uLhbhk/img.png?width=1200&amp;amp;height=734&amp;amp;face=0_0_1200_734');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;운쿠 회고 - Vue, Nodejs로 만들어본 작은 쿠팡 파트너스 사이트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1달간 작은 쿠팡 파트너스 사이트인 '운쿠'를 만들고 간단한 소개와 회고를 적어보려고 합니다. 회고 적는 것은 사실상 처음이라서 부족한 글이지만, 한번 적어보았습니다. 운쿠! 앞으로 제 역할&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 운영은 대차게 말아먹었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면, 처음 만드는 거라 잘 몰랐습니다. 위의 사이트는 SEO를 고려해야 해서 SSR로 만들어야 했던 프로젝트인데, CSR로 만들었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에는 서버를 내렸습니다! 대신 공부는 많이 되어서 나름 의미 있는 일이었다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;친절한 SQL 튜닝 스터디&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS에 이어서 SQL 튜닝에 대해 모른다는 생각을 많이 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 뭔지도 잘 몰랐고, 파티셔닝, 옵티마이저 등 자세한 내용을 모르고 있었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 저와 같은 생각을 가진 동료분들이 여러 있으셔서, 함께 모여 친절한 SQL 튜닝 스터디가 만들어졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drQaAn/btrWWs7r50f/kipeuGtKPu5qyweMDTF0u1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drQaAn/btrWWs7r50f/kipeuGtKPu5qyweMDTF0u1/img.jpg&quot; data-alt=&quot;친절했지만 친절하지 않은 그 책&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drQaAn/btrWWs7r50f/kipeuGtKPu5qyweMDTF0u1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrQaAn%2FbtrWWs7r50f%2FkipeuGtKPu5qyweMDTF0u1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;친절한 SQL 튜닝&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;315&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;친절했지만 친절하지 않은 그 책&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스터디가 은근히 힘들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안 그래도 늦게 퇴근하는데, 집에 오자마자 조금 쉬다가 책 보고 공부하고 공부한 내용을 노션에 적어서 공유해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;795&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AVlk9/btrWTJISxAj/xyO3kyarjblgjhw4fqYq11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AVlk9/btrWTJISxAj/xyO3kyarjblgjhw4fqYq11/img.png&quot; data-alt=&quot;공부한 내용들..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AVlk9/btrWTJISxAj/xyO3kyarjblgjhw4fqYq11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAVlk9%2FbtrWTJISxAj%2FxyO3kyarjblgjhw4fqYq11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;친절한 SQL 튜닝 study history&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;795&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;795&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공부한 내용들..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 블로그에도 친절한 SQL 튜닝을 공부하고 올린 포스트가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부 결과는 DB에 대해 많이 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책은 오라클 위주로 되어있고, 회사에서는 PostgreSQL을 쓰지만, 전반적인 시야가 넓어진 느낌이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 실전으론 별로 써먹진 못했네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;도시부엉 그리기 시작&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운쿠 이후, 생각해 둔 다른 프로젝트 중 하나인 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;도시부엉 프로젝트&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 개발 쪽은 아니고, 이모티콘을 그려서 부업을 해볼까 하는 마음에 시작하게 된 인스타툰 퍼스널 브랜딩입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.instagram.com/dosiowl_official/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.instagram.com/dosiowl_official/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PrCfC/btrWS3Hknty/Ig25QnoPNSjl3C38hQ8rvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PrCfC/btrWS3Hknty/Ig25QnoPNSjl3C38hQ8rvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PrCfC/btrWS3Hknty/Ig25QnoPNSjl3C38hQ8rvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPrCfC%2FbtrWS3Hknty%2FIg25QnoPNSjl3C38hQ8rvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;dosiowl&quot; loading=&quot;lazy&quot; width=&quot;969&quot; height=&quot;881&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;881&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 다니느라 그림 그릴 시간은 별로 없고, 그래서 주말에 올라가거나 짬 내서 그림을 그리다 보니 성장은 좀 느립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 꾸준히 하면서 팔로워도 늘고 그림 그리는 것도 익숙해져서 점차 속도가 올라갈 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 진짜 그릴 시간만 좀 있었으면 좋겠네요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도시부엉 그리기에 대한 자세한 이야기는 따로 포스팅으로 다룰 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;플로우 테크 세미나&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SaaS팀 팀장님이 회사에서 개발 문화를 만들고자 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;플로우 테크 세미나&lt;/b&gt;&lt;/span&gt;를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 정보 공유가 목적이었는데, 갑자기 1회 발표당 상품권 5만원을 받을 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자낳괴인 저에게 이 문화를 참여해야 할 이유가 생기게 되었죠! &lt;s&gt;(처음 목적이 불순했던 것..)&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbWqt/btrW7UVDKJv/olfe2PDwO5uShEV5a0UtW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbWqt/btrW7UVDKJv/olfe2PDwO5uShEV5a0UtW0/img.png&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;297&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.1225%; margin-right: 10px;&quot; data-widthpercent=&quot;49.7&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbWqt/btrW7UVDKJv/olfe2PDwO5uShEV5a0UtW0/img.png&quot; alt=&quot;flow tech seminar 1&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlbWqt%2FbtrW7UVDKJv%2Folfe2PDwO5uShEV5a0UtW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8aCX1/btrW76IwK4n/vg8iIvnua0QLx51Wl8oAKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8aCX1/btrW76IwK4n/vg8iIvnua0QLx51Wl8oAKK/img.png&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;292&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.7147%;&quot; data-widthpercent=&quot;50.3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8aCX1/btrW76IwK4n/vg8iIvnua0QLx51Wl8oAKK/img.png&quot; alt=&quot;flow tech seminar 2&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8aCX1%2FbtrW76IwK4n%2Fvg8iIvnua0QLx51Wl8oAKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;599&quot; height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSFsdU/btrW2uclJ6m/Du9D4uLTU69zxEWWz58lx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSFsdU/btrW2uclJ6m/Du9D4uLTU69zxEWWz58lx0/img.png&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;295&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.1634%; margin-right: 10px;&quot; data-widthpercent=&quot;49.74&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSFsdU/btrW2uclJ6m/Du9D4uLTU69zxEWWz58lx0/img.png&quot; alt=&quot;flow tech seminar 3&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSFsdU%2FbtrW2uclJ6m%2FDu9D4uLTU69zxEWWz58lx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biM5g9/btrW0Pnt4OH/pzLGlv4SlH0RsjKGSlkfi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biM5g9/btrW0Pnt4OH/pzLGlv4SlH0RsjKGSlkfi0/img.png&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;291&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.26&quot; style=&quot;width: 49.6738%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biM5g9/btrW0Pnt4OH/pzLGlv4SlH0RsjKGSlkfi0/img.png&quot; alt=&quot;flow tech seminar 4&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiM5g9%2FbtrW0Pnt4OH%2FpzLGlv4SlH0RsjKGSlkfi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;그동안 발표 했던 기록들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 뭔가 아는 게 별로 없어서 &quot;처음에 어떤 것을 발표해 보지?&quot; 하고 생각하다가 열심히 연구한 QA 건을 발표했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 발표하는 게 어렵지 그다음은 괜찮다고 생각이 들었습니다. 그래서 또 도전하게 되었죠. 다음 발표는 다들 잘 모르면서도 중요하다고 생각한 &quot;정규식&quot;을 발표하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저에게 테크 세미나의 순기능은 5만원을 받는 것이 아니었습니다. 발표 내용을 준비하면서 공부를 많이 하게 되어 그 분야의 전문성을 가지게 되는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규식 발표를 준비할 때도, 저는 정규식을 잘 몰라서 찾아보며 발표를 준비했죠. 그 과정을 통해 굉장히 많은 것을 알게 되었습니다. 그리고 회사에서 정규식을 잘 아는 사람 중 한 명으로 거듭나게 되었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 2번째 발표 이후, 저의 성장을 위해 발표하게 되었습니다. (5만원은 겸사겸사)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;행동하는 사람에게 기회와 성장이 온다는 말이 정말 맞는 것 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회고를 쓰면서, 생각보다 22년에 많은 것을 했다는 생각이 새삼 느끼게 되었습니다. 그동안 이렇게 알차게 한해를 살아온 적이 잘 없었는데, 열심히 살고 이렇게 되돌아보니 뿌듯합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이렇게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;매년 알차게 한 해를 살자고 다짐하면서 이번 회고를 마무리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두들 새해 복 많이 받으세요. 파이팅!&lt;/p&gt;</description>
      <category>이야기/개발일지</category>
      <category>회고</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/126</guid>
      <comments>https://wooncloud.tistory.com/126#entry126comment</comments>
      <pubDate>Wed, 25 Jan 2023 00:48:17 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] 틀에 맞추는 스크롤 스냅 만들기 - Scroll Snap</title>
      <link>https://wooncloud.tistory.com/125</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;scroll snap.gif&quot; data-origin-width=&quot;309&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mUito/btrWeGKRtQF/COpl5LUOacphwZaNYKltmk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mUito/btrWeGKRtQF/COpl5LUOacphwZaNYKltmk/img.gif&quot; data-alt=&quot;스크롤 스냅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mUito/btrWeGKRtQF/COpl5LUOacphwZaNYKltmk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/mUito/btrWeGKRtQF/COpl5LUOacphwZaNYKltmk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Scroll Snap&quot; loading=&quot;lazy&quot; width=&quot;309&quot; height=&quot;203&quot; data-filename=&quot;scroll snap.gif&quot; data-origin-width=&quot;309&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스크롤 스냅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 애니메이션처럼 스크롤 하면 틀에 맞게 스크롤 되는 것을 스크롤 스냅 (Scroll Snap) 이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스크롤 스냅도 자바스크립트 없이 오직 CSS로 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1673619796760&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.scroll-box.horizon {
    flex-direction: row;
    overflow: auto hidden;
    scroll-snap-type: x mandatory;
}

.scroll-box.vertical {
    flex-direction: column;
    overflow: hidden auto;
    scroll-snap-type: y mandatory; 
}

.item {
    scroll-snap-align: start;
    min-width: 100%;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드중 .scroll-box가 부모 element이며 .item이 자식 element입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 부모 element는 'scroll-snap-type' 속성을 가지고 있어야 함.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scroll-snap-type은 어느 방향으로 스냅을 줄지, 어떤 타입으로 스냅을 표현할 지를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 mandatory 값을 넣었는데, 이는 스크롤이 끝날 때 가까이 있는 스냅 지점까지 보정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 자식 element는 'scroll-snap-align' 속성을 가지고 있어야 함.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scroll-snap-align은 스냅 위치를 정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 start, end, center 가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- start는 해당 요소의 부모 컨테이너의 가장 왼쪽 (시작지점)을 스냅 축으로 정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- end는 해당 요소의 부모 컨테이너의 가장 오른쪽 (끝지점)을 스냅 축으로 정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;center&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;는 해당 요소의 부모 컨테이너의 중앙을 스냅 축으로 정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 706px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;706&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;eYjRGLJ&quot; data-user=&quot;wooncloud&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/wooncloud/pen/eYjRGLJ&quot;&gt; Untitled&lt;/a&gt; by wooncloud (&lt;a href=&quot;https://codepen.io/wooncloud&quot;&gt;@wooncloud&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1673620192301&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;scroll-snap-type - CSS: Cascading Style Sheets | MDN&quot; data-og-description=&quot;The scroll-snap-type CSS property sets how strictly snap points are enforced on the scroll container in case there is one.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type&quot; data-og-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;scroll-snap-type - CSS: Cascading Style Sheets | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The scroll-snap-type CSS property sets how strictly snap points are enforced on the scroll container in case there is one.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-align&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-align&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1673620202474&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;scroll-snap-align - CSS: Cascading Style Sheets | MDN&quot; data-og-description=&quot;The scroll-snap-align property specifies the box's snap position as an alignment of its snap area (as the alignment subject) within its snap container's snapport (as the alignment container). The two values specify the snapping alignment in the block axis &quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-align&quot; data-og-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-align&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-align&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-align&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;scroll-snap-align - CSS: Cascading Style Sheets | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The scroll-snap-align property specifies the box's snap position as an alignment of its snap area (as the alignment subject) within its snap container's snapport (as the alignment container). The two values specify the snapping alignment in the block axis&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/HTML, CSS</category>
      <category>CSS</category>
      <category>Scroll Snap</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/125</guid>
      <comments>https://wooncloud.tistory.com/125#entry125comment</comments>
      <pubDate>Fri, 13 Jan 2023 23:36:43 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] 문장 맨 앞 글자 크게 만들기 - Drop Cap 만들기</title>
      <link>https://wooncloud.tistory.com/124</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMQF2S/btrV7eusUVY/1MKIKR92mZ0SFAYiicQ8S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMQF2S/btrV7eusUVY/1MKIKR92mZ0SFAYiicQ8S1/img.png&quot; data-alt=&quot;Drop Cap&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMQF2S/btrV7eusUVY/1MKIKR92mZ0SFAYiicQ8S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMQF2S%2FbtrV7eusUVY%2F1MKIKR92mZ0SFAYiicQ8S1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;487&quot; height=&quot;322&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Drop Cap&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 문단 가장 앞의 글자를 크게 써 두는 것을 Drop Cap 이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS에서 위와 같은 Drop Cap을 만드는 방법은 아주 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673537396779&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.drop-cap-text::first-letter {
  margin-right: 10px;
  font-size: xx-large;
  font-weight: bold;
  float: left;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스명 뒤에 ::first-letter를 넣는 것이 핵심입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 맨 앞의 글자 하나만 CSS 속성의 대상이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;MWBmRLR&quot; data-user=&quot;wooncloud&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/wooncloud/pen/MWBmRLR&quot;&gt; drop cap&lt;/a&gt; by wooncloud (&lt;a href=&quot;https://codepen.io/wooncloud&quot;&gt;@wooncloud&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;</description>
      <category>개발 아카이브/HTML, CSS</category>
      <category>CSS</category>
      <category>drop cap</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/124</guid>
      <comments>https://wooncloud.tistory.com/124#entry124comment</comments>
      <pubDate>Fri, 13 Jan 2023 00:36:06 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] 흑백 효과 만들기 - GrayScale</title>
      <link>https://wooncloud.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사진을 일부러 흑백사진으로 만들지 않아도 CSS를 이용하여 흑백사진으로 만들 수 있는 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 속성 중 filter 속성이 있고, 이 속성에 grayscale을 조절할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;grayscale을 100%로 하면 완전한 흑백사진으로 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1673453977865&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.gray-scale {
  filter: grayscale(100%);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;VwBbKEp&quot; data-user=&quot;wooncloud&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/wooncloud/pen/VwBbKEp&quot;&gt; Grayscale&lt;/a&gt; by wooncloud (&lt;a href=&quot;https://codepen.io/wooncloud&quot;&gt;@wooncloud&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;</description>
      <category>개발 아카이브/HTML, CSS</category>
      <category>GRAYSCALE</category>
      <category>흑백효과</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/123</guid>
      <comments>https://wooncloud.tistory.com/123#entry123comment</comments>
      <pubDate>Thu, 12 Jan 2023 01:22:38 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] 잘라낸 텍스트 만들기 / Cut out Text</title>
      <link>https://wooncloud.tistory.com/122</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SYYgX/btrVUzFUzEL/xJyzY2YHwa2ZzjYyQCbBp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SYYgX/btrVUzFUzEL/xJyzY2YHwa2ZzjYyQCbBp1/img.png&quot; data-alt=&quot;Cut out Text&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SYYgX/btrVUzFUzEL/xJyzY2YHwa2ZzjYyQCbBp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSYYgX%2FbtrVUzFUzEL%2FxJyzY2YHwa2ZzjYyQCbBp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Cut out Text&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;318&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cut out Text&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사진과 같은 텍스트를 컷아웃(Cut out) 텍스트라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS로 위와 같은 효과를 만드는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1673361593882&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.cut-out-text {
  mix-blend-mode: screen;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한건 mix-blend-mode: screen; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트에 배경을 믹스할 때 이러한 속성을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;screen은 이미지 합성 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포토샵을 잘 다루고 익숙하신 분들은 아시겠지만, 속성이 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 차후 다루겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그라디언트 배경&lt;/b&gt;&lt;/h4&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;500&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;jOpBzzG&quot; data-user=&quot;wooncloud&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/wooncloud/pen/jOpBzzG&quot;&gt; Cut Out Text&lt;/a&gt; by wooncloud (&lt;a href=&quot;https://codepen.io/wooncloud&quot;&gt;@wooncloud&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;사진 배경&lt;/b&gt;&lt;/h4&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;500&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;YzjZaBy&quot; data-user=&quot;wooncloud&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/wooncloud/pen/YzjZaBy&quot;&gt; Cut Out Text2&lt;/a&gt; by wooncloud (&lt;a href=&quot;https://codepen.io/wooncloud&quot;&gt;@wooncloud&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;</description>
      <category>개발 아카이브/HTML, CSS</category>
      <category>CSS</category>
      <category>Cut out</category>
      <category>컷아웃</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/122</guid>
      <comments>https://wooncloud.tistory.com/122#entry122comment</comments>
      <pubDate>Tue, 10 Jan 2023 23:58:18 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] background에 fade color 넣기</title>
      <link>https://wooncloud.tistory.com/121</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beibEE/btrVP8OLWjK/mEMvTQkTA6W4Pafg39WjcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beibEE/btrVP8OLWjK/mEMvTQkTA6W4Pafg39WjcK/img.png&quot; data-alt=&quot;fade color&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beibEE/btrVP8OLWjK/mEMvTQkTA6W4Pafg39WjcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeibEE%2FbtrVP8OLWjK%2FmEMvTQkTA6W4Pafg39WjcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;fade color&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;479&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;fade color&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 CSS 코드는 이렇게 카드에 배경화면을 넣고 linear-gradient를 넣어 페이드 효과를 주는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;background shortcut을 사용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 그라디언트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이미지 url&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서로 넣어서 효과를 완성할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ymnAX/btrVOzTKdz8/NrHpW1FRrDyRR0XOjygZ31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ymnAX/btrVOzTKdz8/NrHpW1FRrDyRR0XOjygZ31/img.png&quot; data-alt=&quot;그라디언트 방향&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ymnAX/btrVOzTKdz8/NrHpW1FRrDyRR0XOjygZ31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FymnAX%2FbtrVOzTKdz8%2FNrHpW1FRrDyRR0XOjygZ31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;css gradient 1&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;444&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그라디언트 방향&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1673278149990&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.card-fade-bg-right {
  background: linear-gradient(to right, #000F, #0000), url('image url');
}

.card-fade-bg-left {
  background: linear-gradient(to left, #000F, #0000), url('image url');
}

.card-fade-bg-top {
  background: linear-gradient(to top, #000F, #0000), url('image url');
}

.card-fade-bg-bottom {
  background: linear-gradient(to bottom, #000F, #0000), url('image url');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cudVSl/btrVLuFH0Lr/T1MBrJ2n2OxlEOjd0CcQU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cudVSl/btrVLuFH0Lr/T1MBrJ2n2OxlEOjd0CcQU0/img.png&quot; data-alt=&quot;그라디언트 컬러&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cudVSl/btrVLuFH0Lr/T1MBrJ2n2OxlEOjd0CcQU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcudVSl%2FbtrVLuFH0Lr%2FT1MBrJ2n2OxlEOjd0CcQU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;css gradient 2&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;173&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그라디언트 컬러&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1673278140490&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.card-fade-bg-green {
  background: linear-gradient(to right, #000F, green), url('image url');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/h2&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 584px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;584&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;mdjRZZW&quot; data-user=&quot;wooncloud&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/wooncloud/pen/mdjRZZW&quot;&gt; Untitled&lt;/a&gt; by wooncloud (&lt;a href=&quot;https://codepen.io/wooncloud&quot;&gt;@wooncloud&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;script src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;PS&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 카드 효과를 내는 CSS는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1673278169210&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.card {
  font-size: xx-large;
  width: 400px;
  height: 200px;
  border-radius: 20px;
  border: 1px solid lightgray;
  padding: 15px;
  color: white;
  margin: 10px 0;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발 아카이브/HTML, CSS</category>
      <category>CSS</category>
      <category>Fade</category>
      <category>Gradient</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/121</guid>
      <comments>https://wooncloud.tistory.com/121#entry121comment</comments>
      <pubDate>Tue, 10 Jan 2023 00:32:47 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] 긴 글자를 Fade Out으로 숨기는 CSS</title>
      <link>https://wooncloud.tistory.com/120</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;36&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BbmZb/btrVzKoSSyc/yqYqN5FrKBUHHG4YDu0mKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BbmZb/btrVzKoSSyc/yqYqN5FrKBUHHG4YDu0mKK/img.png&quot; data-alt=&quot;이렇게 긴 글자를 갈수록 페이드 아웃해서 숨기는 css 입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BbmZb/btrVzKoSSyc/yqYqN5FrKBUHHG4YDu0mKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBbmZb%2FbtrVzKoSSyc%2FyqYqN5FrKBUHHG4YDu0mKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Fade Out text&quot; loading=&quot;lazy&quot; width=&quot;472&quot; height=&quot;36&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;36&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게 긴 글자를 갈수록 페이드 아웃해서 숨기는 css 입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;489&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/snFZm/btrVBc50bMm/oGx2rK1MCzqkAubBiB9KQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/snFZm/btrVBc50bMm/oGx2rK1MCzqkAubBiB9KQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/snFZm/btrVBc50bMm/oGx2rK1MCzqkAubBiB9KQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsnFZm%2FbtrVBc50bMm%2FoGx2rK1MCzqkAubBiB9KQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;css Fade Out text&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;489&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;489&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;LYBxzXM&quot; data-user=&quot;wooncloud&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/wooncloud/pen/LYBxzXM&quot;&gt; long text fade out&lt;/a&gt; by wooncloud (&lt;a href=&quot;https://codepen.io/wooncloud&quot;&gt;@wooncloud&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;script src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/HTML, CSS</category>
      <category>CSS</category>
      <category>긴글자</category>
      <category>페이드아웃</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/120</guid>
      <comments>https://wooncloud.tistory.com/120#entry120comment</comments>
      <pubDate>Mon, 9 Jan 2023 00:32:37 +0900</pubDate>
    </item>
    <item>
      <title>코드 예쁘게 이미지화 해주는 사이트 모음</title>
      <link>https://wooncloud.tistory.com/119</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTBEij/btrVEpKiMaH/fEtmtKCkgPaHKznbFi5i8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTBEij/btrVEpKiMaH/fEtmtKCkgPaHKznbFi5i8k/img.png&quot; data-alt=&quot;코드를 이미지로 만들어보자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTBEij/btrVEpKiMaH/fEtmtKCkgPaHKznbFi5i8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTBEij%2FbtrVEpKiMaH%2FfEtmtKCkgPaHKznbFi5i8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;code to picture 1&quot; loading=&quot;lazy&quot; width=&quot;1114&quot; height=&quot;648&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코드를 이미지로 만들어보자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/115&quot;&gt;코드를 예쁘게 이미지로 만들어주는 툴 - carbon.now.sh &amp;mdash; Wooncloud Blog (tistory.com)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1673187554282&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;코드를 예쁘게 이미지로 만들어주는 툴 - carbon.now.sh&quot; data-og-description=&quot;제가 여러분께 보여드릴 코드가 있습니다. 아래에 그 코드를 공유하니 한번 봐주시기 바랍니다. const pluckDeep = key =&amp;gt; obj =&amp;gt; key.split('.').reduce((accum, key) =&amp;gt; accum[key], obj) const compose = (...fns) =&amp;gt; res =&amp;gt; fns.re&quot; data-og-host=&quot;wooncloud.tistory.com&quot; data-og-source-url=&quot;https://wooncloud.tistory.com/115&quot; data-og-url=&quot;https://wooncloud.tistory.com/115&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cXabcs/hyRcHu3G2e/MpVGuOj2PcceWJbscvZwSk/img.png?width=720&amp;amp;height=480&amp;amp;face=0_0_720_480,https://scrap.kakaocdn.net/dn/nDNS0/hyRdClla6S/Cvgz3z5wqle2ThjkVPe0mK/img.png?width=720&amp;amp;height=480&amp;amp;face=0_0_720_480,https://scrap.kakaocdn.net/dn/bCFOUF/hyRcOubb74/mQx6vKQnAkDmG01Zv8gVEk/img.png?width=973&amp;amp;height=799&amp;amp;face=0_0_973_799&quot;&gt;&lt;a href=&quot;https://wooncloud.tistory.com/115&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooncloud.tistory.com/115&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cXabcs/hyRcHu3G2e/MpVGuOj2PcceWJbscvZwSk/img.png?width=720&amp;amp;height=480&amp;amp;face=0_0_720_480,https://scrap.kakaocdn.net/dn/nDNS0/hyRdClla6S/Cvgz3z5wqle2ThjkVPe0mK/img.png?width=720&amp;amp;height=480&amp;amp;face=0_0_720_480,https://scrap.kakaocdn.net/dn/bCFOUF/hyRcOubb74/mQx6vKQnAkDmG01Zv8gVEk/img.png?width=973&amp;amp;height=799&amp;amp;face=0_0_973_799');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코드를 예쁘게 이미지로 만들어주는 툴 - carbon.now.sh&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;제가 여러분께 보여드릴 코드가 있습니다. 아래에 그 코드를 공유하니 한번 봐주시기 바랍니다. const pluckDeep = key =&amp;gt; obj =&amp;gt; key.split('.').reduce((accum, key) =&amp;gt; accum[key], obj) const compose = (...fns) =&amp;gt; res =&amp;gt; fns.re&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooncloud.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 위의 링크와 같은 내용으로 포스트를 쓴 적이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 carbon 사이트보다 좀 더 예쁘고 유용한 사이트들이 많아서, 이번에 코드를 이미지화 해주는 툴 모음으로 가져왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주저 없이 바로 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ray.so&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ray.so는 정말 심플하고 예쁜 툴입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;정말 다른 복잡한 기능 필요없고 코드만 딱 적어서 이미지로 추출하고 싶고, 예쁘면 딱이다.&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 생각을 가지고 있다면, 이 사이트를 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능이 단순해서 자세한 설명은 생략하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ray.so/&quot;&gt;Create beautiful images of your code (ray.so)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1673187696531&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Ray.so - Create beautiful images of your code&quot; data-og-description=&quot;Turn your code into beautiful images. Choose from a range of syntax colors, hide or show the background, and toggle between a dark and light window.&quot; data-og-host=&quot;www.ray.so&quot; data-og-source-url=&quot;https://www.ray.so/&quot; data-og-url=&quot;https://ray.so/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cr05fC/hyRdMO16Vo/DhkOYrasBOwAkx5svhipB0/img.png?width=1270&amp;amp;height=760&amp;amp;face=0_0_1270_760&quot;&gt;&lt;a href=&quot;https://www.ray.so/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.ray.so/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cr05fC/hyRdMO16Vo/DhkOYrasBOwAkx5svhipB0/img.png?width=1270&amp;amp;height=760&amp;amp;face=0_0_1270_760');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Ray.so - Create beautiful images of your code&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Turn your code into beautiful images. Choose from a range of syntax colors, hide or show the background, and toggle between a dark and light window.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.ray.so&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV6Hb4/btrVz9IEDte/433aedTnwdXjkITBEPQSnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV6Hb4/btrVz9IEDte/433aedTnwdXjkITBEPQSnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV6Hb4/btrVz9IEDte/433aedTnwdXjkITBEPQSnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV6Hb4%2FbtrVz9IEDte%2F433aedTnwdXjkITBEPQSnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;ray.so&quot; loading=&quot;lazy&quot; width=&quot;928&quot; height=&quot;909&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;snappify.com&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;snappify.com는 다양한 기능이 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 에디터와 대시보드, 템플릿 기능이 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 가장 마음에 들었던 것은, 블로그에 작성한 코드를 iframe으로 보여줄 수 있다는 점이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지로 블로그에 공유하면, 다른 사람들이 복사 붙여넣기를 하기 힘들다는 점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 snappify.com는 코드를 예쁘게 보여주면서, 복사할 수 있게 iframe으로 공유 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 일부 기능은 유료입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 많은 기능을 사용할 수 있고, 다양한 기능이 제공되기 때문에 아주 쓸만합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에디터에만 들어가도 엄청나게 많은 기능을 제공하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://snappify.com/&quot;&gt;snappify - Create beautiful code snippets with ease&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1673187811372&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;snappify - Create beautiful code snippets with ease&quot; data-og-description=&quot;A powerful design tool to create and manage beautiful images of your code.&quot; data-og-host=&quot;snappify.com&quot; data-og-source-url=&quot;https://snappify.com/&quot; data-og-url=&quot;https://snappify.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fg8lW/hyRcELQ55I/B3BuADpuIkhKbnK6NweW51/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627,https://scrap.kakaocdn.net/dn/bKs73Z/hyRdHfTByh/nMMghNSmVqV7LOjLfcdgx1/img.png?width=250&amp;amp;height=200&amp;amp;face=0_0_250_200&quot;&gt;&lt;a href=&quot;https://snappify.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://snappify.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fg8lW/hyRcELQ55I/B3BuADpuIkhKbnK6NweW51/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627,https://scrap.kakaocdn.net/dn/bKs73Z/hyRdHfTByh/nMMghNSmVqV7LOjLfcdgx1/img.png?width=250&amp;amp;height=200&amp;amp;face=0_0_250_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;snappify - Create beautiful code snippets with ease&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A powerful design tool to create and manage beautiful images of your code.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;snappify.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;overflow: hidden; margin-left: auto; margin-right: auto; border-radius: 10px; width: 100%; max-width: 484px; position: relative;&quot;&gt;&lt;iframe src=&quot;https://snappify.com/embed/4db6f209-b31e-429d-b6d2-44d97a7617af?responsive&quot; width=&quot;484&quot; height=&quot;303&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 계정을 만들면 iframe으로 코드를 공유할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KVsR8/btrVF8O38QQ/OzuC9B8GoUBDo7elLO18y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KVsR8/btrVF8O38QQ/OzuC9B8GoUBDo7elLO18y1/img.png&quot; data-alt=&quot;코드를 작성할 수 있는 에디터. 엄청나게 많은 기능이 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KVsR8/btrVF8O38QQ/OzuC9B8GoUBDo7elLO18y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKVsR8%2FbtrVF8O38QQ%2FOzuC9B8GoUBDo7elLO18y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;snappify.com&quot; loading=&quot;lazy&quot; width=&quot;1918&quot; height=&quot;958&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코드를 작성할 수 있는 에디터. 엄청나게 많은 기능이 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1389&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Vm78/btrVEpwMksm/hgLg8PvkniyYZUNVOlLkO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Vm78/btrVEpwMksm/hgLg8PvkniyYZUNVOlLkO1/img.png&quot; data-alt=&quot;내가 쓴 코드를 다시 볼 수 있고 관리할 수 있는 대시보드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Vm78/btrVEpwMksm/hgLg8PvkniyYZUNVOlLkO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Vm78%2FbtrVEpwMksm%2FhgLg8PvkniyYZUNVOlLkO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;snappify.com 2&quot; loading=&quot;lazy&quot; width=&quot;1389&quot; height=&quot;725&quot; data-origin-width=&quot;1389&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내가 쓴 코드를 다시 볼 수 있고 관리할 수 있는 대시보드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v0cHi/btrVCeWx1Jz/osEWuT42SuQKUidu64v4ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v0cHi/btrVCeWx1Jz/osEWuT42SuQKUidu64v4ck/img.png&quot; data-alt=&quot;코드를 꾸며 줄 각종 템플릿들을 제공합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v0cHi/btrVCeWx1Jz/osEWuT42SuQKUidu64v4ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv0cHi%2FbtrVCeWx1Jz%2FosEWuT42SuQKUidu64v4ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;snappify.com 3&quot; loading=&quot;lazy&quot; width=&quot;1322&quot; height=&quot;715&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코드를 꾸며 줄 각종 템플릿들을 제공합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;codetoimg.com&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codetoimg.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codetoimg.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1673188895667&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://codetoimg.com/&quot; data-og-description=&quot;&quot; data-og-host=&quot;codetoimg.com&quot; data-og-source-url=&quot;https://codetoimg.com/&quot; data-og-url=&quot;https://codetoimg.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://codetoimg.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codetoimg.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;https://codetoimg.com/&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codetoimg.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;codetoimg 도 ray.so 처럼 심플한 기능에 예쁜 코드를 작성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 codetoimg는 반응형 css 개발이 좀 부족한 것 같습니다. 사용성에 좀 불편한 점이 있네요.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;034&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/034.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/034.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 ray.so를 쓸 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWYNe7/btrVBdqhnEQ/pYbDVtT3FvRKmg3Sorjq2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWYNe7/btrVBdqhnEQ/pYbDVtT3FvRKmg3Sorjq2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWYNe7/btrVBdqhnEQ/pYbDVtT3FvRKmg3Sorjq2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWYNe7%2FbtrVBdqhnEQ%2FpYbDVtT3FvRKmg3Sorjq2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;codetoimg.com&quot; loading=&quot;lazy&quot; width=&quot;926&quot; height=&quot;868&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;codekeep.io (비추천)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codekeep.io/home&quot;&gt;CodeKeep.io - Save &amp;amp; Organize code snippets&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1673189493519&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot; Save,  Share   Organize your code snippets with CodeKeep.&quot; data-og-description=&quot; Codekeep lets you store and share bits of code and text with other users. Snippets can be organized into folders/labels for instant reuse. We took the best parts from Google Keep and Github to Organize your code snippets.&quot; data-og-host=&quot;codekeep.io&quot; data-og-source-url=&quot;https://codekeep.io/home&quot; data-og-url=&quot;https://codekeep.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRRD67/hyRcJzDoWP/bHkZIzoAVLFyni7qiOBb1k/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/cbfjwg/hyRcHhwMWW/qhYkhBUB4zULlnktHYipbK/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot;&gt;&lt;a href=&quot;https://codekeep.io/home&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codekeep.io/home&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRRD67/hyRcJzDoWP/bHkZIzoAVLFyni7qiOBb1k/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/cbfjwg/hyRcHhwMWW/qhYkhBUB4zULlnktHYipbK/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt; Save,  Share  Organize your code snippets with CodeKeep.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; Codekeep lets you store and share bits of code and text with other users. Snippets can be organized into folders/labels for instant reuse. We took the best parts from Google Keep and Github to Organize your code snippets.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codekeep.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 인지도가 있어서 찾아 써봤는데, 개인적으로 제일 불만족스러운 사이트.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡다한 버그가 많고 사용성이 너무 불편해서 이번엔 비추천으로 소개해드립니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cte776/btrVBd4QVDg/mINm2WkDWpM7CGmh0j9tMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cte776/btrVBd4QVDg/mINm2WkDWpM7CGmh0j9tMK/img.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;949&quot; data-is-animation=&quot;false&quot; style=&quot;width: 75.0171%; margin-right: 10px;&quot; data-widthpercent=&quot;75.9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cte776/btrVBd4QVDg/mINm2WkDWpM7CGmh0j9tMK/img.png&quot; alt=&quot;codekeep.io 1&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcte776%2FbtrVBd4QVDg%2FmINm2WkDWpM7CGmh0j9tMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1796&quot; height=&quot;949&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzZRVq/btrVEqvFWpz/GS607Z3RjthIDgX1CKMuMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzZRVq/btrVEqvFWpz/GS607Z3RjthIDgX1CKMuMK/img.png&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;862&quot; data-is-animation=&quot;false&quot; style=&quot;width: 23.8201%;&quot; data-widthpercent=&quot;24.1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzZRVq/btrVEqvFWpz/GS607Z3RjthIDgX1CKMuMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzZRVq%2FbtrVEqvFWpz%2FGS607Z3RjthIDgX1CKMuMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz5WUd/btrVAuFRYFn/VgU7jKgewhpzpRKozHT7kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz5WUd/btrVAuFRYFn/VgU7jKgewhpzpRKozHT7kK/img.png&quot; data-alt=&quot;codekeep.io&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz5WUd/btrVAuFRYFn/VgU7jKgewhpzpRKozHT7kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz5WUd%2FbtrVAuFRYFn%2FVgU7jKgewhpzpRKozHT7kK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;codekeep.io 2&quot; loading=&quot;lazy&quot; width=&quot;1758&quot; height=&quot;900&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;codekeep.io&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 소개해 봤지만 결국 2가지로 나눠지는 것 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드를 이미지로 심플하고 예쁘게 변경하고 싶다면 ray.so&lt;/li&gt;
&lt;li&gt;내가 작성한 코드를 계속 보관하고 싶고, 다양한 기능을 사용하고 iframe으로 블로그에 공유하고 싶다면 &lt;a title=&quot;snappify.com&quot; href=&quot;http://snappify.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;snappify.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>정보/유용한 사이트</category>
      <category>ray.so</category>
      <category>snappify.com</category>
      <category>코드 이미지화</category>
      <category>코드공유</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/119</guid>
      <comments>https://wooncloud.tistory.com/119#entry119comment</comments>
      <pubDate>Mon, 9 Jan 2023 00:08:08 +0900</pubDate>
    </item>
    <item>
      <title>정규식을 이용한 공격 - ReDos</title>
      <link>https://wooncloud.tistory.com/118</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;redos.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zb1w0/btrUum9I8VA/oE5mW0DHIAoOcTPKfqRvwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zb1w0/btrUum9I8VA/oE5mW0DHIAoOcTPKfqRvwk/img.png&quot; data-alt=&quot;redos title&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zb1w0/btrUum9I8VA/oE5mW0DHIAoOcTPKfqRvwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZb1w0%2FbtrUum9I8VA%2FoE5mW0DHIAoOcTPKfqRvwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;redos title&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;redos.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;redos title&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정규식을&amp;nbsp;이용한&amp;nbsp;공격&amp;nbsp;ReDos&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReDOS는 정규표현식을 사용자로부터 입력받았을 때 발생할 수 있는 보안 문제입니다. 특정 정규식 패턴은 입력값을 평가하는데 오래 걸립니다. ReDos는 이를 이용해 정규식 평가에 오랜 시간이 걸리게 하는 알고리즘 복잡성 공격입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타깃이 되는 정규식 로직은 자체적으로 반복되는 그룹이 주원인이며, 이 정규식에 어느 정도 만족하는 대량의 문자 등을 입력하여 가용성이 떨어지게 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReDos 원인은 정규식을 평가하는 엔진이 Backtracking을 사용하는 엔진이라면 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 NFA (Nondeterministic Finite Automaton) 엔진이 그 원인입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NFA 방식은 JAVA, javascript, .NET, PHP, Perl, Python, Ruby 등 광범위하게 사용되고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;redos1.gif&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zte3e/btrUupZyZIE/thqL8yVKxFxO8Xtbxpeb6k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zte3e/btrUupZyZIE/thqL8yVKxFxO8Xtbxpeb6k/img.gif&quot; data-alt=&quot;Catastrophic Backtracking이 일어나는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zte3e/btrUupZyZIE/thqL8yVKxFxO8Xtbxpeb6k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Zte3e/btrUupZyZIE/thqL8yVKxFxO8Xtbxpeb6k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Catastrophic Backtracking&quot; loading=&quot;lazy&quot; width=&quot;605&quot; height=&quot;300&quot; data-filename=&quot;redos1.gif&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Catastrophic Backtracking이 일어나는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Catastrophic Backtracking&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규식에는 두가지 알고리즘이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Deterministic Finite Automaton (DFA):&lt;/b&gt; 문자열의 문자를 한 번만 확인한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Nondeterministic Finite Automaton (NFA)&lt;/b&gt;: 최적의 일치를 찾을 때까지 여러 번 확인한다. 이런 여러 번 확인하는 NFA의 동작으로 인해 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;Catastrophic Backtracking&lt;/b&gt;&lt;/span&gt;이 일어날 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ReDos에 취약한 곳&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nodejs&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReDos공격을 이용하여 nodejs 서버를 공격할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js 런타임 아키텍처는 싱글 스레드 이벤트 루프를 구현합니다. 싱글 스레드 이벤트 루프 아키텍처는 확장성이 매우 높지만, 하나의 함수 실행에 오랜 시간이 걸리면 전체 프로세스를 중단시키기 때문에 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 Node.js가 ReDoS 취약점에 크게 영향을 받는 이유입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Moment.js&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.15.2이하 버전의 Moment.js는 ReDos 취약성이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://snyk.io/test/npm/moment/2.15.2&quot;&gt;https://snyk.io/test/npm/moment/2.15.2&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671887990543&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Snyk Vulnerability Database | Snyk&quot; data-og-description=&quot;The most comprehensive, accurate, and timely database for open source vulnerabilities.&quot; data-og-host=&quot;security.snyk.io&quot; data-og-source-url=&quot;https://snyk.io/test/npm/moment/2.15.2&quot; data-og-url=&quot;https://security.snyk.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VX0Xa/hyQZF5qkya/hDaSMLYM26zUxohchWw8b1/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600&quot;&gt;&lt;a href=&quot;https://snyk.io/test/npm/moment/2.15.2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://snyk.io/test/npm/moment/2.15.2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VX0Xa/hyQZF5qkya/hDaSMLYM26zUxohchWw8b1/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Snyk Vulnerability Database | Snyk&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The most comprehensive, accurate, and timely database for open source vulnerabilities.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;security.snyk.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 Moment.js가 날짜 구문을 검사하기 위해 사용했던 취약한 정규식입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/D[oD]?(\\[[^\\[\\]]*\\]|\\s+)+MMMM?/&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 + 구문을 많이 사용한 것입니다. 보통 그룹 내 + 구문이 있는데 그 그룹 밖에 또 + 구문이 있으면 취약한 정규식이 됩니다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;var moment = require('moment')
moment.locale('be')
moment().format('D                               MMN MMMM')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;889&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pFZHO/btrUzln5xzi/swSRkGhKssX8tBpHkSjET1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pFZHO/btrUzln5xzi/swSRkGhKssX8tBpHkSjET1/img.png&quot; data-alt=&quot;142843회 스탭을 검사했지만 정규식 매칭에 실패한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pFZHO/btrUzln5xzi/swSRkGhKssX8tBpHkSjET1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpFZHO%2FbtrUzln5xzi%2FswSRkGhKssX8tBpHkSjET1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Catastrophic Backtracking 2&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;889&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;889&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;142843회 스탭을 검사했지만 정규식 매칭에 실패한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스택 오버플로우가 멈춘 사례&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2016년에 Stack Overflow는 34분 동안 다운되었습니다.&amp;nbsp;다음과 같은 정규식을 사용했기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;^[\\s\\u200c]+|[\\s\\u200c]+$&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHpO7A/btrUtKKgjDA/1cuyrIKhTBKcgVE364Ir9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHpO7A/btrUtKKgjDA/1cuyrIKhTBKcgVE364Ir9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHpO7A/btrUtKKgjDA/1cuyrIKhTBKcgVE364Ir9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHpO7A%2FbtrUtKKgjDA%2F1cuyrIKhTBKcgVE364Ir9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Stack Overflow redos&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;458&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 정규식은 잘못된 형식의 게시물에서 실행되어 정규식이 웹서버에서 높은 CPU를 소비하도록 했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Cloudflare가 멈춘 사례&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2019년 Cloudflare의 ReDoS 중단으로 인해 27분 동안 많은 서비스가 멈춘 사례가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 ReDos이며 아래 링크에서 그 내용을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;안전한 정규식 작성하는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 안전한 정규식을 작성하는 기본 원칙&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주의해야 할 3가지 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나쁜 정규식 패턴을 발견하는 것은 실제로 매우 까다롭습니다.&amp;nbsp;그러나 주의해야 할 3가지 주요 패턴은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(a+)+ : 중첩 수량자&lt;/li&gt;
&lt;li&gt;(a|a)+ : 정량화된 중첩 분리&lt;/li&gt;
&lt;li&gt;\d+\d+ : 정량화된 중첩 인접성&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정규식 패턴 입력을 사용자에게 열어두지 말자.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 정규식 패턴에 입력을 할 수 있도록 서비스를 제공하면 안 됩니다. 당연한 이야기지만, 이는&amp;nbsp;공격자가 정규식을 수정할 수 있는 경우 위험한 패턴을 구성하여 시스템을 ReDoS에 취약하게 만들 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정규식 패턴은 단순하게 작성하자&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규식 패턴이 길면 길수록 취약점이 생깁니다. 가능한 단순한 정규식을 만드세요. 당연히 위의 중첩수량자와 같은 취약패턴은 피하시고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 정규식 패턴을 검사해줄 툴을 사용하자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 언급하다시피 정규식 패턴을 단순하게 작성하는 것이 좋지만, 패턴을 길게 작성해야 할 경우가 생깁니다. 작성한 정규식이 취약한지 확인할 수 있는 여러 도구들이 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;validation 라이브러리 사용&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/validatorjs/validator.js&quot;&gt;https://github.com/validatorjs/validator.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://express-validator.github.io/docs/&quot;&gt;https://express-validator.github.io/docs/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 라이브러리로 정규식을 한번 검토하고 나갈 필요가 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정규식 analyzer 사용&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://makenowjust-labs.github.io/recheck/playground/&quot;&gt;https://makenowjust-labs.github.io/recheck/playground/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/2bdenny/ReScue&quot;&gt;https://github.com/2bdenny/ReScue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/davisjam/safe-regex&quot;&gt;https://github.com/davisjam/safe-regex&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 사용하여, 안전한 정규식인지 한번 확인할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Nodejs를 사용한다면, ReDos를 방어하는 정규식 엔진을 사용하자.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodeJS의 기본 정규식 엔진은&amp;nbsp;&lt;b&gt;ReDos&lt;/b&gt;&amp;nbsp;공격에 취약합니다. 구글은 이를 파악하고 &lt;b&gt;re2라는&lt;/b&gt; 엔진을 만들었습니다. re2 엔진은&amp;nbsp;**ReDos**를 방어할 수도 있으며, 기존 정규식 엔진과 사용도 거의 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/uhop/node-re2&quot;&gt;https://github.com/uhop/node-re2&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671887912091&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - uhop/node-re2: node.js bindings for RE2: fast, safe alternative to backtracking regular expression engines.&quot; data-og-description=&quot;node.js bindings for RE2: fast, safe alternative to backtracking regular expression engines. - GitHub - uhop/node-re2: node.js bindings for RE2: fast, safe alternative to backtracking regular expre...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/uhop/node-re2&quot; data-og-url=&quot;https://github.com/uhop/node-re2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/9bxge/hyQ1lqKeMo/gz6JubKPAWKydv8xWKL3bK/img.png?width=1200&amp;amp;height=600&amp;amp;face=954_153_1047_254&quot;&gt;&lt;a href=&quot;https://github.com/uhop/node-re2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/uhop/node-re2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/9bxge/hyQ1lqKeMo/gz6JubKPAWKydv8xWKL3bK/img.png?width=1200&amp;amp;height=600&amp;amp;face=954_153_1047_254');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - uhop/node-re2: node.js bindings for RE2: fast, safe alternative to backtracking regular expression engines.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;node.js bindings for RE2: fast, safe alternative to backtracking regular expression engines. - GitHub - uhop/node-re2: node.js bindings for RE2: fast, safe alternative to backtracking regular expre...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/ReDoS&quot;&gt;ReDoS - Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671887902955&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ReDoS - Wikipedia&quot; data-og-description=&quot;A regular expression denial of service (ReDoS)[1] is an algorithmic complexity attack that produces a denial-of-service by providing a regular expression and/or an input that takes a long time to evaluate. The attack exploits the fact that many[2] regular &quot; data-og-host=&quot;en.wikipedia.org&quot; data-og-source-url=&quot;https://en.wikipedia.org/wiki/ReDoS&quot; data-og-url=&quot;https://en.wikipedia.org/wiki/ReDoS&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/ReDoS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://en.wikipedia.org/wiki/ReDoS&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ReDoS - Wikipedia&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A regular expression denial of service (ReDoS)[1] is an algorithmic complexity attack that produces a denial-of-service by providing a regular expression and/or an input that takes a long time to evaluate. The attack exploits the fact that many[2] regular&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;en.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.hahwul.com/cullinan/redos/&quot;&gt;ReDOS (Regex DOS)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671887905172&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;ReDOS (Regex DOS)&quot; data-og-description=&quot;  Introduction&quot; data-og-host=&quot;www.hahwul.com&quot; data-og-source-url=&quot;https://www.hahwul.com/cullinan/redos/&quot; data-og-url=&quot;https://www.hahwul.com/cullinan/redos/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eoMnaV/hyQZP72ozx/lpNx0YGLzhs8twawmbQRM1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/ceMfRP/hyQZDGATr6/kyKmvKvNrSfDmtLAZGtkn1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/M858s/hyQZGiZsYj/ssLIBYivDXHk2DKep4GPHk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://www.hahwul.com/cullinan/redos/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.hahwul.com/cullinan/redos/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eoMnaV/hyQZP72ozx/lpNx0YGLzhs8twawmbQRM1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/ceMfRP/hyQZDGATr6/kyKmvKvNrSfDmtLAZGtkn1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/M858s/hyQZGiZsYj/ssLIBYivDXHk2DKep4GPHk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ReDOS (Regex DOS)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  Introduction&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.hahwul.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yceffort.kr/2021/09/deep-dive-javascript-regex#%EC%A0%95%EA%B7%9C%EC%8B%9D%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot;&gt;자바스크립트에서의 정규식, 이론부터 조심해야 할 것 까지&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671887908704&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Home&quot; data-og-description=&quot;yceffort&quot; data-og-host=&quot;yceffort.kr&quot; data-og-source-url=&quot;https://yceffort.kr/2021/09/deep-dive-javascript-regex#%EC%A0%95%EA%B7%9C%EC%8B%9D%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot; data-og-url=&quot;https://yceffort.kr&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMc8lg/hyQZInwTSg/aPpSTcCiRSO27BUVZAnnt1/img.png?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260&quot;&gt;&lt;a href=&quot;https://yceffort.kr/2021/09/deep-dive-javascript-regex#%EC%A0%95%EA%B7%9C%EC%8B%9D%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yceffort.kr/2021/09/deep-dive-javascript-regex#%EC%A0%95%EA%B7%9C%EC%8B%9D%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMc8lg/hyQZInwTSg/aPpSTcCiRSO27BUVZAnnt1/img.png?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Home&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;yceffort&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yceffort.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://makenowjust-labs.github.io/recheck/&quot;&gt;recheck&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671887907227&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;recheck&quot; data-og-description=&quot;recheck: The trustworthy ReDoS checker&quot; data-og-host=&quot;makenowjust-labs.github.io&quot; data-og-source-url=&quot;https://makenowjust-labs.github.io/recheck/&quot; data-og-url=&quot;https://makenowjust-labs.github.io/recheck/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/7tE0U/hyQZM4xV6W/IgBreq4kDEyQwIczMFsbO0/img.png?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631,https://scrap.kakaocdn.net/dn/tEjmi/hyQ1inenLO/mr2WuU5DQnTNPFE477u7aK/img.png?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631&quot;&gt;&lt;a href=&quot;https://makenowjust-labs.github.io/recheck/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://makenowjust-labs.github.io/recheck/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/7tE0U/hyQZM4xV6W/IgBreq4kDEyQwIczMFsbO0/img.png?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631,https://scrap.kakaocdn.net/dn/tEjmi/hyQ1inenLO/mr2WuU5DQnTNPFE477u7aK/img.png?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;recheck&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;recheck: The trustworthy ReDoS checker&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;makenowjust-labs.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 아카이브/개발 관련 지식</category>
      <category>Catastrophic Backtracking</category>
      <category>redos</category>
      <category>RegExp</category>
      <category>공격</category>
      <category>리도스</category>
      <category>정규식</category>
      <author>운클라우드</author>
      <guid isPermaLink="true">https://wooncloud.tistory.com/118</guid>
      <comments>https://wooncloud.tistory.com/118#entry118comment</comments>
      <pubDate>Sat, 24 Dec 2022 22:31:42 +0900</pubDate>
    </item>
  </channel>
</rss>