<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>segyeom_dev.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 28 Jul 2023 05:56:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>segyeom_dev.log</title>
            <url>https://velog.velcdn.com/images/segyeom_dev/profile/19bdde5d-127a-450c-bf9c-225907aefb08/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. segyeom_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/segyeom_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Swagger json 파일 활용하기]]></title>
            <link>https://velog.io/@segyeom_dev/Swagger-json-%ED%8C%8C%EC%9D%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@segyeom_dev/Swagger-json-%ED%8C%8C%EC%9D%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 28 Jul 2023 05:56:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>최근 @nest/swagger 라이브러리를 사용 하며 @Apiquery()로 받는 인자에 대해서는 schema를 작성해 주지 않는 문제가 있어 해결 했던 과정에 대해 써보자  한다.</p>
</blockquote>
<pre><code class="language-ts">// main.ts
  const config = new DocumentBuilder()
    .setTitle(&#39;Cats example&#39;)
    .setDescription(&#39;The cats API description&#39;)
    .setVersion(&#39;1.0&#39;)
    .addTag(&#39;cats&#39;)
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup(&#39;api&#39;, app, document);</code></pre>
<p><a href="https://docs.nestjs.com/openapi/introduction">nest.js 공식 문서</a>에 나와있는 swagger적용 방식이다.</p>
<pre><code class="language-ts">export declare class SwaggerModule {
    private static readonly metadataLoader;
    static createDocument(app: INestApplication, config: Omit&lt;OpenAPIObject, &#39;paths&#39;&gt;, options?: SwaggerDocumentOptions): OpenAPIObject;
    static loadPluginMetadata(metadataFn: () =&gt; Promise&lt;Record&lt;string, any&gt;&gt;): Promise&lt;void&gt;;
    private static serveStatic;
    private static serveDocuments;
    static setup(path: string, app: INestApplication, documentOrFactory: OpenAPIObject | (() =&gt; OpenAPIObject), options?: SwaggerCustomOptions): void;
}
// swagger을 동작시켜 주는 SwaggerModule.setup을 보면 options라는 인자를 받는것이 보인다.

// setup함수의 인자인 path로 지정한 경로에 &#39;-json&#39;을 붙여서 접속 하면 swagger의 json파일을 볼 수 있다. ex) localhost:3000/path-json</code></pre>
<p><img src="https://velog.velcdn.com/images/segyeom_dev/post/efe9f207-edfc-41c0-9963-9e96eddd6f7b/image.png" alt=""></p>
<blockquote>
<p>이 json 파일을 가지고 <a href="https://editor.swagger.io/">https://editor.swagger.io</a> 사이트에서 swagger-ui를 내가 원하는 대로 꾸밀수 있다.
사이트에 접속하여 components부분을 작성해 주고 json파일을 뽑아 낸다.</p>
</blockquote>
<pre><code class="language-ts">  const json: OpenAPIObject = JSON.parse(fs.readFileSync(&quot;./openapi.json&quot;, {encoding: &#39;utf8&#39;}));

  SwaggerModule.setup(&#39;doc&#39;, app, json);</code></pre>
<p>이 후 파일을 읽어 주고 json 형식으로 파싱을 해준 후 setup함수에 인자로 주면 내가 작성한 json파일 형식대로 swagger-ui가 나오는 것을 확인 할 수 있다.</p>
<h2 id="2023-12-15">2023-12-15</h2>
<pre><code class="language-ts">간단히 @ApiExtraModels(Class)
데코레이터를 달아주면 swagger-doc에 잘 추가 된다...</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeORM - IN]]></title>
            <link>https://velog.io/@segyeom_dev/TypeORM-IN</link>
            <guid>https://velog.io/@segyeom_dev/TypeORM-IN</guid>
            <pubDate>Thu, 25 May 2023 08:40:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 진행중인 프로젝트에서 기존에는 가게이름을 검색할 때 필터로 지역은 하나만 선택 가능하게 enum 타입으로 인자를 받아왔지만 팀원의 의견을 통해 여러 지역에서 검색이 가능하게 기능을 수정 해야 되서 enum타입이 아닌 string로 인자를 받아 split후 in절로 검색을 하기로 했다.</p>
</blockquote>
<pre><code class="language-ts">// 기존 코드
const result = await this.storeRepository.createQueryBuilder(&#39;store&#39;)
        .leftJoinAndSelect(&#39;store.store_type&#39;, &#39;store_type&#39;)
        .where(&#39;store.name like :name&#39;, {name: `%${dto.storename ?? &#39;&#39;}%`})
        .andWhere(&#39;store.city_name like :city_name&#39;, {city_name: `%${dto.region ?? &#39;&#39;}%`})
        .getMany()

//수정후 코드
const result = await this.storeRepository.createQueryBuilder(&#39;store&#39;)
        .leftJoinAndSelect(&#39;store.store_type&#39;, &#39;store_type&#39;)
        .where(&#39;store.name like :name&#39;, {name: `%${dto.storename ?? &#39;&#39;}%`})
        .andWhere(&#39;store.city_name IN (:city_name)&#39;, {city_name: regionNames})
        .getMany()</code></pre>
<blockquote>
<p>수정을 하고 테스트를 하는데 자꾸 빈배열이 return 값으로 넘어왔다. 왜 그런가 확인을 하니 IN절은 like절과 다르게 완전히 같은 값이어야 가져오기 때문이었다. 그래서 DB를 확인해 보니 내가 인자로 받아오는 값은 &#39;수성구&#39; 였는데 DB에 저장된 값은 &#39;대구 수성구&#39; 여서 못가져 오는 것이었다...
그래서 DB를 수정하기보다 값을 조회 할 때 인자의 값을 바꾸어 주는 로직을 추가 하였다.</p>
</blockquote>
<pre><code class="language-ts">const storeRegion = dto.region;
        const regionNames = storeRegion.split(&#39;,&#39;);
        regionNames.forEach((name, idx) =&gt; {
            regionNames[idx] = &#39;대구 &#39; + name
        })</code></pre>
<blockquote>
<p>이 후 확인하니 정상적으로 값을 가져오는 것을 확인하였다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - test (cannot find module)]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-test-cannot-find-module</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-test-cannot-find-module</guid>
            <pubDate>Mon, 08 May 2023 07:03:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>테스트 코드 작성 중 발생한 오류에 대해 적어보고자 한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/segyeom_dev/post/8b38ebda-98f1-445f-a0eb-adc1c2a8cd27/image.png" alt=""></p>
<blockquote>
<p>위에서 보이다 싶이 Cannot find module 이라는 에러가 발생했다.
검색 결과 Nest에서 jest를 사용 시 경로 문제가 발생 하는 경우가 있고 package.json 파일을 수정해 주면 에러가 고쳐진다고 해서 수정해 주었다.</p>
</blockquote>
<pre><code class="language-json">// package.json
// 변경 전
  &quot;jest&quot;: {
    &quot;moduleFileExtensions&quot;: [
      &quot;js&quot;,
      &quot;json&quot;,
      &quot;ts&quot;
    ],
    &quot;rootDir&quot;:&quot;src&quot;,
    &quot;testRegex&quot;: &quot;.*\\.spec\\.ts$&quot;,
    &quot;transform&quot;: {
      &quot;^.+\\.(t|j)s$&quot;: &quot;ts-jest&quot;
    },
    &quot;collectCoverageFrom&quot;: [
      &quot;**/*.(t|j)s&quot;
    ],
    &quot;coverageDirectory&quot;: &quot;../coverage&quot;,
    &quot;testEnvironment&quot;: &quot;node&quot;,
    &quot;moduleDirectories&quot;: [&quot;node_modules&quot;, &quot;src&quot;]
  }
// 변경 후
  &quot;jest&quot;: {
    &quot;moduleDirectories&quot;: [
      &quot;node_modules&quot;,
      &quot;src&quot;
    ],
    &quot;moduleFileExtensions&quot;: [
      &quot;js&quot;,
      &quot;json&quot;,
      &quot;ts&quot;
    ],
    &quot;roots&quot;: [
      &quot;src&quot;
    ],
    &quot;testRegex&quot;: &quot;.spec.ts$&quot;,
    &quot;transform&quot;: {
      &quot;^.+\\.(t|j)s$&quot;: &quot;ts-jest&quot;
    },
    &quot;coverageDirectory&quot;: &quot;../coverage&quot;,
    &quot;testEnvironment&quot;: &quot;node&quot;,
    &quot;moduleNameMapper&quot;: {
      &quot;src/(.*)&quot;: &quot;&lt;rootDir&gt;/src/$1&quot;
    }
  }</code></pre>
<p><a href="https://velog.io/@flobeeee/nestJS-cannot-find-module-when-test">참조</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeORM - ManyToOne 컬럼이름 지정 하기]]></title>
            <link>https://velog.io/@segyeom_dev/TypeORM-ManyToOne-%EC%BB%AC%EB%9F%BC%EC%9D%B4%EB%A6%84-%EC%A7%80%EC%A0%95-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@segyeom_dev/TypeORM-ManyToOne-%EC%BB%AC%EB%9F%BC%EC%9D%B4%EB%A6%84-%EC%A7%80%EC%A0%95-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 20 Apr 2023 09:13:29 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-ts">@ManyToOne(() =&gt; User, (user) =&gt; user.store_direction, {nullable: false})
@JoinColumn({name: &#39;user_id&#39;})
user: User</code></pre>
<blockquote>
<p>JoinColumn의 옵션을 통해 외래키 등록시 컬럼의 이름을 지정할 수 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeORM - select문 파라메터 이상 문제]]></title>
            <link>https://velog.io/@segyeom_dev/TypeORM-select%EB%AC%B8-%ED%8C%8C%EB%9D%BC%EB%A9%94%ED%84%B0-%EC%9D%B4%EC%83%81-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@segyeom_dev/TypeORM-select%EB%AC%B8-%ED%8C%8C%EB%9D%BC%EB%A9%94%ED%84%B0-%EC%9D%B4%EC%83%81-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 20 Apr 2023 07:37:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 프로젝트 진행 중 typeorm select문을 실행하는데 파라메터 값이 이상하게 들어가는 문제가 있어 그걸 해결하고자 한다.</p>
</blockquote>
<pre><code class="language-ts">const like = await this.storeLikeRepository.createQueryBuilder(&#39;store_like&#39;)
        .select()
        .where(&#39;store_like.store = :id&#39;, {id: 22080004})
        .andWhere(&#39;store_like.user = :id&#39;, {id: 2})
        .getOne();</code></pre>
<pre><code>query: SELECT `store_like`.`id` AS `store_like_id`, `store_like`.`user_id` AS `store_like_user_id`, `store_like`.`store_id` AS `store_like_store_id` FROM `store_like` `store_like` WHERE `store_like`.`store_id` = ? AND `store_like`.`user_id` = ? -- PARAMETERS: [2,2]</code></pre><p>분명 고정값으로 id를 입력했음에도 불구하고 parameters가 [2,2] 로 들어가고 있다.</p>
<p>현재 유저의 아이디값이 두번 들어가는 문제가 생기고 있다.</p>
<pre><code class="language-ts">const like = await this.storeLikeRepository.createQueryBuilder(&#39;store_like&#39;)
        .select()
        .where(&#39;store_like.store = :store_id&#39;, {store_id})
        .andWhere(&#39;store_like.user = :user_id&#39;, {user_id})
        .getOne();</code></pre>
<blockquote>
<p>where 안에 변수 명을 바꾸어 주었더니 정상적으로 값이 들어간다.
나중에 시간이 될 때 typeorm의 실행 순서 관련 문서를 찾아봐야 겠다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeORM - FUNCTION  .AsText does not exist error]]></title>
            <link>https://velog.io/@segyeom_dev/TypeORM-FUNCTION-.AsText-does-not-exist-error</link>
            <guid>https://velog.io/@segyeom_dev/TypeORM-FUNCTION-.AsText-does-not-exist-error</guid>
            <pubDate>Wed, 12 Apr 2023 10:09:04 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-ts">TypeOrmModule.forRoot({
      type: &#39;mysql&#39;,
      host: process.env.DB_HOST,
      port: 3306,
      username: process.env.DB_USERNAME,
      password: process.env.DB_SECRET,
      database: process.env.DB_NAME,
      synchronize: false,
      autoLoadEntities: true,
      logging: process.env.NODE_ENV === &#39;development&#39; ? true : false,
      legacySpatialSupport: false
    }),</code></pre>
<blockquote>
<p>TypeOrmModule에 legacySpatialSupport: false 속성을 추가하여 해결 </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL 컬럼 속성 변경]]></title>
            <link>https://velog.io/@segyeom_dev/MySQL-%EC%BB%AC%EB%9F%BC-%EC%86%8D%EC%84%B1-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@segyeom_dev/MySQL-%EC%BB%AC%EB%9F%BC-%EC%86%8D%EC%84%B1-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Wed, 12 Apr 2023 07:50:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 진행 중인 프로젝트에서 TypeORM 과 내가 생성한 DB를 사용하다 보니 만들어진 DB의 컬럼의 속성을 변경 해야 하는 경우가 있었다. 나중에도 사용 할 것 같으니 그 내용을 정리한다.</p>
</blockquote>
<h3 id="컬럼명-변경">컬럼명 변경</h3>
<pre><code>alter table 테이블명 change 기존컬럼명 변경컬럼명 컬럼타입;</code></pre><h3 id="컬럼명-디폴트값-변경">컬럼명 디폴트값 변경</h3>
<pre><code>alter table 테이블명 alter column 변경할컬럼명 set default 디폴트값;</code></pre><h3 id="컬럼-추가">컬럼 추가</h3>
<pre><code>ALTER TABLE 테이블이름 ADD 컬럼이름 데이터타입 NULL or NOT NULL;</code></pre><h3 id="컬럼-삭제">컬럼 삭제</h3>
<pre><code>ALTER TABLE 테이블이름 DROP COLUMN 컬럼명;</code></pre><h2 id="참고">참고</h2>
<p><a href="https://prinha.tistory.com/entry/MySQL-%EC%BB%AC%EB%9F%BC%EC%B6%94%EA%B0%80-%EC%BB%AC%EB%9F%BC%EC%9D%98-%EC%9D%B4%EB%A6%84-%EB%B3%80%EA%B2%BD-%EC%BB%AC%EB%9F%BC%EC%82%AD%EC%A0%9C-%EC%BB%AC%EB%9F%BC%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%ED%83%80%EC%9E%85-%EB%B3%80%EA%B2%BD">링크텍스트</a>
<a href="https://juyoung-1008.tistory.com/17">링크텍스트</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeORM - Transactions]]></title>
            <link>https://velog.io/@segyeom_dev/TypeORM-Transactions</link>
            <guid>https://velog.io/@segyeom_dev/TypeORM-Transactions</guid>
            <pubDate>Fri, 31 Mar 2023 07:13:20 GMT</pubDate>
            <description><![CDATA[<p>현재 진행중인 프로젝트에서 게시글을 삭제 할 때 게시글의 id를 배열로 받아와서 처리 중이다.
처음 로직을 짤 때는 Promise.all 만을 사용하여서 처리를 하였는데 생각을 해보니 중간에 이상한 id값이 들어 오는 등 예외상황을 생각해서 처리를 해주어야 할 것 같아 트렌젝션을 추가하기로 하였다.
<a href="https://typeorm.io/transactions">참고 : TypeORM-Transaction</a></p>
<p>현재 코드</p>
<pre><code class="language-ts">await Promise.all(idArr.map(async id =&gt; {
            if(typeof id !== &#39;number&#39;) throw new BadRequestException(&#39;공지사항의 아이디를 정확히 보내주세요.&#39;);

            const result = await this.faqRepository
            .createQueryBuilder()
            .update(Faq)
            .set({deletedAt: new Date()})
            .where(&#39;id = :id&#39;, {id})
            .execute();

            if( result.affected === 0 ) {
                throw new BadRequestException(&#39;존재하지 않는 게시글 입니다.&#39;);
            }
        }))</code></pre>
<p>공식 문서</p>
<pre><code class="language-ts">// create a new query runner
const queryRunner = dataSource.createQueryRunner()

// establish real database connection using our new query runner
await queryRunner.connect()

// now we can execute any queries on a query runner, for example:
await queryRunner.query(&quot;SELECT * FROM users&quot;)

// we can also access entity manager that works with connection created by a query runner:
const users = await queryRunner.manager.find(User)

// lets now open a new transaction:
await queryRunner.startTransaction()

try {
    // execute some operations on this transaction:
    await queryRunner.manager.save(user1)
    await queryRunner.manager.save(user2)
    await queryRunner.manager.save(photos)

    // commit transaction now:
    await queryRunner.commitTransaction()
} catch (err) {
    // since we have errors let&#39;s rollback changes we made
    await queryRunner.rollbackTransaction()
} finally {
    // you need to release query runner which is manually created:
    await queryRunner.release()
}</code></pre>
<p>수정 후 코드</p>
<pre><code class="language-ts">async deleteFaq(idArr: BigInt[]) {
        const queryRunner = this.connection.createQueryRunner();
        await queryRunner.connect(); // 2
        await queryRunner.startTransaction(); // 3
        try {
            await Promise.all(idArr.map(async id =&gt; {        
                const result = await queryRunner.manager.delete(Faq, {id});

                if( result.affected === 0 ) {
                    throw new BadRequestException(&#39;존재하지 않는 게시글 입니다.&#39;);
                };
            }))
          // 삭제 도중 잘못된 id가 들어올 경우 transaction 롤백
        } catch (error) {
            await queryRunner.rollbackTransaction();
            await queryRunner.release();
            throw new BadRequestException(&#39;존재하지 않는 게시글 입니다.&#39;);
        }
  // 모든 아이디가 정상 적일 경우 transaction commit
        await queryRunner.commitTransaction();
        await queryRunner.release();
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - File-upload]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-File-upload</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-File-upload</guid>
            <pubDate>Fri, 24 Mar 2023 09:45:18 GMT</pubDate>
            <description><![CDATA[<p>Multer를 사용해서 파일 업로드를 진행해 보겠다.</p>
<p>Nest.js 공식 홈페이지 : <a href="https://docs.nestjs.kr/techniques/file-upload">https://docs.nestjs.kr/techniques/file-upload</a>
Multer 깃허브 : <a href="https://github.com/expressjs/multer#multeropts">https://github.com/expressjs/multer#multeropts</a></p>
<p>설치</p>
<pre><code>npm i -D @types/multer
npm i multer</code></pre><p>단일 파일 업로드</p>
<pre><code class="language-ts">@Post(&#39;upload&#39;)
@UseInterceptors(FileInterceptor(&#39;file&#39;))
uploadFile(@UploadedFile() file: Express.Multer.File) {
  console.log(file);
}</code></pre>
<blockquote>
<p>@FileInterceptor(fileName, option)</p>
</blockquote>
<ul>
<li>fileName: 프론트에서 보낼 때 필드 이름</li>
<li>option: MulterOptions</li>
</ul>
<p>다중 파일 업로드</p>
<pre><code class="language-ts">@Post(&#39;upload&#39;)
@UseInterceptors(FilesInterceptor(&#39;files&#39;))
uploadFile(@UploadedFiles() files: Array&lt;Express.Multer.File&gt;) {
  console.log(files);
}

@Post(&#39;upload&#39;)
@UseInterceptors(FileFieldsInterceptor([
  { name: &#39;avatar&#39;, maxCount: 1 },
  { name: &#39;background&#39;, maxCount: 1 },
]))
uploadFile(@UploadedFiles() files: { avatar?: Express.Multer.File[], background?: Express.Multer.File[] }) {
  console.log(files);
}

@Post(&#39;upload&#39;)
@UseInterceptors(AnyFilesInterceptor())
uploadFile(@UploadedFiles() files: Array&lt;Express.Multer.File&gt;) {
  console.log(files);
}
</code></pre>
<p>Multer 옵션</p>
<pre><code class="language-ts">    dest?: string;
    /** The storage engine to use for uploaded files. */
    storage?: any;
    /**
     * An object specifying the size limits of the following optional properties. This object is passed to busboy
     * directly, and the details of properties can be found on https://github.com/mscdex/busboy#busboy-methods
     */
    limits?: {...};
    /** Keep the full path of files instead of just the base name (Default: false) */
    preservePath?: boolean;
    fileFilter?(req: any, file: {...}, callback: (error: Error | null, acceptFile: boolean) =&gt; void): void;</code></pre>
<h4 id="dest">dest</h4>
<p>dest 같은경우는 받은 파일을 어디에다 저장 할 지 정하는 옵션이다.</p>
<h4 id="storage">storage</h4>
<p>srotage는 dest보다 더 세밀하게 파일 저장 옵션을 정 할 수 있다.</p>
<h4 id="limits">limits</h4>
<p>파일을 받아 올 때 제한을 걸 수 있다.</p>
<table>
<thead>
<tr>
<th align="left">Key</th>
<th align="center">Description</th>
<th align="right">Default</th>
</tr>
</thead>
<tbody><tr>
<td align="left">fieldNameSize</td>
<td align="center">Max field name size</td>
<td align="right">100 bytes</td>
</tr>
<tr>
<td align="left">fieldSize</td>
<td align="center">Max field value size (in bytes)</td>
<td align="right">1MB</td>
</tr>
<tr>
<td align="left">fields</td>
<td align="center">Max number of non-file fields</td>
<td align="right">Infinity</td>
</tr>
<tr>
<td align="left">fileSize</td>
<td align="center">For multipart forms, the max file size (in bytes)</td>
<td align="right">Infinity</td>
</tr>
<tr>
<td align="left">files</td>
<td align="center">For multipart forms, the max number of file fields</td>
<td align="right">Infinity</td>
</tr>
<tr>
<td align="left">parts</td>
<td align="center">For multipart forms, the max number of parts (fields + files)</td>
<td align="right">Infinity</td>
</tr>
<tr>
<td align="left">headerPairs</td>
<td align="center">For multipart forms, the max number of header key=&gt;value pairs to parse</td>
<td align="right">2000</td>
</tr>
<tr>
<td align="left">#### filterFilter</td>
<td align="center"></td>
<td align="right"></td>
</tr>
<tr>
<td align="left">특정 파일만 받아오게 할 수 있다.</td>
<td align="center"></td>
<td align="right"></td>
</tr>
</tbody></table>
<h4 id="uploadfile-데코레이터에-pipe-등록하기">@UploadFile() 데코레이터에 pipe 등록하기</h4>
<pre><code class="language-ts">import { PipeTransform, Injectable, ArgumentMetadata } from &#39;@nestjs/common&#39;;

@Injectable()
export class FileSizeValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    // &quot;value&quot; is an object containing the file&#39;s attributes and metadata
    const oneKb = 1000;
    return value.size &lt; oneKb;
  }
}

@Post(&#39;file&#39;)
uploadFileAndPassValidation(
  @Body() body: SampleDto,
  // 1kb 보다 용량이 작은 파일만 등록이 가능하다.
  @UploadedFile(FileSizeValidationPipe)
  )
  file: Express.Multer.File,
) {
  return ;
}</code></pre>
<h4 id="uploadfile-데코레이터에-내장-pipe-등록하기">@UploadFile() 데코레이터에 내장 pipe 등록하기</h4>
<p>Nest에는 파일 업로드를 편하게 해주는 내장 pipe를 제공한다. 내장 파이프를 등록해 보자.</p>
<pre><code class="language-ts">@UploadedFile(
  new ParseFilePipe({
    validators: [
      new MaxFileSizeValidator({ maxSize: 1000 }),
      new FileTypeValidator({ fileType: &#39;image/jpeg&#39; }),
    ],
  }),
)
file: Express.Multer.File,</code></pre>
<p>위와 같이 내장 pipe를 활용하여서 파일의 유효성 검사도 할 수 있으며, ParseFilePipeBuilder를 사용하여 유효성 검사를 할 수도 있다.</p>
<pre><code class="language-ts">export declare class ParseFilePipeBuilder {
    private validators;
    addMaxSizeValidator(options: MaxFileSizeValidatorOptions): this;
    addFileTypeValidator(options: FileTypeValidatorOptions): this;
    addValidator(validator: FileValidator): this;
    build(additionalOptions?: Omit&lt;ParseFileOptions, &#39;validators&#39;&gt;): ParseFilePipe;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[uduntu 특정 포트에 실행중인 프로그램 죽이기]]></title>
            <link>https://velog.io/@segyeom_dev/uduntu-%ED%8A%B9%EC%A0%95-%ED%8F%AC%ED%8A%B8%EC%97%90-%EC%8B%A4%ED%96%89%EC%A4%91%EC%9D%B8-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%A3%BD%EC%9D%B4%EA%B8%B0</link>
            <guid>https://velog.io/@segyeom_dev/uduntu-%ED%8A%B9%EC%A0%95-%ED%8F%AC%ED%8A%B8%EC%97%90-%EC%8B%A4%ED%96%89%EC%A4%91%EC%9D%B8-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%A3%BD%EC%9D%B4%EA%B8%B0</guid>
            <pubDate>Thu, 16 Mar 2023 09:04:23 GMT</pubDate>
            <description><![CDATA[<pre><code>// pid 확인하기
$ sudo lsof -i :포트번호

// pid 로 프로세스 죽이기
$ kill -9 pid</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[permission denied 0.0.0.0:80 에러]]></title>
            <link>https://velog.io/@segyeom_dev/permission-denied-0.0.0.080-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@segyeom_dev/permission-denied-0.0.0.080-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Tue, 14 Mar 2023 09:18:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-발생">1. 발생</h2>
<blockquote>
<p>현재 진행중인 프로젝트를 배포하기 위해 aws ubuntuOS에 pm2로 nest앱을 실행 시키니 </p>
<blockquote>
<p>Error: listen EACCES: permission denied 0.0.0.0:80  란 에러가 발생했다.</p>
</blockquote>
</blockquote>
<h2 id="2-해결">2. 해결</h2>
<blockquote>
<p>에러코드는 80포트에 대한 권한이 없다는 말이다.
검색결과 리눅스에서 1024번 이하의 포트를 사용하려면 root 권한이 필요 하다고 한다.</p>
</blockquote>
<p>루트 권한을 주는법</p>
<pre><code>$ sudo su
$ sudo pm2 start dist/main.js</code></pre><h2 id="3-참조">3. 참조</h2>
<p><a href="https://velog.io/@juunghunz/permission-denied-0.0.0.080-%EC%97%90%EB%9F%AC-%EB%B0%9C%EC%83%9D%EC%8B%9C-%ED%95%B4%EA%B2%B0%EB%B2%95">일문학도의 개발자 도전기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[aws 배포]]></title>
            <link>https://velog.io/@segyeom_dev/aws-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@segyeom_dev/aws-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Tue, 14 Mar 2023 08:43:55 GMT</pubDate>
            <description><![CDATA[<h2 id="1-github-푸시">1. github 푸시</h2>
<blockquote>
<p>aws 서버에서 코드를 올릴 때 FTP 소프트웨어로 직접 코드를 옮겨도 되지만 코드 관리를 위해 github에 코드를 올려 놓았으므로 github를 통해 코드를 옮겨 받아보자.</p>
</blockquote>
<h2 id="2-필요-프로그램-설치">2. 필요 프로그램 설치</h2>
<pre><code>- sudo apt-get update 
- sudo apt-get -y upgrade
- sudo apt-get install build-essential
- sudo apt-get install curl
- curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash --
- sudo apt-get install -y nodejs
- sudo apt-get install git
- sudo apt-get install vim</code></pre><h2 id="3-git-설정">3. git 설정</h2>
<pre><code>- touch .gitconfig
- git config --global user.name github닉네임 
- git config --global user.email github이메일

// 변경사항 확인
- git config --global --list
- git clone &lt;프로젝트&gt;</code></pre><blockquote>
<p>현재 git 보안 정책으로 인해 자신의 깃 비밀번호로는 private repository를 clone불가능 
<a href="https://velog.io/@tera_geniel/ec2%EC%97%90%EC%84%9C-git-clone%ED%95%A0-%EB%95%8C-fatal-authentication-failed-for-%EC%97%90%EB%9F%AC-%EB%9C%A8%EB%8A%94-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0">이 주소</a>를 보고 참조 하여 clone 한다.</p>
</blockquote>
<h2 id="4-nest서버-실행">4. nest서버 실행</h2>
<pre><code>- cd &lt;프로젝트&gt;
- npm i
- sudo npm i -g @nestjs/cli
- sudo npm i -g pm2
- vi .env (환경변수 붙여 넣기)
- sudo npm run start:prod</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - Passport-jwt]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-Passport-jwt</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-Passport-jwt</guid>
            <pubDate>Wed, 08 Mar 2023 04:27:33 GMT</pubDate>
            <description><![CDATA[<h2 id="1-설치">1. 설치</h2>
<pre><code class="language-cmd">$ npm i --save @nestjs/passport passport-jwt
$ npm i --save-dev @types/passport-jwt</code></pre>
<h2 id="2-적용">2. 적용</h2>
<h3 id="1-strategy-만들기">1. strategy 만들기</h3>
<pre><code class="language-ts">import { PassportStrategy } from &quot;@nestjs/passport&quot;;
import { Request } from &quot;express&quot;;
import { Strategy } from &quot;passport-jwt&quot;;

export class JwtStrategy extends PassportStrategy(Strategy) {
    constructor() {
        super({
            jwtFromRequest: (req: Request) =&gt; {
                return req.cookies[&#39;access_token&#39;];
            },
            secretOrKey: process.env.JWT_SECRET,
        })

    }

    async validate(payload) {    
        console.log(payload);  
        return payload;
    }
}</code></pre>
<h3 id="2-guard-만들기">2. guard 만들기</h3>
<pre><code class="language-ts">import { Injectable } from &#39;@nestjs/common&#39;;
import { AuthGuard } from &#39;@nestjs/passport&#39;;

@Injectable()
export class JwtAuthGuard extends AuthGuard(&#39;jwt&#39;) {}</code></pre>
<h3 id="3-guard-등록하기">3. guard 등록하기</h3>
<blockquote>
<p>가드는 다른 프로바이더들과 다르게 모듈에 등록하지 않고 contoller에서 @UseGuards() 데코레이터를 사용하여 등록을 한다.</p>
</blockquote>
<pre><code class="language-ts"> @Get(&#39;test&#39;)
    @UseGuards(JwtAuthGuard)
    async test(@Req() req) {
        console.log(req.user);

    }</code></pre>
<blockquote>
<p>등록이 완료 되면 strategy에 따라 jwt에 담긴 내용을 req.user에 담아 준다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - JWT]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-JWT</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-JWT</guid>
            <pubDate>Tue, 07 Mar 2023 11:28:24 GMT</pubDate>
            <description><![CDATA[<h2 id="1-설치">1. 설치</h2>
<pre><code class="language-cmd">$ npm install --save @nestjs/jwt passport-jwt
$ npm install --save-dev @types/passport-jwt</code></pre>
<h2 id="2-적용">2. 적용</h2>
<h3 id="1-jwt-module-등록">1. jwt module 등록</h3>
<pre><code class="language-ts">import { Module } from &#39;@nestjs/common&#39;;
import { JwtModule } from &#39;@nestjs/jwt&#39;;
import { AuthService } from &#39;./auth.service&#39;;

@Module({
  imports: [
    JwtModule.register({
      secret: process.env.JWT_SECRET,
      signOptions: { expiresIn: &#39;1y&#39; },
    }),
  ],
  providers: [AuthService],
  exports: [AuthService]
})
export class AuthModule {}</code></pre>
<h3 id="2-authservicets">2. auth.service.ts</h3>
<pre><code class="language-ts">import { Injectable, UnauthorizedException } from &#39;@nestjs/common&#39;;
import { JwtService } from &#39;@nestjs/jwt&#39;;
import * as bcrypt from &#39;bcrypt&#39;;
import { User } from &#39;src/users/users.entity&#39;;
import { UsersRepository } from &#39;src/users/users.repository&#39;;
import { LoginDto } from &#39;./dto/login.dto&#39;;

@Injectable()
export class AuthService {
    constructor(
        private jwtService: JwtService,
        private readonly usersRepository: UsersRepository
    ){}

    async jwtLogIn(dto: LoginDto) {
        const {email, password} = dto;

        const user: User = await this.usersRepository.findUserByEmail(email);
        console.log(user);

        if(!user) {
            throw new UnauthorizedException(&#39;이메일과 비밀번호를 확인해주세요.&#39;);
        }

        const isPasswordValidated: boolean = await bcrypt.compare(password, user.password);
        console.log(isPasswordValidated);

        if(!isPasswordValidated) {
            throw new UnauthorizedException(&#39;이메일과 비밀번호를 확인해주세요.&#39;);
        }
        const payload = {
            email: email, sub: user.id, user_type: user.user_type
        }
        return {access_token: this.jwtService.sign(payload)}
    }
}</code></pre>
<h3 id="3-순환종속성-해결">3. 순환종속성 해결</h3>
<blockquote>
<p>현재 user.module가 auth.module를 import하고 auth.module이 user.module을 import하고 있어서 순환종속성에 걸려 &quot;Nest cannot create the module instance&quot;에러를 내고 있다.
<a href="https://docs.nestjs.com/fundamentals/circular-dependency#moduleref-class-alternative">Nest.js공식문서</a>에 따르면 forwardRef()를 사용하라고 나와있다. </p>
</blockquote>
<pre><code class="language-ts">// auth.module
@Module({
  imports: [
    JwtModule.register({
      secret: process.env.JWT_SECRET,
      signOptions: { expiresIn: &#39;1y&#39; },
    }),
    forwardRef(() =&gt; UsersModule)
  ],
  providers: [AuthService],
  exports: [AuthService]
})
export class AuthModule {}

//users.module
@Module({
  imports: [TypeOrmModule.forFeature([User]), EmailModule, forwardRef(() =&gt; AuthModule)],
  providers: [UsersService, UsersRepository],
  controllers: [UsersController],
  exports: [UsersRepository]
})</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - Chching]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-Chching</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-Chching</guid>
            <pubDate>Mon, 06 Mar 2023 07:35:09 GMT</pubDate>
            <description><![CDATA[<h2 id="1-들어가며">1. 들어가며</h2>
<blockquote>
<p>현재 진행중인 프로젝트에서 이메일 인증 기능을 구현 중이다. 이메일 인증 키가 영구적으로 저장되면 DB용량 문제등 여러 부분에서 좋지 않을것이라는 생각이 들어서 특정 시간 동안만 저장을 하고 자동으로 삭제 되게 하기 위해서 캐시를 사용 해보고자 한다.</p>
<blockquote>
<p>처음에는 Redis를 사용하고자 했지만, 서버의 스펙과 그렇게 많은 캐시 정보가 필요하지 않을듯 싶어 Nest 자체의 캐시 메니저를 사용해서 구현해 보고자 한다.</p>
</blockquote>
</blockquote>
<h2 id="2-적용">2. 적용</h2>
<h3 id="1-설치">1. 설치</h3>
<pre><code class="language-cmd">$ npm install cache-manager</code></pre>
<h3 id="2-등록">2. 등록</h3>
<pre><code class="language-ts">import { CacheModule, Module } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;

@Module({
  imports: [CacheModule.register({
      isGlobal: true, // 전역으로 사용가능
    ttl: 5, // 디폴트 만료시간
      max: 10, // 최대 등록가능 개수
  })],
  controllers: [AppController],
})
export class AppModule {}
</code></pre>
<h3 id="3-의존성-주입">3. 의존성 주입</h3>
<pre><code class="language-ts">constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}</code></pre>
<blockquote>
<p>다른 프로바이더, 컨트롤러에서 의존성을 주입받아 사용가능</p>
</blockquote>
<h3 id="4-사용방법">4. 사용방법</h3>
<pre><code class="language-ts">// 값 가져오기
const value = await this.cacheManager.get(&#39;key&#39;);

// 값 등록하기 - 기본 만료시간은 5초
await this.cacheManager.set(&#39;key&#39;, &#39;value&#39;);

// 만료시간 설정하여 등록하기
await this.cacheManager.set(&#39;key&#39;, &#39;value&#39;, 1000);

// 만료시간 비활성화 하여 등록하기
await this.cacheManager.set(&#39;key&#39;, &#39;value&#39;, 0);

// 키 값으로 등록된 값 제거하기
await this.cacheManager.del(&#39;key&#39;);

// 모든 캐시 제거하기
await this.cacheManager.reset();</code></pre>
<h2 id="3-cacheintercepter">3. cacheIntercepter</h2>
<blockquote>
<p>인터셉터를 사용하여 특정 라우트로 들어오는 요청에대한 예외 처리가 가능하다.</p>
</blockquote>
<pre><code class="language-ts">import { CacheModule, Module, CacheInterceptor } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { APP_INTERCEPTOR } from &#39;@nestjs/core&#39;;

@Module({
  imports: [CacheModule.register()],
  controllers: [AppController],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: CacheInterceptor,
    },
  ],
})
export class AppModule {}</code></pre>
<blockquote>
<p><a href="https://github.com/nestjs/nest/blob/ad39c3cfd78e94f191d51ae5799ad9dadb522d38/packages/common/cache/interceptors/cache.interceptor.ts">CacheInterceptor</a> 구현 코드를 보았을 때 trackBy 함수가 실행 컨택스트를 받아서 엔드포인트 값을 키로 가지는 cache를 등록 해준다.
즉, CacheInterceptor를 상속받는 클래스를 생성하고 trackBy 함수를 수정한다면 내가 원하는 내용만 저장 하는 cacheIntercepter를 만들 수 있다.</p>
</blockquote>
<h2 id="4-참고">4. 참고</h2>
<p><a href="https://velog.io/@ypd03008/NestJS-REST-API%EC%97%90-%EC%BA%90%EC%8B%9C">[NestJS] REST API에 캐시</a>
<a href="https://hwasurr.io/nestjs/caching/">Nestjs REST 애플리케이션의 캐시 처리와 캐시 무효화</a>
<a href="https://zuminternet.github.io/nestjs-custom-decorator/">NestJS Custom Caching Decorator 만들기</a>
<a href="https://github.com/nestjs/nest/blob/ad39c3cfd78e94f191d51ae5799ad9dadb522d38/packages/common/cache/interceptors/cache.interceptor.ts">nestjs/github</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - Mailer]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-Mailer</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-Mailer</guid>
            <pubDate>Fri, 03 Mar 2023 05:49:52 GMT</pubDate>
            <description><![CDATA[<h2 id="1-들어가며">1. 들어가며</h2>
<blockquote>
<p>현재 진행중인 프로젝트에서 이메일 인증, 비밀번호 찾기등의 서비스를 위해 메일 서비스가 필요해서 작성하게 되었다.</p>
</blockquote>
<h2 id="2-nestjs-modulesmailer">2. nestjs-modules/mailer</h2>
<blockquote>
<p>메일 서비스를 위해 많이 사용되는nestjs-modules/mailer를 사용하여 구현 하겠다.
<a href="https://nest-modules.github.io/mailer/docs/mailer">docs</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/segyeom_dev/post/96fa80ee-e16f-4b4f-b157-3c379e0f6c03/image.png" alt=""></p>
<h3 id="1-설치">1. 설치</h3>
<pre><code class="language-cmd">npm install --save @nestjs-modules/mailer nodemailer
npm install --save-dev @types/nodemailer

// 보내는 형식을 위한 템플릿 엔진
npm install --save handlebars
#or
npm install --save pug
#or
npm install --save ejs
</code></pre>
<h3 id="2-mailer-모듈-등록">2. Mailer 모듈 등록</h3>
<pre><code class="language-ts">//app.module.ts
import { Module } from &#39;@nestjs/common&#39;;
import { MailerModule } from &#39;@nestjs-modules/mailer&#39;;
import { PugAdapter } from &#39;@nestjs-modules/mailer/dist/adapters/pug.adapter&#39;;

@Module({
  imports: [
    MailerModule.forRootAsync({
      useFactory: () =&gt; ({
        transport: `smtps://${process.env.EMAIL_AUTH_EMAIL}:${process.env.EMAIL_AUTH_PASSWORD}@${process.env.EMAIL_HOST}`,
        defaults: {
          from: `&quot;${process.env.EMAIL_FROM_USER_NAME}&quot; &lt;${process.env.EMAIL_AUTH_EMAIL}&gt;`,
        },
      }),
    }),
    EmailModule,
  ],
})
export class AppModule {}

// email.module.ts
import { Module } from &#39;@nestjs/common&#39;;
import { EmailService } from &#39;./email.service&#39;;

@Module({
  providers: [EmailService],
  exports: [EmailService]
})
export class EmailModule {}

// email.service.ts
import { Injectable } from &#39;@nestjs/common&#39;;
import { MailerService } from &#39;@nestjs-modules/mailer&#39;;
import { Logger } from &#39;winston&#39;;

@Injectable()
export class EmailService {
    constructor(private readonly mailerService: MailerService) {}
    private logger = new Logger();
    sendAuthCode(email: string): void {
        console.log(email);

        this.mailerService
            .sendMail({
                to: email, // list of receivers
                subject: &#39;인증 이메일 입니다.&#39;, // Subject line
                text: &#39;안녕하세요&#39;, // plaintext body
                html: &#39;&lt;b&gt;welcome&lt;/b&gt;&#39;, // HTML body content
            })
            .then(() =&gt; {
                console.log(&quot;성공&quot;);      
            })
            .catch((err) =&gt; {
                this.logger.error(err)
                console.log(err);
            });
    }
}</code></pre>
<h3 id="3-구글-이메일-인증">3. 구글 이메일 인증</h3>
<blockquote>
<p>실행 결과</p>
</blockquote>
<pre><code class="language-cmd">Error: Invalid login: 535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8  https://support.google.com/mail/?p=BadCredentials a4-20020aa78644000000b005a8ba70315bsm635106pfo.6 - gsmtp</code></pre>
<p>내용을 보자면 사용자 이름 그리고 비밀번호가 승인되지 않는다며 밑에 주소로 가서 이유를 찾으라고 되어있다.
<img src="https://velog.velcdn.com/images/segyeom_dev/post/6806ccea-771f-49d2-a0a9-6d56c2c69d36/image.png" alt="">
대충 내용을 보니 내 앱이 보안 등급이 낮아서 일어나는 일인거 같다. 해결해 보자.</p>
<blockquote>
<p><a href="https://support.google.com/mail/?p=BadCredentials">https://support.google.com/mail/?p=BadCredentials</a> 주소로 접속 하여서 2단계 인증을 설정하고, 앱 비밀번호를 사용해 주면된다.</p>
</blockquote>
<h2 id="3-결과">3. 결과</h2>
<p><img src="https://velog.velcdn.com/images/segyeom_dev/post/bd6bf6f4-b859-4881-8c61-7abe1f0375ef/image.png" alt=""></p>
<blockquote>
<p>정상적으로 날아 오는 것을 확인가능하다.</p>
</blockquote>
<h2 id="4-참고">4. 참고</h2>
<p><a href="https://blog.naver.com/PostView.naver?blogId=pjt3591oo&amp;logNo=222467283376&amp;redirect=Dlog&amp;widgetTypeCall=true&amp;directAccess=false">https://blog.naver.com/PostView.naver?blogId=pjt3591oo&amp;logNo=222467283376&amp;redirect=Dlog&amp;widgetTypeCall=true&amp;directAccess=false</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - nest g cli 안됨 버그]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-nest-g-cli-%EC%95%88%EB%90%A8-%EB%B2%84%EA%B7%B8</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-nest-g-cli-%EC%95%88%EB%90%A8-%EB%B2%84%EA%B7%B8</guid>
            <pubDate>Thu, 02 Mar 2023 11:11:48 GMT</pubDate>
            <description><![CDATA[<h2 id="1-들어가며">1. 들어가며</h2>
<blockquote>
<p>현재 프로젝트를 진행 중에 이상한 버그를 만났다.
<img src="https://velog.velcdn.com/images/segyeom_dev/post/0c3cff26-50d8-468a-9395-e0b3da0d5e77/image.png" alt="">
<img src="https://velog.velcdn.com/images/segyeom_dev/post/2e093fd9-0b7e-4287-aeeb-761be7da001f/image.png" alt="">
바로 위에 사진과 같이 nest cli 명령어를 사용해서 resource를 생성 할려고 하는데 ok 로그는 뜨고 파일은 생성 안되는 버그이다.
물론 내가 직접 하나하나 폴더, 파일을 생성해도 되지만 이상한 상황을 만났는데 해결해 보고자 노력도 하지않고 그냥 넘기는 것은 별로 마음에 들지않아 일단 해결 할려고 노력해보고자 한다.</p>
</blockquote>
<h2 id="2-해결">2. 해결</h2>
<blockquote>
<p><a href="https://velog.io/@isntkyu/NestJS">이 블로그</a>에 따르면 npx를 사용 하면 된다고 해서 npx를 사용하여 실행하니 잘 실행된다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - Pipe]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-Pipe</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-Pipe</guid>
            <pubDate>Thu, 02 Mar 2023 06:02:53 GMT</pubDate>
            <description><![CDATA[<h2 id="1-파이프란">1. 파이프란?</h2>
<blockquote>
<p>파이프는 @Injecttable() 데코레이터와 PipeTransform 인터페이스를 구현하는 클레스이다.
<img src="https://velog.velcdn.com/images/segyeom_dev/post/2a8a5497-bc8d-461c-850e-5575e1c55cbe/image.png" alt="">
파이프에는 보통 2가지의 목적으로 사용된다.</p>
<blockquote>
<ul>
<li>변환 : 입력 데이터를 원하는 형식으로 변환(예: 문자열에서 정수로)</li>
</ul>
</blockquote>
</blockquote>
<ul>
<li>유효성 검사 : 입력 데이터를 평가하고 유효하지 않은 경우 예외처리.</li>
</ul>
<h2 id="2-내장-파이프">2. 내장 파이프</h2>
<blockquote>
<ul>
<li>ValidationPipe</li>
</ul>
</blockquote>
<ul>
<li>ParseIntPipe</li>
<li>ParseFloatPipe</li>
<li>ParseBoolPipe</li>
<li>ParseArrayPipe</li>
<li>ParseUUIDPipe</li>
<li>ParseEnumPipe</li>
<li>DefaultValuePipe</li>
<li>ParseFilePipe</li>
</ul>
<h2 id="3-class-validator">3. Class Validator</h2>
<h3 id="1-설치">1. 설치</h3>
<pre><code class="language-cmd">$ npm i --save class-validator class-transformer</code></pre>
<h3 id="2-적용">2. 적용</h3>
<pre><code class="language-ts">async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - Exception filters]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-Exception-filters</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-Exception-filters</guid>
            <pubDate>Thu, 02 Mar 2023 04:52:43 GMT</pubDate>
            <description><![CDATA[<h2 id="1-예외-필터란">1. 예외 필터란?</h2>
<blockquote>
<p>Nest 프레임워크는 기본적으로 예외 레이어를 제공한다. 이를 통해 기본 예외처리기가 예외를 처리해 준다.</p>
</blockquote>
<h2 id="2-nest-제공-표준-예외">2. Nest 제공 표준 예외</h2>
<h3 id="1-httpexception">1. HttpException</h3>
<pre><code class="language-ts">@Get()
async findAll() {
  throw new HttpException(&#39;Forbidden&#39;, HttpStatus.FORBIDDEN);
}

// 매개변수
constructor(response: string | Record&lt;string, any&gt;, status: number, options?: HttpExceptionOptions);</code></pre>
<blockquote>
<ul>
<li>response: 응답의 본문</li>
</ul>
</blockquote>
<ul>
<li>status: 에러의 상태를 나타내는 HTTP 상태코드</li>
</ul>
<h3 id="2-httpexception을-상속받는-표준-예외">2. HttpException을 상속받는 표준 예외</h3>
<p><img src="https://velog.velcdn.com/images/segyeom_dev/post/815c43cf-da4b-475f-9952-0ecf12a3a58e/image.png" alt=""></p>
<h2 id="3-예외-필터-만들기">3. 예외 필터 만들기</h2>
<blockquote>
<p>예외 필터는 ExceptionFilter 인터페이스를 구현하는 class로 만들 수 있다.</p>
</blockquote>
<pre><code class="language-ts">import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger } from &#39;@nestjs/common&#39;;
import { Request, Response } from &#39;express&#39;;

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  private logger = new Logger();

  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const res = ctx.getResponse&lt;Response&gt;();
    const req = ctx.getRequest&lt;Request&gt;();
    const status = exception.getStatus();
    const error = exception.getResponse() as
      | string
      | { error: string; statusCode: number; message: string | string[] };

    this.logger.error(`${req.ip} ${req.originalUrl} ${req.method} ${exception}`)
    res
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: req.url,
        error
      });
  }
}</code></pre>
<h2 id="4-예외-필터-적용">4. 예외 필터 적용</h2>
<h3 id="1-특정-엔드포인트">1. 특정 엔드포인트</h3>
<pre><code class="language-ts">@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}</code></pre>
<h3 id="2-특정-컨트롤러">2. 특정 컨트롤러</h3>
<pre><code class="language-ts">@Controller(&#39;cats&#39;)
@UseFilters(HttpExceptionFilter)
export class CatsController {}</code></pre>
<h3 id="3-앱-전체-적용">3. 앱 전체 적용</h3>
<pre><code class="language-ts">async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(HttpExceptionFilter);
  await app.listen(3000);
}
bootstrap();</code></pre>
<h2 id="5-로그-미들웨어를-있는데-예외-필터에서-따로-로그를-확인하는이유">5. 로그 미들웨어를 있는데 예외 필터에서 따로 로그를 확인하는이유.</h2>
<blockquote>
<p>Nest 프레임 워크에서 자체적으로 제공하는 예외 레이어로 인해 사용자의 잘못된 요청을 받거나 서버 문제가 생겼을 때 등 에러가 발생했을 때 에러를 캐치해서 서버가 정상 적으로 돌아가게 한다. 그 이후 미들웨어가 응답으로 날아가는 에러 문자와 에러 코드를 단순히 info형식으로 로그를 보여주고 후 처리 작업을 한다. 이러한 부분을 확실하게 error 로그와 후 처리를 하기위해 커스텀 예외 필터를 추가하여 처리한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js - winston]]></title>
            <link>https://velog.io/@segyeom_dev/Nest.js-winston</link>
            <guid>https://velog.io/@segyeom_dev/Nest.js-winston</guid>
            <pubDate>Thu, 02 Mar 2023 02:39:06 GMT</pubDate>
            <description><![CDATA[<h2 id="1-들어가며">1. 들어가며</h2>
<blockquote>
<p><a href="https://velog.io/@segyeom_dev/Nest.js-Logging">지난이야기를</a> 통해 우리가 만든 로거로 내장 로거를 대채 할 수 있음을 알았다. 하지만 대형 회사가 아닌 개인의 목적으로 개발을 하는데 필요한 기능을 추가한 커스텀 로거를 만드는 일은 매우 귀찮고 어려울 수 있다. 그러므로 Node.js에서 인기 있는 라이브러리인  winston을 활용해 보겠다.</p>
</blockquote>
<h2 id="2-winston">2. winston</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/segyeom_dev/post/10da3ad2-e592-46d3-b78b-2f6d8fdc6bfe/image.png" alt="">
주간 다운로드 횟수가 25만 회가 될 정도로 인기있는 라이브러리 이다. 그러면 설치 후 적용까지 해보자.</p>
</blockquote>
<h3 id="1-설치">1. 설치</h3>
<pre><code>npm install --save nest-winston winston</code></pre><h3 id="2-적용">2. 적용</h3>
<pre><code class="language-ts">// winston.util.ts
// main.ts 코드가 지저분 해지는것을 막기 위한 파일
import { utilities, WinstonModule } from &#39;nest-winston&#39;;
// winston 자체 file 함수를 사용하려고 했지만 typescript에서 moment 작동 안하는 버그로 인해 대체로 사용
import * as winstonDaily from &#39;winston-daily-rotate-file&#39;;
import * as winston from &#39;winston&#39;;

const env = process.env.NODE_ENV;
const logDir = __dirname + &#39;/../../logs&#39;; // log 파일을 관리할 폴더

const fileOption = (level: string) =&gt; {
    return {
        level,
        datePattern: &#39;YYYY-MM-DD&#39;,
        dirname: logDir + `/${level}`,
        filename: `%DATE%.${level}.log`,
        maxFiles: 30, //30일치 로그파일 저장
        zippedArchive: true, // 로그가 쌓이면 압축하여 관리
    };
}

// rfc5424를 따르는 winston만의 log level
// error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6
export const winstonLogger = WinstonModule.createLogger({
  transports: [
    new winston.transports.Console({
      level: env === &#39;production&#39; ? &#39;http&#39; : &#39;silly&#39;,
      // production 환경이라면 http, 개발환경이라면 모든 단계를 로그
      format:
        env === &#39;production&#39;
        // production 환경은 자원을 아끼기 위해 simple 포맷 사용
          ? winston.format.simple() 
          : winston.format.combine(
              winston.format.timestamp(),
              utilities.format.nestLike(&#39;powerfulDaegu&#39;, {
                prettyPrint: true, // nest에서 제공하는 옵션. 로그 가독성을 높여줌
              }),
            ),
    }),

    // info, warn, error 로그는 파일로 관리
    new winstonDaily(fileOption(&#39;info&#39;)),
    new winstonDaily(fileOption(&#39;warn&#39;)),
    new winstonDaily(fileOption(&#39;error&#39;)),
  ],
});

// main.ts
import { NestFactory } from &#39;@nestjs/core&#39;;
import { DocumentBuilder, SwaggerModule } from &#39;@nestjs/swagger&#39;;
import { AppModule } from &#39;./app.module&#39;;
import { winstonLogger } from &#39;./common/utils/winston.util&#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    // winston 추가
    logger: winstonLogger
  });
  await app.listen(3000);
}
bootstrap();</code></pre>
<blockquote>
<p>내장 로거를 winston 로거로 대체하여 부트스트래핑 과정에 winston적용.</p>
</blockquote>
<h3 id="3-middleware-등록">3. middleware 등록</h3>
<pre><code class="language-ts">// logger.middleware.ts
import { Injectable, Logger, NestMiddleware } from &#39;@nestjs/common&#39;;
import { Request, Response, NextFunction } from &#39;express&#39;;

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
    private logger = new Logger();

    use(req: Request, res: Response, next: NextFunction) {
          // 응답할떄 로그를 찍음 
        res.on(&#39;finish&#39;, () =&gt; {
            this.logger.log(`${req.ip} ${req.originalUrl} ${req.method} ${res.statusCode}`);
        });

        next();
    }
}

//app.module.ts
import { MiddlewareConsumer, Module, NestModule } from &#39;@nestjs/common&#39;;
import { LoggerMiddleware } from &#39;./common/middleware/logger.middleware&#39;;

@Module()
export class AppModule implements NestModule {
  // 미들웨어 등록
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(&#39;*&#39;);
  }
}</code></pre>
<h2 id="3-참조">3. 참조</h2>
<p><a href="https://velog.io/@aryang/NestJS-winston%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC">https://velog.io/@aryang/NestJS-winston%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC</a></p>
]]></description>
        </item>
    </channel>
</rss>