<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>T3ch</title>
    <link>https://bubobubo003.tistory.com/</link>
    <description>코딩을 기록하고 공유하는 페이지
피드백도 환영합니다</description>
    <language>ko</language>
    <pubDate>Thu, 9 Apr 2026 12:30:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>댕댕이발 </managingEditor>
    <image>
      <title>T3ch</title>
      <url>https://tistory1.daumcdn.net/tistory/2892454/attach/0e7eea1e0c9148a69f4f8d0fffe3fb05</url>
      <link>https://bubobubo003.tistory.com</link>
    </image>
    <item>
      <title>Spring boot에서 Http request 비동기 요청하기</title>
      <link>https://bubobubo003.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring boot로 http request 코드를 개발할 때 비동기 처리로 요청 및 처리하기위한 내용입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동기방식으로 호출하게되면 앞에 호출된 request의 응답이 완료될 때까지 다음요청이 실행되지 못하게되므로&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러개의 request를 보내는 상황에서는 더 높은 효율을 보여줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 글에선 http 라이브러리는 okhttp3 를 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;build.gradle에 의존성 추가&lt;/h4&gt;
&lt;pre id=&quot;code_1698306867716&quot; class=&quot;clean&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;implementation 'com.squareup.okhttp3:okhttp:3.10.0'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;코드작성 (비동기 요청)&lt;/h4&gt;
&lt;pre id=&quot;code_1698306867717&quot; class=&quot;reasonml&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;public class Helloworld {
	private void asyncOkHttp() {
    	// http 요청하려는 URL 리스트가 있다고 가정
        List&amp;lt;String&amp;gt; requestUrls = new ArrayList&amp;lt;&amp;gt;();
        requestUrls.add(&quot;https://111...&quot;);
        requestUrls.add(&quot;https://222...&quot;);
        requestUrls.add(&quot;https://333...&quot;);
        
    	OkHttpClient client = new OkHttpClient().newBuilder().build();
        
        for (String url : requestUrls) {
            Request request = new Request.Builder()
                    .url(url)
                    .method(&quot;GET&quot;, null)
                    .build();

            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                	// request 요청 실패 응답에 대한 처리
                }

                @Override
                public void onResponse(Call call, Response response) {
                    if (response.isSuccessful()) {
                        // request 요청 성공 응답에 대한 처리
                    }
                }
            });
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(참고로 동기 요청은 enqueue 메서드 대신 execute 메서드를 사용합니다.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;&amp;nbsp;추가로 Request_Limit_Exceeded 라고해서 동일한 IP에서 너무 짧은 간격으로 Request를 요청할 시에 요청자체를 거부하는 경우가 있는데 해당 상황을 해결하기 위해 사용했던 부분도 공유합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;코드작성 (비동기 요청 + Dispatcher)&lt;/h4&gt;
&lt;pre id=&quot;code_1698307308720&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Helloworld {
	private void asyncOkHttp() {
    	// http 요청하려는 URL 리스트가 있다고 가정
        List&amp;lt;String&amp;gt; requestUrls = new ArrayList&amp;lt;&amp;gt;();
        requestUrls.add(&quot;https://111...&quot;);
        requestUrls.add(&quot;https://222...&quot;);
        requestUrls.add(&quot;https://333...&quot;);
        
        // dispatcher를 생성하여 최대 호출 수를 제어
        Dispatcher dispatcher = new Dispatcher();
        dispatcher.setMaxRequests(50);
        OkHttpClient client = new OkHttpClient().newBuilder().dispatcher(dispatcher).build();

        for (String url : requestUrls) {
            Request request = new Request.Builder()
                    .url(url)
                    .method(&quot;GET&quot;, null)
                    .build();

            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                	// request 요청 실패 응답에 대한 처리
                }

                @Override
                public void onResponse(Call call, Response response) {
                    if (response.isSuccessful()) {
                        // request 요청 성공 응답에 대한 처리
                    }
                }
            });
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용방법은 크게 다르지 않고 client 객체 build시에 dispatcher를 추가해준 채로 생성해주면 됩니다. 각 디스패처는 ExecutorService를 사용하여 내부적으로 호출을 실행합니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>spring </category>
      <category>Dispatcher</category>
      <category>HTTP request</category>
      <category>Java</category>
      <category>okhttp3</category>
      <category>Request_Limit_Exceeded</category>
      <category>Spring</category>
      <category>비동기 요청</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/86</guid>
      <comments>https://bubobubo003.tistory.com/86#entry86comment</comments>
      <pubDate>Thu, 26 Oct 2023 17:04:38 +0900</pubDate>
    </item>
    <item>
      <title>Spring MVC의 비동기처리 (ThreadPoolTaskExecutor 사용)</title>
      <link>https://bubobubo003.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring MVC에서 프로세스는 기본적으로 동기식 처리로 이루어집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드 한줄한줄 차례대로 실행이 완료된 후 넘어갈 수 있다는 뜻이죠. 보통 이걸 Blocking 이라고 하고 반대가 되는 말은 Non-Blocking 이라고 합니다. 코드가 차례대로 실행되기 때문에 코드 실행에 따른 결과 값을 보는게 명확한 장점이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Node.js로 개발을 좀 해오다보니 (Node.js는 반대로 비동기처리가 기본 컨셉입니다.) Async에 대한 용어에 더 눈이가서 Spring 어노테이션중 @Async 어노테이션에 먼저 눈이 갔습니다. 어노테이션 하나만 추가하면 내가 원하는게 된다?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;@Async&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Async는 Spring 프레임워크에서 비동기적인 메서드 실행을 지원하기 위한 어노테이션입니다. 이 어노테이션을 메서드에 붙이면 해당 메서드는 별도의 쓰레드에서 실행되며, 호출한 쓰레드는 메서드가 완료될 때까지 블로킹되지 않습니다. @Async를 사용하면 동기적인 방식이 아닌 비동기적인 방식으로 메서드를 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드에 그냥 어노테이션만 붙이면 Async 메소드가 되는 것인가? 했지만 @Async를 사용할 때 주의할 점은 이 어노테이션도 Thread pool을 사용하는 것이기 때문에 무분별하게 사용하면 오버헤드가 발생할 수도 있고, 스프링의 프록시 매커니즘을 통해 비동기 처리가 구현되기 때문에 @Async 메서드는 같은 클래스 내에서 호출되면 비동기적으로 실행되지 않을 수 있습니다. 이 경우 AOP 프록시가 적용되지 않기 때문입니다.&lt;/p&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 MVC 프로젝트에 기본적으로 Filter를 사용하고있는데 위 내용때문에 제대로 작동하지 않는 부분을 확인하고 바로 다른 방법을 찾아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;ThreadPoolTaskExecutor&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;그러기위해선 부하가 큰 Service 메소드에 대한 Async 처리가 필요했고, 아래와같이 처리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AsyncConfig.class&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 쓰레드풀을 사용하기 위한 custom 설정을 하는 클래스입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692671972606&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class AsyncConfig {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);  // 쓰레드풀의 기본 크기
        executor.setMaxPoolSize(50);   // 쓰레드풀의 최대 크기
        executor.setQueueCapacity(100); // 작업 큐 크기
        executor.setThreadNamePrefix(&quot;Custom-Async-&quot;); // 쓰레드 이름 접두사 설정
        executor.initialize();
        return executor;
    }
}&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;Controller코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 서비스 메소드를 백그라운드 로직으로 수정하였고, DeferredResult 객체에 비동기로직에 대한 결과 값을 저장할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1692671904600&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
public class AlarmController {
    private final AlarmService alarmService;
    @Qualifier(&quot;taskExecutor&quot;)
    private final ThreadPoolTaskExecutor taskExecutor;

   @PostMapping(value = &quot;/{id}/send&quot;)
   public Response&amp;lt;Map&amp;lt;String,Object&amp;gt;&amp;gt; immediatelyPush(
           HttpServletRequest req,
           @PathVariable(&quot;id&quot;) Long id
   ) {
       try {
           // DeferredResult&amp;lt;Response&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt;&amp;gt; deferredResult = new DeferredResult&amp;lt;&amp;gt;();

           taskExecutor.submit(() -&amp;gt; {
               // 비동기로 실행되는 백그라운드 로직
               alarmService.immediatelyPush(id);
               // deferredResult.setResult(new Response&amp;lt;&amp;gt;(ResultCode.REGISTER_SUCCESS));
               // deferredResult.setResult(new Response&amp;lt;&amp;gt;(ResultCode.REGISTER_FAIL));
           });
           return new Response&amp;lt;&amp;gt;(ResultCode.REGISTER_SUCCESS);
       } catch (Exception e) {
           return new Response&amp;lt;&amp;gt;(ResultCode.REGISTER_FAIL);
       }
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게되면 서비스 메소드에 대한 부분이 실행완료되지않아도 즉시 response를 넘겨주게 됩니다. 물론 response후에도 Spring Application 에서는 해당 로직이 실행중이게 됩니다.&lt;/p&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>spring </category>
      <category>async</category>
      <category>DeferredResult</category>
      <category>Spring</category>
      <category>Spring Async</category>
      <category>ThreadPoolTaskExecutor</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/85</guid>
      <comments>https://bubobubo003.tistory.com/85#entry85comment</comments>
      <pubDate>Tue, 22 Aug 2023 11:47:24 +0900</pubDate>
    </item>
    <item>
      <title>NestJS로 프로젝트 만들어보기 - 4 (configuration 적용)</title>
      <link>https://bubobubo003.tistory.com/84</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/c3BWWu/btrZJCT0cBi/HWd4FIymsKKzr5lYeaEw61/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3BWWu/btrZJCT0cBi/HWd4FIymsKKzr5lYeaEw61/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3BWWu/btrZJCT0cBi/HWd4FIymsKKzr5lYeaEw61/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3BWWu%2FbtrZJCT0cBi%2FHWd4FIymsKKzr5lYeaEw61%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;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;size16&quot;&gt;어느 언어이던간에 환경변수 값, 즉 configuration 이라는 모듈을 사용하게 되는데, Nest.js에서도 configuration 모듈을 제공해주고있으며, 해당 모듈은 dotenv 모듈을 기반해서 작동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통으로 사용해야할 변수 값이거나 혹은 개발레벨 (프로덕션, 개발, 테스팅 등등..) 에 따른 다른 환경변수를 가져야하는 경우가 많으므로 프로젝트를 구성할 때 거의 대부분 셋팅하게 되는 모듈입니다. 이전에 포스팅한 데이터베이스 연결정보같은 경우도 config 파일로 따로 관리하는 경우가 많아서 해당 모듈을 먼저 셋팅하고 넘어가고자 합니다.&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;1. @nestjs/config 모듈 인스톨&lt;/h4&gt;
&lt;pre id=&quot;code_1676707627665&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm i --save @nestjs/config
or
$ yarn add @nestjs/config&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nestjs에서 공식으로 제공해주는 모듈을 인스톨합니다. 해당 패키지는 내부적으로 위에 말씀드렸다시피 dotenv를 사용합니다.&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. 모듈에 적용하기 (Getting Started)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서 가이드하는 방법이 여러가지입니다. 기본제공(Getting Started)에서는 이와같이 가이드합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;app.module.ts&lt;/blockquote&gt;
&lt;pre id=&quot;code_1676707947428&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule.forRoot()],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;eDivResult&quot;&gt;
&lt;div id=&quot;transTextContainer&quot;&gt;
&lt;p id=&quot;eSelTextTrans&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;위의 코드는 기본 위치(프로젝트 루트 디렉터리)에서 .env 파일을 로드 및 구문 분석하고 .env 파일의 키/값 쌍을 process.env에 할당된 환경 변수와 병합하고 결과를 개인 구조에 저장합니다.&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;@nestjs/config는 dotenv에 의존하기 때문에 환경 변수 이름의 충돌을 해결하기 위해 해당 패키지의 규칙을 사용합니다.&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;&lt;/span&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;키가 런타임 환경에 환경 변수로 존재하는 경우(예: export DATABASE_USER=test와 같은 OS 셸 내보내기를 통해) 및 .env 파일에 있는 경우 런타임 환경 변수가 우선합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, '돋움', sans-serif;&quot;&gt;즉, bash_profile 혹은 bashrc 와 같은 파일에 이미 환경변수 설정이 되어있더라도, 프로젝트 루트에 있는 .env 파일의 환경변수 값이 우선순위가 더 높습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 가이드 방법이 있고, 저는 애초에 어플리케이션 시작시에 DB를 연동시키기 때문에 &lt;br /&gt;DB module에서 config를 로드하고, 그 이후에 로드된 config 값을 가지고 DB 연결을 먼저 진행하였습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.&amp;nbsp; DB 모듈 작성&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;src/common/db.module.ts&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1676990448668&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import {ConfigModule, ConfigService} from '@nestjs/config';
import configuration from '@Common/configuration';
import {TypeOrmModule} from '@nestjs/typeorm';
import * as Joi from 'joi';

@Module({
    imports: [
        ConfigModule.forRoot({
            isGlobal: true,
            load: [configuration],
            envFilePath: process.env.NODE_ENV == 'local' ? '.env' : process.env.NODE_ENV == 'development' ? '.env.development' : '.env.production',
            validationSchema: Joi.object({ // 환경변수 유효성 체크
                NODE_ENV: Joi.string().valid('local','development', 'production', 'testing', 'staging').required(),
                DATABASE_HOST: Joi.string().required(),
                DATABASE_PORT: Joi.string().required(),
                DATABASE_USER: Joi.string().required(),
                DATABASE_PASSWORD: Joi.string().required(),
                DATABASE_NAME: Joi.string().required(),
            })
        }),
        TypeOrmModule.forRootAsync({
            imports: [ConfigModule],
            inject: [ConfigService],
            useFactory: async (configService: ConfigService) =&amp;gt; ({
                type: 'mysql',
                host: configService.get('database.host'),
                port: configService.get('database.port'),
                username: configService.get('database.user'),
                password: configService.get('database.password'),
                database: configService.get('database.name'),
                legacySpatialSupport: false,
                logging: true,
                acquireTimeout: 5000,
                charset: 'UTF8MB4_GENERAL_CI',
                entities: ['dist/**/*.entity.{ts,js}'], // Entity 연결
                synchronize: false, //true 값을 설정하면 어플리케이션을 다시 실행할 때 엔티티안에서 수정된 컬럼의 길이 타입 변경값등을 해당 테이블을 Drop한 후 다시 생성
                extra: {
                    connectionLimit: configService.get('CONNECTION_LIMIT')
                }
            }),
        }),
    ],
})
export class DatabaseModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 루트모듈에서 ConfigModule을 이용해 config값을 로드하면 ConfigService를 주입받을 수 있는데, 그 이후에는 ConfigService 객체를 통해 config의 값을 가져올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;envFilePath를 통해 NODE_ENV값에 따른 참조 환경변수 파일을 다르게 가져올 수 있게끔 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;.env&lt;/blockquote&gt;
&lt;pre id=&quot;code_1676991008255&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# #MYSQL
DATABASE_HOST = localhost
DATABASE_USER = root
DATABASE_NAME = store
DATABASE_PASSWORD = root
DATABASE_PORT = 3306
DATABASE_LOGGING = true
CONNECTION_LIMIT=15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트에 이와같이 env 파일을 작성하면 ConfigModule에서 프로젝트 루트에 있는 해당 환경변수 파일을 로드하게됩니다.&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;4. 루트모듈에 DB모듈 import 및 타 모듈에서 configService 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 연결은 어플리케이션 시작 시에 필요하기때문에 아래와 같이 import 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;app.module.ts&lt;/blockquote&gt;
&lt;pre id=&quot;code_1676991541947&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { DatabaseModule } from '@Common/db.module';
import { Module } from '@nestjs/common';
import { OrderModule } from '@Order/order.module';
import {TypeOrmModule} from '@nestjs/typeorm';

@Module({
    imports: [
        DatabaseModule,
        TypeOrmModule.forFeature([]),
        OrderModule,
    ]
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DatabaseModule을 못가져오면 어플리케이션 실행 에러가 떨어질겁니다. 그리고 해당 모듈에서 ConfigModule도 같이 import했기때문에 정상실행됐다면 다른 모듈에서도 ConfigService를 주입하여 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;src/order/services/order.service.ts&lt;/blockquote&gt;
&lt;pre id=&quot;code_1676991682961&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';
import {ConfigService} from '@nestjs/config';

@Injectable()
export class OrderService {
    constructor(
      private readonly config: ConfigService
    ) {
        console.log(config.get(&quot;database.host&quot;))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 여기서 저 config값이 필요한건 아니지만 위와같이 ConfigService를 바로 주입받아서 config 값을 참조 가능하게됩니다.&lt;/p&gt;</description>
      <category>Node.js/Nest.js</category>
      <category>@nestjs/config</category>
      <category>dotenv</category>
      <category>NestJS</category>
      <category>nestjs config</category>
      <category>nestjs configService</category>
      <category>typeorm</category>
      <category>typeorm 연결</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/84</guid>
      <comments>https://bubobubo003.tistory.com/84#entry84comment</comments>
      <pubDate>Wed, 22 Feb 2023 00:03:18 +0900</pubDate>
    </item>
    <item>
      <title>NestJS로 프로젝트 만들어보기 - 3 (DB연동)</title>
      <link>https://bubobubo003.tistory.com/83</link>
      <description>&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vgLD9/btrZD2DA4W3/BvbWwQYfpZloNc5KF9nUNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vgLD9/btrZD2DA4W3/BvbWwQYfpZloNc5KF9nUNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vgLD9/btrZD2DA4W3/BvbWwQYfpZloNc5KF9nUNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvgLD9%2FbtrZD2DA4W3%2FBvbWwQYfpZloNc5KF9nUNk%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;800&quot; height=&quot;336&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹어플리케이션을 만들면서 가장 많이 필요로 하는 것중에 하나가 데이터베이스 연동인데, Nest.js에서도 공식적으로 데이터베이스 클라이언트 모듈을 제공해주고, Typescript의 ORM인 TYPE ORM을 사용할 수 있습니다. 이전 프로젝트에 데이터베이스를 연동시켜보겠습니다.&lt;/p&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;1. MYSQL image 다운받기&lt;/h4&gt;
&lt;pre id=&quot;code_1676552613066&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker pull mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전을 따로기입하지 않고 image를 다운받으면 최신버전을 기준으로 mysql 이미지를 다운받게 됩니다.&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; Container 실행&lt;/h4&gt;
&lt;pre id=&quot;code_1676552843495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=root -d -p 3306:3306 mysql&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;--name : 컨테이너 이름&lt;/li&gt;
&lt;li&gt;-e MYSQL_ROOT_PASSWORD : 패스워드 설정&lt;/li&gt;
&lt;li&gt;-d : 백그라운드(데몬)으로 실행&lt;/li&gt;
&lt;li&gt;-p : 포트바인딩&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커실행이 제대로 됐다면, docker ps 명령어로 실행이 됐는지 확인할 수 있습니다.&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;3.&amp;nbsp; Mysql 접속 테스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너가 제대로 떳다면 DBMS로 접속이 가능합니다. 저는 DBeaver 라는 DBMS 툴로 사용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 MYSQL8 버전 이후부터 바로 접속하려했을 때 아래의 에러가 하나 발생했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Public&amp;nbsp;Key&amp;nbsp;Retrieval&amp;nbsp;is&amp;nbsp;not&amp;nbsp;allowed&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;allowPublicKeyRetrieval=true 옵션값을 설정하여 해결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySql 공식사이트에서 MITM 공격을 방어하기 위해 해당 디폴트 옵션값을 false로 설정했다라는 설명을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBeaver를 사용한다는 가정하에는 아래와 같이 옵션 설정을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;1230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OHjew/btrZBsbSz1E/n3hQVokr9nlgirV8QkAQyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OHjew/btrZBsbSz1E/n3hQVokr9nlgirV8QkAQyK/img.png&quot; data-alt=&quot;default 값으로 false로 설정되어있는걸 true로 변경합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OHjew/btrZBsbSz1E/n3hQVokr9nlgirV8QkAQyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOHjew%2FbtrZBsbSz1E%2Fn3hQVokr9nlgirV8QkAQyK%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;1398&quot; height=&quot;1230&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;1230&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;default 값으로 false로 설정되어있는걸 true로 변경합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.&amp;nbsp; Database 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDL 문으로도 Database를 만들어도되고, 아니면 DBMS의 UI로 간단하게 만들 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 사용할 Database 이름을 store라고 짓도록 하겠습니다.&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;5.&amp;nbsp; Nestjs 프로젝트에서 DB 연결 (&lt;a href=&quot;https://docs.nestjs.com/recipes/sql-typeorm&quot;&gt;출처: &lt;/a&gt;&lt;a href=&quot;https://docs.nestjs.com/techniques/database&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.nestjs.com/techniques/database&lt;/a&gt;)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nest.js 공식 문서에 보면 여러가지 ORM 연결을 공식적으로 지원하고 있습니다. 그중에서 TypeORM을 사용하려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Mysql을 접속하기위한 모듈들을 설치하도록 하게습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1676556046806&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install --save @nestjs/typeorm typeorm mysql2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지 설치 후에 DB 접속을 모듈화한 코드를 작성하고, 해당 모듈을 루트모듈에 import하는 코드입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;src/common/db.module.ts&lt;/blockquote&gt;
&lt;pre id=&quot;code_1676556661508&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import {TypeOrmModule} from '@nestjs/typeorm';

@Module({
    imports: [
        TypeOrmModule.forRoot({
            type: 'mysql',
            host: 'localhost',
            port: 3306,
            username: 'root',
            password: 'admin1234',
            database: 'store',
            entities: [],
            synchronize: true,
        }),
    ],
    providers: [],
    exports: [],
})
export class DatabaseModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;루트모듈에 위에 작성한 DatabaseModule을 import 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;src/app.module.ts&lt;/blockquote&gt;
&lt;pre id=&quot;code_1676557464542&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import {TypeOrmModule} from '@nestjs/typeorm';
import { DatabaseModule } from '@Common/db.module';

@Module({
    imports: [
        DatabaseModule,
        TypeOrmModule.forFeature([]),
    ]
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forFeature안에 entity를 넣으라고 되어있지만 Database.module.ts 파일에서 entity 의 경로를 추후에 다시 잡아줄거기 때문에 이번엔 일단 연결만 한다는 생각으로 코드를 작성합니다. 그리고 파일을 import 할 때 절대경로를 사용하기위해 tsconfig.json 파일에 path 설정을 추가해줍니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    ...
    &quot;paths&quot;: {
      &quot;@Common/*&quot;: [&quot;./src/common/*&quot;]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성을 끝낸후 npm run start:dev 혹은 yarn start:dev (저는 yarn으로 프로젝트를 생성했습니다) 으로 프로젝트를 실행시킵니다.&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;스크린샷 2023-02-16 오후 11.29.23.png&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSDYRB/btrZz10sELk/9fcrXQbSUUZ21ZVKh9YtK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSDYRB/btrZz10sELk/9fcrXQbSUUZ21ZVKh9YtK0/img.png&quot; data-alt=&quot;에러없이 실행완료된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSDYRB/btrZz10sELk/9fcrXQbSUUZ21ZVKh9YtK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSDYRB%2FbtrZz10sELk%2F9fcrXQbSUUZ21ZVKh9YtK0%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;1870&quot; height=&quot;680&quot; data-filename=&quot;스크린샷 2023-02-16 오후 11.29.23.png&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;680&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;제가 소스에서는 지워놓은 몇몇 모듈들이 있어서 OrderModule 같은건 아마 안나올텐데, 중요한건 에러가 안난다는 것과 TypeOrmCoreModule, DatabaseModule 의 dependencies initialized가 정상적으로 되면 성공한겁니다.&lt;/p&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;br /&gt;개발 환경에 따라 커넥션 정보를 관리해야하기때문에 약간 다듬어야할 필요가 있는데, 그건 이후의 과정에서 포스팅 하도록 하겠습니다.&lt;/p&gt;</description>
      <category>Node.js/Nest.js</category>
      <category>docker mysql</category>
      <category>NestJS</category>
      <category>nestjs typeorm</category>
      <category>typeorm</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/83</guid>
      <comments>https://bubobubo003.tistory.com/83#entry83comment</comments>
      <pubDate>Thu, 16 Feb 2023 23:33:31 +0900</pubDate>
    </item>
    <item>
      <title>NestJS로 프로젝트 만들어보기 - 2 (디렉토리구성)</title>
      <link>https://bubobubo003.tistory.com/82</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWSiqG/btrZeyrseRg/fSCikPNC7gsth6WQAUmkwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWSiqG/btrZeyrseRg/fSCikPNC7gsth6WQAUmkwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWSiqG/btrZeyrseRg/fSCikPNC7gsth6WQAUmkwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWSiqG%2FbtrZeyrseRg%2FfSCikPNC7gsth6WQAUmkwK%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;429&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nest cli를 통해 기본적으로 만들어지는 프로젝트 구조를 이전에 학습해보았는데, 디렉토리 구조를 조금 더 다듬어보려고 합니다.&lt;br /&gt;공식 문서에 나오는 구조를 최대한 지키되, 코딩하기에 좀 더 알아보기 쉽고 간편한 방법을 찾아보겠습니다.&lt;br /&gt;&lt;br /&gt;Nest js에서 기본적으로 필요한 구성은 아래와 같습니다. 간단히 역할을 설명하자면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGJ9fH/btrZmDRHZqX/CCOJ5YvfI5f81D79VGkms0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGJ9fH/btrZmDRHZqX/CCOJ5YvfI5f81D79VGkms0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGJ9fH/btrZmDRHZqX/CCOJ5YvfI5f81D79VGkms0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGJ9fH%2FbtrZmDRHZqX%2FCCOJ5YvfI5f81D79VGkms0%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;936&quot; height=&quot;460&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&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;controller : 라우트를 기술&lt;/li&gt;
&lt;li&gt;service : 서비스에 필요한 메서드를 기술&lt;/li&gt;
&lt;li&gt;module : Nest 프레임워크를 구동하는데 필요한 메타데이터를 제공 (Nest에선 적어도 하나의 root 모듈이 필요합니다)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;790&quot; data-origin-height=&quot;812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xrpGN/btrZhSiSWxV/kukgFYWMjSUsIiMUIGHMJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xrpGN/btrZhSiSWxV/kukgFYWMjSUsIiMUIGHMJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xrpGN/btrZhSiSWxV/kukgFYWMjSUsIiMUIGHMJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxrpGN%2FbtrZhSiSWxV%2FkukgFYWMjSUsIiMUIGHMJ0%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;790&quot; height=&quot;812&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러는 컨트롤러끼리, 서비스는 서비스끼리 이런식으로 묶을 수 있겠죠. 그러고선 app.module.ts(루트모듈)에 전부 import를 하면 간단하게 구동시킬 수 있습니다. 근데 Nest.js에서는 Module이라는 구조가 있어서 다른 방법으로 구성이 가능합니다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJeRs/btrZlqkMR9m/ytSo1plzaetbTlEksCFzq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJeRs/btrZlqkMR9m/ytSo1plzaetbTlEksCFzq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJeRs/btrZlqkMR9m/ytSo1plzaetbTlEksCFzq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJeRs%2FbtrZlqkMR9m%2FytSo1plzaetbTlEksCFzq0%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;970&quot; height=&quot;526&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상단에 있는 Application Module은 app.module.ts (루트모듈) 이라고 보시면되고, 이전에 설명했던 컨트롤러, 서비스가 한데 묶인 새로운 모듈을 루트모듈에 주입시킬 수도 있습니다. 이렇게되면 모양새가 마이크로서비스같은 느낌이 되죠. 각각의 기능별로 하나의 세트를 만들어놓았다고 생각할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sinJm/btrZk9QXzER/xNKwkdyvuWkerE6I0HNXq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sinJm/btrZk9QXzER/xNKwkdyvuWkerE6I0HNXq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sinJm/btrZk9QXzER/xNKwkdyvuWkerE6I0HNXq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsinJm%2FbtrZk9QXzER%2FxNKwkdyvuWkerE6I0HNXq1%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;1096&quot; height=&quot;764&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cats 이라는 모듈안에 컨트롤러 서비스 등 하나의 기능으로 작동하게끔 만듭니다. Module이 기본적으로 갖고있어야할 요소들을 한데에 모으는거죠. 이와같은 모양새로 이전에 구성했던 디렉토리 구조를 변경한다면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOQBLM/btrZjYPUmhU/MX348x26UbTMeKK0zz22rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOQBLM/btrZjYPUmhU/MX348x26UbTMeKK0zz22rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOQBLM/btrZjYPUmhU/MX348x26UbTMeKK0zz22rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOQBLM%2FbtrZjYPUmhU%2FMX348x26UbTMeKK0zz22rk%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;712&quot; height=&quot;882&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;882&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;order라는 모듈을 만들어서 한데에 모으고 order 모듈을 루트모듈에 import시키는 방법으로 프로젝트 구성이 가능합니다!&lt;br /&gt;두 구성방법 중에 저는 주로 전자를 사용해왔었습니다. 제가 겪은 시행착오가 정답은 아니지만 이유를 얘기해보자면,&lt;br /&gt;&lt;br /&gt;1. 모듈별로 명확하게 기능을 나누기가 생각보다 쉽지가 않았습니다.&lt;br /&gt;2. 각각의 모듈을 추가로 만들어줘야한다는 점이 일단 불편했고, 애초에 1번 사항이 충족이 되지 않았었기때문에 루트모듈에 다 때려넣는거나 각각의 모듈에 구분해서 때려넣는거나 크게 다르다고 느끼지 못했습니다.&lt;br /&gt;3. 모듈을 여러개 만들었을 때 모듈들 사이에서도 재사용성이 있어야 의미가 있다고 생각했는데 재사용성이 있게 만드는게 어려웠습니다.&lt;br /&gt;&lt;br /&gt;위와같은 이유때문에 대부분의 nest 프로젝트는 모듈로 작성하지 않았었는데, 이번기회에 모듈식으로 구성해볼까합니다. 모듈도 하나의 개념만 존재하는 것이 아니라 여러가지 부가적인 개념이 존재하기때문에 공부할겸 겸사겸사 좋을 것 같습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Node.js/Nest.js</category>
      <category>NestJS</category>
      <category>nestjs directory</category>
      <category>nestjs doc</category>
      <category>nestjs module</category>
      <category>nestjs project</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/82</guid>
      <comments>https://bubobubo003.tistory.com/82#entry82comment</comments>
      <pubDate>Tue, 14 Feb 2023 23:56:43 +0900</pubDate>
    </item>
    <item>
      <title>NestJS로 프로젝트 만들어보기 - 1 (nestjs/cli)</title>
      <link>https://bubobubo003.tistory.com/81</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/bMDWly/btrYIcvK7F8/PZpzr1kWGYJXzhhPe1a191/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMDWly/btrYIcvK7F8/PZpzr1kWGYJXzhhPe1a191/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMDWly/btrYIcvK7F8/PZpzr1kWGYJXzhhPe1a191/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMDWly%2FbtrYIcvK7F8%2FPZpzr1kWGYJXzhhPe1a191%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;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js를 사용하고, Typescript를 배우던 와중에 접했던 프레임워크인 NestJS라는 물건을 사용하게 되었습니다.&lt;br /&gt;기존엔 Express를 사용했었고, 그 안에 Typescript를 셋팅하여 프로젝트를 구성했던 것과는 다르게 이 프레임워크를 쓰게되면 기본적인 Typescript 셋팅부터 전체적인 구조까지 프레임워크 생성 커맨드를 통해 기본적으로 제공해줍니다. 공식 문서에서는 아래와 같이 소개합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nest (NestJS) is a framework for building efficient, scalable&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://nodejs.org/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Node.js&lt;/span&gt;&lt;/a&gt;&lt;span&gt; &lt;/span&gt;server-side applications. It uses progressive JavaScript, is built with and fully supports&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;http://www.typescriptlang.org/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;TypeScript&lt;/span&gt;&lt;/a&gt;&lt;span&gt; &lt;/span&gt;(yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Under the hood, Nest makes use of robust HTTP Server frameworks like&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://expressjs.com/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Express&lt;/span&gt;&lt;/a&gt;&lt;span&gt; &lt;/span&gt;(the default) and optionally can be configured to use&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://github.com/fastify/fastify&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Fastify&lt;/span&gt;&lt;/a&gt;&lt;span&gt; &lt;/span&gt;as well!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nest provides a level of abstraction above these common Node.js frameworks (Express/Fastify), but also exposes their APIs directly to the developer. This gives developers the freedom to use the myriad of third-party modules which are available for the underlying platform.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[출처 - &lt;a href=&quot;https://docs.nestjs.com/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://docs.nestjs.com/&lt;/span&gt;&lt;/a&gt;]&lt;br /&gt;&lt;br /&gt;직접 사용해보면서 느꼈던거지만 약간 Spring 같은 느낌이 있었습니다.&lt;br /&gt;기존에 바닐라 자바스크립트로 구성하던 모양새와는 달리 OOP(객체지향 프로그래밍)의 요소가 강력하게 느껴졌습니다.&lt;br /&gt;공식 문서에서 가이드해주는 시작대로 프로젝트를 생성하면 굉장히 쉽게 웹어플리케이션을 실행할 수 있고, 아주 깔끔하게 프로젝트의 구조가 생성됩니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ npm i -g @nestjs/cli
$ nest new project-name&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 커맨드를 입력하면 package manager로 무엇을 쓸지 물어보는데&lt;br /&gt;그건 취향에 맞게끔 선택하시면 이런 구조의 프로젝트가 생성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djngzc/btrYPEw5tUq/fYwQSmCJWR1dkwD3dPPoq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djngzc/btrYPEw5tUq/fYwQSmCJWR1dkwD3dPPoq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djngzc/btrYPEw5tUq/fYwQSmCJWR1dkwD3dPPoq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdjngzc%2FbtrYPEw5tUq%2FfYwQSmCJWR1dkwD3dPPoq0%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;490&quot; height=&quot;468&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test모듈과 기타 설정파일들은 제쳐두고 src디렉토리 안에 파일들을 보시면 되는데 기본적으로 세가지의 구성이 있습니다.&lt;br /&gt;물론 일반적인 디렉토리 구조는 아니고 앱을 구동하기 위한 기본 구성만을 생성해줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Controller : &lt;span style=&quot;color: #252525;&quot;&gt;컨트롤러는 들어오는 요청을 처리하고 클라이언트에 응답을 반환하는 역할을 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Module : &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Nest가 애플리케이션 구조를 만들때 사용할 수 있는&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 메타데이터를&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 제공해주는 역할을 합니다. 내용이 조금 헷갈리는데 굳이 비유를 하자면 Module에 Controller나 Service등을 추가하여 특정 기능을 가지는 모듈로 만들 수 있습니다. 프로젝트 생성시에 기본적으로 만들어진 모듈은 루트모듈입니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Service : 서비스, 말그대로 서비스를 하기위한 로직을 담고있는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는 Typescript를 기반으로 만들어진 프레임워크기때문에 기본적으로 빌드가 필요합니다. &lt;br /&gt;그래서 nest build 명령어를 쓰면 tsconfig 설정을 이용하여 Typescript 빌드를 하고 빌드된 js 파일을 node 명령어로 실행시킬 수 있습니다.&lt;br /&gt;npm 혹은 yarn start 명령어로 실행을 하면 아주 간단하게 3000 포트로 웹어플리케이션 하나가 뜨게됩니다.&lt;br /&gt;시작은 굉장히 간단하죠? 그럼 굳이 이런 프레임워크를 왜 써야할까 싶었는데 사용하다보니 NestJS의 여러 기능들이 있었습니다.&lt;br /&gt;다양한 어노테이션 지원과 아키텍쳐가 명확하고 그에따른 각종 기능들을 공식적으로 지원해주고있기때문에 큰 무리없이 프로젝트를 구성할 수 있었습니다. 이런 부분들은 다른 글에서 담아볼 수 있도록 하겠습니다.&lt;/p&gt;</description>
      <category>Node.js/Nest.js</category>
      <category>backend</category>
      <category>backend-framework</category>
      <category>Nest</category>
      <category>NestJS</category>
      <category>nestjs/cli</category>
      <category>node.js</category>
      <category>node.js backend</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/81</guid>
      <comments>https://bubobubo003.tistory.com/81#entry81comment</comments>
      <pubDate>Fri, 10 Feb 2023 13:17:59 +0900</pubDate>
    </item>
    <item>
      <title>[javascript] - Promise bulk로 처리하기</title>
      <link>https://bubobubo003.tistory.com/80</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7p9gb/btrYIvVMqu0/Sf0VYA4NKcA0oWxkYGu9FK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7p9gb/btrYIvVMqu0/Sf0VYA4NKcA0oWxkYGu9FK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7p9gb/btrYIvVMqu0/Sf0VYA4NKcA0oWxkYGu9FK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7p9gb%2FbtrYIvVMqu0%2FSf0VYA4NKcA0oWxkYGu9FK%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;1398&quot; height=&quot;768&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript의 객체 중 &lt;b&gt;Promise&lt;/b&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;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise는 다음 중 하나의 상태를 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대기(pending): 이행하지도, 거부하지도 않은 초기 상태.&lt;/li&gt;
&lt;li&gt;이행(fulfilled): 연산이 성공적으로 완료됨.&lt;/li&gt;
&lt;li&gt;거부(rejected): 연산이 실패함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대기 중인 프로미스는 값과 함께 이행할 수도, 어떤 이유(오류)로 인해 거부될 수도 있습니다. 이행이나 거부될 때, 프로미스의 then 메서드에 의해 대기열(큐)에 추가된 처리기들이 호출됩니다. 이미 이행했거나 거부된 프로미스에 처리기를 연결해도 호출되므로, 비동기 연산과 처리기 연결 사이에 경합 조건은 없습니다.&lt;/p&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의 언어특징인 비동기성때문에 Promise객체를 많이쓰게되는데 각각 하나의 Promise를 실행하는 경우에는 크게 고민할게 없는데, 작업을 하다보니 여러개의 Promise 객체를 실행해야하는 경우가 생겼습니다! 첨엔 별 생각없이 Promise 여러개를 배열에 넣고 Promise 기본함수에 있는 Promise.all 혹은 Promise.allsettled 함수를 쓰면 해결될거라 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들면 이런식으로 말이죠.&lt;/p&gt;
&lt;pre id=&quot;code_1675958550073&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let p1 = new Promise((resolve, reject)=&amp;gt;{
...
})
let p2 = new Promise((resolve, reject)=&amp;gt;{
...
})
Promise.allSettled([p1, p2])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 Promise.all과 Promise.allSettled의 차이는 모든 Promise객체를 성공시켜야 인정할건지 아니면 성공하는 Promise와 실패하는 Promise를 염두에 두고 실행시킬건지의 차이인데, all은 하나라도 실패하면 실패가 되고, allSettled는 10개중에 하나가 실패해도 일단 전부 실행은 한다라는 차이점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675959610752&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let promises = []
for(const requestUrl of requestUrls) {
  promises.push(await this.util.httpRequest(url, 'GET'))
}
Promise.allSettled(promises)
for (const result of results) {
    if(result.status === 'fulfilled') {
    // ... 성공
    } else {
    // ... 실패
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼간 저렇게 Promise가 적을때는 상관이 없었습니다. 근데 저는 약 1500개 정도의 Promise를 실행시키려 했는데 에러가 뜨더군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http request를 하는 공용함수를 만들어서&amp;nbsp; 여러개의 url에 'GET' 으로 호출하고 응답값을 받아오는 상황이었는데 한번에 너무 많은 양을 호출하고 처리하려고하니 Promise관련된 에러와 request를 받는 OPEN API 쪽에서도 에러를 내더군요.. 참고로 저는 기상청의 OPEN API를 호출하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 삽집을 하던중, Promise 의 실행을 제어한다는 limit관련 모듈들이 있는걸로 보아 일단 Promise의 실행개수를 줄여야한다고 생각했고, npm에서 제공하는 모듈들을 사용할까 해봤지만 javascript 내부함수로도 구현이 가능할 것 같아서 아래와 같이 바꿔봤습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675959588423&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while(this.requestUrls.length) {
    const results = await Promise.allSettled(this.requestUrls.splice(0, 100).map((url)=&amp;gt;this.util.httpRequest(url, 'GET')))
    for await(const result of results) {
        if(result.status === 'fulfilled') {
        // ... 성공
        } else {
        // ... 실패
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개수를 줄인다고해서 모든 Promise가 fulfiled의 상태, 즉 성공한다는 보장은 없었습니다. 위와같은 경우는 requestUrls의 배열에서 100개씩 추출하여 promise를 만들어내고 그다음에 promise.allSettled 함수를 사용하여 실행하는 구조로 바꾸니 일단 Promise의 관련한 에러는 발생하지 않았습니다. 물론 http 통신에 대한 에러는 종종 나기에 예외처리가 필요하긴 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js의 Worker를 사용하여 퍼포먼스를 더 높일 수도 있다고 생각이 듭니다만 생각보다 OPEN API 쪽에서도 응답에러가 많이 발생하여 일단 이정도로 구현해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>javascript/모던 자바스크립트</category>
      <category>HTTP</category>
      <category>JavaScript</category>
      <category>node.js</category>
      <category>Promise</category>
      <category>Promise bulk</category>
      <category>Promise error</category>
      <category>promise.all</category>
      <category>promise.allSettled</category>
      <category>기상청API</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/80</guid>
      <comments>https://bubobubo003.tistory.com/80#entry80comment</comments>
      <pubDate>Fri, 10 Feb 2023 01:36:11 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 &amp;gt; 코딩테스트 연습 &amp;gt; 해시 &amp;gt; 완주하지 못한 선수</title>
      <link>https://bubobubo003.tistory.com/79</link>
      <description>&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;마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.&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;마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.&lt;/li&gt;
&lt;li&gt;completion의 길이는 participant의 길이보다 1 작습니다.&lt;/li&gt;
&lt;li&gt;참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.&lt;/li&gt;
&lt;li&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;participantcompletionreturn&lt;/p&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&gt;[&quot;leo&quot;, &quot;kiki&quot;, &quot;eden&quot;]&lt;/td&gt;
&lt;td&gt;[&quot;eden&quot;, &quot;kiki&quot;]&lt;/td&gt;
&lt;td&gt;&quot;leo&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[&quot;marina&quot;, &quot;josipa&quot;, &quot;nikola&quot;, &quot;vinko&quot;, &quot;filipa&quot;]&lt;/td&gt;
&lt;td&gt;[&quot;josipa&quot;, &quot;filipa&quot;, &quot;marina&quot;, &quot;nikola&quot;]&lt;/td&gt;
&lt;td&gt;&quot;vinko&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[&quot;mislav&quot;, &quot;stanko&quot;, &quot;mislav&quot;, &quot;ana&quot;]&lt;/td&gt;
&lt;td&gt;[&quot;stanko&quot;, &quot;ana&quot;, &quot;mislav&quot;]&lt;/td&gt;
&lt;td&gt;&quot;mislav&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 #1&lt;br /&gt;&quot;leo&quot;는 참여자 명단에는 있지만, 완주자 명단에는 없기 때문에 완주하지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 #2&lt;br /&gt;&quot;vinko&quot;는 참여자 명단에는 있지만, 완주자 명단에는 없기 때문에 완주하지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 #3&lt;br /&gt;&quot;mislav&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&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;/p&gt;
&lt;pre id=&quot;code_1632288419809&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String solution(String[] participant, String[] completion) {

    HashMap&amp;lt;String, Integer&amp;gt; hashMap = new HashMap&amp;lt;String, Integer&amp;gt;();

    for (String s : participant) {
      if(hashMap.containsKey(s)) {
      	hashMap.put(s,hashMap.get(s).intValue()+1);
      } else {
      	hashMap.put(s, 1);
      }
    }

    for (String s : completion) {
      if(hashMap.get(s) &amp;gt; 0) {
        hashMap.put(s, hashMap.get(s).intValue()-1);
      }
    }

    String answer = &quot;&quot;;
    for (Map.Entry&amp;lt;String, Integer&amp;gt; stringIntegerEntry : hashMap.entrySet()) {
      if(stringIntegerEntry.getValue().equals(1)) {
        answer = stringIntegerEntry.getKey();
      }
    }
    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래머스 알고리즘</category>
      <category>알고리즘</category>
      <category>프로그래머스</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/79</guid>
      <comments>https://bubobubo003.tistory.com/79#entry79comment</comments>
      <pubDate>Wed, 22 Sep 2021 14:27:52 +0900</pubDate>
    </item>
    <item>
      <title>Go lang으로 xml parser 구현</title>
      <link>https://bubobubo003.tistory.com/78</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;golang.jpeg&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;162&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drIM3u/btq0zdNePy2/Fe5t5asfVlF3PDMp3g51Pk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drIM3u/btq0zdNePy2/Fe5t5asfVlF3PDMp3g51Pk/img.jpg&quot; data-alt=&quot;golang&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drIM3u/btq0zdNePy2/Fe5t5asfVlF3PDMp3g51Pk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrIM3u%2Fbtq0zdNePy2%2FFe5t5asfVlF3PDMp3g51Pk%2Fimg.jpg&quot; data-filename=&quot;golang.jpeg&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;162&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;golang&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 언어에서도 흔히 할 수 있는 XML 파일 Parser를 Golang 을 가지고 구현해보았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;전 보통 Node.js나 순수 javascript의 코드를 이용해 소량의 XML 파일을 파싱하여 사용했었는데&amp;nbsp;&lt;/p&gt;
&lt;p&gt;대랑의 XML 파일 분석을 하기에는 싱글스레드 기반인 Node.js 의 처리방식이 부적합하다고 생각하여&amp;nbsp;&lt;/p&gt;
&lt;p&gt;추후에는 Go 의 장점인 고루틴을 활용한 멀티쓰레드 기능을 활용하여 다수의 파일 XML 파일 파싱을 구현해보려 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아직 Golang 에 대한 사용법이 익숙치않아 이 소스코드는 단건 XML 파일에 대한 파싱을 구현하였습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2021-03-20 오후 5.21.47.png&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;314&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwCr2N/btq0AqyhiGn/BgyVJTEPfv5K5ENpU3yO8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwCr2N/btq0AqyhiGn/BgyVJTEPfv5K5ENpU3yO8k/img.png&quot; data-alt=&quot;디렉토리 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwCr2N/btq0AqyhiGn/BgyVJTEPfv5K5ENpU3yO8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwCr2N%2Fbtq0AqyhiGn%2FBgyVJTEPfv5K5ENpU3yO8k%2Fimg.png&quot; data-filename=&quot;스크린샷 2021-03-20 오후 5.21.47.png&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;314&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디렉토리 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;소스코드 구성은 main파일과 parser 파일로 굉장히 단순합니다. main 코드에서 parser 코드에 있는 함수를 호출하는 구조입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[utils/parser.go]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1616228617455&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package utils

import (
	&quot;encoding/xml&quot;
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;os&quot;
	&quot;path/filepath&quot;
)

type Gpx struct {
	XMLName xml.Name `xml:&quot;gpx&quot;`
	Trk     Trk      `xml:&quot;trk&quot;`
}

type Trk struct {
	XMLName xml.Name `xml:&quot;trk&quot;`
	Name    Name     `xml:&quot;name&quot;`
	Trkseg  Trkseg   `xml:&quot;trkseg&quot;`
}

type Name struct {
	Name string `xml:&quot;name&quot;`
}

type Trkseg struct {
	XMLName xml.Name `xml:&quot;trkseg&quot;`
	Trkpt   []Trkpt  `xml:&quot;trkpt&quot;`
}

type Trkpt struct {
	XMLName xml.Name `xml:&quot;trkpt&quot;`
	Lat     string   `xml:&quot;lat,attr&quot;`
	Lon     string   `xml:&quot;lon,attr&quot;`
	Ele     string   `xml:&quot;ele&quot;`
	Time    string   `xml:&quot;time&quot;`
}

func Run() {
	// 프로젝트 root 경로 기준
	p := filepath.Join(&quot;./files&quot;, filepath.Base(&quot;20210320143858.gpx&quot;))
	fp, err := os.Open(p)
	if err != nil {
		panic(err)
	}
	defer fp.Close()

	// xml 파일 읽기
	data, err := ioutil.ReadAll(fp)

	// xml 디코딩
	var Gpx Gpx
	xmlerr := xml.Unmarshal(data, &amp;amp;Gpx)
	if xmlerr != nil {
		panic(xmlerr)
	}

	for index, coord := range Gpx.Trk.Trkseg.Trkpt {
		fmt.Printf(&quot;[index : %d] lat : %s, lon: %s&quot;, index, coord.Lat, coord.Lon)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;샘플 XML 파일의 태그구조가 쪼금 복잡하여 Type 정의가 좀 복잡해보일 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최상단의 Tag부터 최하위 레벨의 태그를 순서대로 Type 정의한다고 생각해주시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Type 정의 후에 파일을 읽고, 반복문을 통해 특정 태그를 print하는 구조입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[main.go]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1616228846113&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package main

import (
	&quot;go_parser/utils&quot;
)

func main() {
	utils.Run()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;main 파일은 parser.go 파일에서 정의한 Run 함수를 호출하는 것이 다입니다. 물론 Golang의 내용을 공부하시면 아시겠지만&lt;/p&gt;
&lt;p&gt;import한 함수를 사용하려면 해당 함수의 맨 앞 문자는 반드시 대문자로 작성하시는 것은 필수입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[소스 전문]&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/JooHyeongLee/go_parser&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/JooHyeongLee/go_parser&lt;/a&gt;&lt;/p&gt;</description>
      <category>Go</category>
      <category>golang</category>
      <category>xml parser</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/78</guid>
      <comments>https://bubobubo003.tistory.com/78#entry78comment</comments>
      <pubDate>Sat, 20 Mar 2021 17:30:35 +0900</pubDate>
    </item>
    <item>
      <title>[Node.js + Vue] 브라우저에서 서버에 있는 파일 다운로드하기</title>
      <link>https://bubobubo003.tistory.com/77</link>
      <description>&lt;p&gt;Node.js와 Vue (물론 기본적인 html도 가능합니다) 를 이용하여 서버단에 있는 파일을 다운로드하는 코드샘플입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적인 express 서버를 띄우는 설정이나 실행환경 셋팅은 제외하였습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[router.js]&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;const&lt;/span&gt;&amp;nbsp;util&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;require(&lt;span style=&quot;color: #63a35c;&quot;&gt;'./util.js'&lt;/span&gt;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;router.get(&lt;span style=&quot;color: #63a35c;&quot;&gt;'/download'&lt;/span&gt;,(req,&amp;nbsp;res,&amp;nbsp;next)&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;util.download(req,&amp;nbsp;res)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;})&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;일단 기본적인 라우터 셋팅입니다. 단순하게 /download 라는 라우터로 진입했을 때 util.js 파일에 있는 download 함수를 실행시켜주는 코드입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[util.js]&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;exports.download&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;(req,&amp;nbsp;res)&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;const&lt;/span&gt;&amp;nbsp;date&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;Date&lt;/span&gt;()&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res.setHeader(&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;Content-disposition&quot;&lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;attachment;&amp;nbsp;filename=&quot;&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;+&lt;/span&gt;&amp;nbsp;date.yyyymmddhhmmss()&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #63a35c;&quot;&gt;'.csv'&lt;/span&gt;);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res.set(&lt;span style=&quot;color: #63a35c;&quot;&gt;'Content-Type'&lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;color: #63a35c;&quot;&gt;'text/csv'&lt;/span&gt;);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fs.createReadStream(`.&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;/&lt;/span&gt;data.csv`)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.pipe(res)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.on(&lt;span style=&quot;color: #63a35c;&quot;&gt;'finish'&lt;/span&gt;,&amp;nbsp;()&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;console&lt;/span&gt;.log(&lt;span style=&quot;color: #63a35c;&quot;&gt;'download&amp;nbsp;complete'&lt;/span&gt;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;})&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;header를 정의해주는 부분을 먼저 작성합니다. 파일 타입과 파일 저장시에 파일명을 시스템 시간으로 정의하였습니다.&lt;/p&gt;
&lt;p&gt;그리고 CreateReadStream을 이용하여 서버에 있는 특정 파일을 다운로드 할 수 있게끔 파일명을 넣어줍니다.&lt;/p&gt;
&lt;p&gt;마지막으로 pipe 메서드를 통해서 해당 스트림을 response로 전송합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;백엔드쪽 작업은 끝났습니다. 이제 간단하게 프론트단에서 버튼과 해당 API를 호출하는 코드를 작성합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[index.vue]&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;12&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;13&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;14&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;15&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;16&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;17&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;18&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;19&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;20&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;21&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;22&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;23&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;24&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;25&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;26&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;button&amp;nbsp;@click&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;download&quot;&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;CSV&amp;nbsp;다운로드&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;/&lt;/span&gt;button&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;/&lt;/span&gt;div&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;/&lt;/span&gt;template&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;export&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;default&lt;/span&gt;&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;name&lt;/span&gt;:&amp;nbsp;&lt;span style=&quot;color: #63a35c;&quot;&gt;'download'&lt;/span&gt;,&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;methods:&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;download()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;try&lt;/span&gt;&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;let&lt;/span&gt;&amp;nbsp;element&amp;nbsp;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;document&lt;/span&gt;.createElement(&lt;span style=&quot;color: #63a35c;&quot;&gt;'a'&lt;/span&gt;);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;element.setAttribute(&lt;span style=&quot;color: #63a35c;&quot;&gt;'href'&lt;/span&gt;,&lt;span style=&quot;color: #63a35c;&quot;&gt;'/download'&lt;/span&gt;);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;element.click();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;catch&lt;/span&gt;(error)&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;console&lt;/span&gt;.log(error)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;/&lt;/span&gt;script&lt;span style=&quot;color: #ff3399;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Vue.js 코드이긴하지만, HTML의 아주 단순한 코드를 옮긴 코드입니다. 화면에 버튼이 있고, 해당 버튼을 누를 때 발생하는 이벤트 함수를 작성한 코드입니다. download 함수에는 단순하게 download 라우터를 호출하는 코드가 전부입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;물론 axios나 form tag 같이 라우터를 호출하는 다른 방식으로 해도 괜찮습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기존 소스에서 해당 기능만 발췌하여 작성하였습니다. 해당 방식으로 구현한 모습은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;file.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;352&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXZPWi/btqNjUMeFnS/d354kYZtfcIlXAYWsSsIIk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXZPWi/btqNjUMeFnS/d354kYZtfcIlXAYWsSsIIk/img.gif&quot; data-alt=&quot;파일 다운로드 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXZPWi/btqNjUMeFnS/d354kYZtfcIlXAYWsSsIIk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bXZPWi/btqNjUMeFnS/d354kYZtfcIlXAYWsSsIIk/img.gif&quot; data-filename=&quot;file.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;352&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파일 다운로드 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;서버에 있는 파일을 클라이언트의 PC에서 다운로드 받을 수 있게 되었습니다. 파일명은 위 서버 코드에서 작성했다시피 시스템 시간의 시분초의 파일명으로 기본셋팅되었습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Node.js/Node.js 실습</category>
      <author>댕댕이발 </author>
      <guid isPermaLink="true">https://bubobubo003.tistory.com/77</guid>
      <comments>https://bubobubo003.tistory.com/77#entry77comment</comments>
      <pubDate>Thu, 12 Nov 2020 19:53:21 +0900</pubDate>
    </item>
  </channel>
</rss>