<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>bin-lee.log</title>
        <link>https://velog.io/</link>
        <description>🚀 오늘 배운 건 오늘 적자</description>
        <lastBuildDate>Wed, 19 Jan 2022 12:06:32 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>bin-lee.log</title>
            <url>https://images.velog.io/images/bin-lee/profile/37df8cf6-26dd-48df-8f5d-59bdbdbec168/33.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. bin-lee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/bin-lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[NestJS에서 Swagger 사용하기
]]></title>
            <link>https://velog.io/@bin-lee/NestJS%EC%97%90%EC%84%9C-Swagger-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@bin-lee/NestJS%EC%97%90%EC%84%9C-Swagger-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 19 Jan 2022 12:06:32 GMT</pubDate>
            <description><![CDATA[<h1 id="swagger">Swagger</h1>
<p>Swagger는 NestJS 공식 홈페이지에서 소개하는 API 문서 자동화 도구이다. 협업 시에 &#39;어느 엔드 포인트에 들어가서 어떤 메서드를 사용하고, body에는 이런 값이 들어간다&#39;라는 것을 설명해야 할 때가 있다. Swagger를 이용하면 ppt나 notion을 사용했을 때보다 용이하다. </p>
<p>코드를 짜면서 즉각적으로 작성 · 수정할 수 있다는 장점이 있으며 API를 테스트해 볼 수도 있다. </p>
<br>

<h3 id="swagger-설치">Swagger 설치</h3>
<p>express 환경과 fastify 환경에 따라 설치해야 하는 모듈이 다르다. 나는 express를 사용하기 때문에 아래 모듈을 설치했다.</p>
<pre><code>npm install --save @nestjs/swagger swagger-ui-express</code></pre><br>

<h3 id="swagger-사용-1">Swagger 사용 (1)</h3>
<p><code>main</code>에서 <code>SwaggerModule</code> 클래스로 Swagger를 초기화한다. <code>createDocument()</code> 메서드를 사용해서 API 문서를 만들 수 있다. <code>config</code>는 일종의 옵션으로 title, description, version 등을 설정할 수 있다. 접속하는 엔드 포인트는 <code>SwaggerModule.setup()</code>메서드에서 설정한다. <code>http://localhost:3000/docs</code>로 접속할 수 있다. </p>
<pre><code>import { HttpExceptionFilter } from &#39;src/cats/commons/exceptions/http-exception.filter&#39;;
import { NestFactory } from &#39;@nestjs/core&#39;;
import { AppModule } from &#39;./app.module&#39;;
import { ValidationPipe } from &#39;@nestjs/common&#39;;
import { DocumentBuilder, OpenAPIObject, SwaggerModule } from &#39;@nestjs/swagger&#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  app.useGlobalFilters(new HttpExceptionFilter());

  const config = new DocumentBuilder()
    .setTitle(&#39;C.I.C&#39;)
    .setDescription(&#39;cat&#39;)
    .setVersion(&#39;1.0.0&#39;)
    .addTag(&#39;cats&#39;)
    .build();
  const document: OpenAPIObject = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup(&#39;docs&#39;, app, document);

  ⁝ 
  (생략)
</code></pre><p><code>http://localhost:3000/docs</code>에 접속하면 Nest가 내 controller를 해석하여 해당하는 API를 띄워 준다.
<img src="https://images.velog.io/images/bin-lee/post/c992f993-fe0f-4c14-8ef6-a1338f6eaf51/%EC%A0%9C%EB%AA%A9-%EC%97%86%EC%9D%8C-11.jpg" alt=""> 하지만 각각의 API가 무엇을 뜻하는지 즉각적으로 알아보기 힘들다. API에 설명을 달아 주는 건 controller에서 <code>@ApiOperation</code> 데코레이터가 담당한다.</p>
<pre><code> @ApiOperation({ summary: &#39;회원가입&#39; })
  @Post()
  async signUp(@Body() body: CatRequestDto) {
    // ‥ (생략)
 }</code></pre><p>해당하는 API에 summary를 달아 주면 Swagger에서 대응하는 설명이 나타난다.
<img src="https://images.velog.io/images/bin-lee/post/b02a7656-c7ab-4926-b76e-88b6482a9711/%EC%A0%9C%EB%AA%A9-%EC%97%86%EC%9D%8C-124.jpg" alt=""></p>
<br>

<h3 id="swagger-사용-2---request-body-설정">Swagger 사용 (2) - Request body 설정</h3>
<p>Swagger는 API 테스트 기능을 지원하기 때문에 직접 <code>body</code>를 입력하여 테스트해 볼 수 있다. 이때 어떤 <code>body</code>를 써야 하는지, 어떤 객체들을 넘겨 줘야 하는지 알려 주기 위해 또 다른 설정이 필요하다. 이 설정은 DTO, <code>CatRequestDto</code>에서 <code>@ApiProperty</code> 데코레이터로 할 수 있다.</p>
<pre><code>@ApiProperty({
  example: &#39;qwerty@abc.com&#39;,
  description: &#39;email&#39;,
  required: true,
})
// ‥ (생략)</code></pre><p><img src="https://images.velog.io/images/bin-lee/post/8a58b70b-46e9-4611-ba8e-cefd2f06d307/%EC%A0%9C%EB%AA%A9-%EC%97%86%EC%9D%8C-1d.jpg" alt=""> Request body 영역도 잘 뜬다.</p>
<br>

<h3 id="swagger-사용-3---response-설정">Swagger 사용 (3) - Response 설정</h3>
<p>지금까지는 API 설명, Request로 보내야 하는 것을 설정해 줬다. 이제 마지막으로 Response를 보냈을 때 결과값이 어떻게 출력되는지 설정할 차례다. Response 설정은 API 설명을 작성했을 때처럼 controller에서 해 준다. </p>
<p>우선 ResponseDto를 만들기 위해 <code>cat.dto.ts</code> 파일을 생성했다. response는 signUp에 대한 대응으로 readonly인 readOnlyData(버추얼 필드)가 왔기 때문에 <code>cat.dto.ts</code>도 이에 맞는 코드만 작성해 준다. </p>
<p>파일 내 코드는 아래와 같다.</p>
<pre><code>export class ReadOnlyCatDto {
  @ApiProperty({
    example: &#39;3425343334&#39;,
    description: &#39;id&#39;,
  })
  id: string;

  @ApiProperty({
    example: &#39;qwerty@abc.com&#39;,
    description: &#39;email&#39;,
  })
  email: string;

  @ApiProperty({
    example: &#39;김가나&#39;,
    description: &#39;name&#39;,
  })
  name: string;
}
</code></pre><p><code>@ApiResponse</code> 데코레이터로 status 값에 대한 description을 설정할 수 있다.</p>
<pre><code>  @ApiResponse({
    status: 500,
    description: &#39;Server Error...&#39;,
  })
  @ApiResponse({
    status: 200,
    description: &#39;성공&#39;,
    type: ReadOnlyCatDto
  })
  @ApiOperation({ summary: &#39;회원가입&#39; })</code></pre><p>500 error가 발생했을 때와 200으로 성공했을 때 각각 작성해 준다. type에는 지금까지 사용했던 RequestDto가 아닌 Response용 DTO가 와야 한다.
<img src="https://images.velog.io/images/bin-lee/post/1b18fcbf-f733-43f6-835e-1e09cdac8c6c/image.png" alt=""></p>
<br>

<h3 id="swagger-사용-3---보안-설정">Swagger 사용 (3) - 보안 설정</h3>
<p>Swagger 문서는 노출되었을 때 심각한 보안 문제를 초래할 수 있다. 그렇기 때문에 Swagger에 보안을 해 줘야 한다.<code>express-basic-auth</code> 라이브러리를 사용하여 보안 설정을 해 보자.</p>
<pre><code>npm install express-basic-auth</code></pre><pre><code>// main.ts

import * as expressBasicAuth from &#39;express-basic-auth&#39;;</code></pre><p>설치와 import를 마친 후 <code>main.ts</code>에 미들웨어를 추가해 준다.</p>
<pre><code>  app.use(
    [&#39;/docs&#39;, &#39;/docs-json&#39;],
    expressBasicAuth({
      challenge: true,
      users: { [process.env.SWAGGER_USER]: process.env.SWAGGER_PASSWORD },
    }),
  );</code></pre><p><code>SWAGGER_USER</code>는 <code>/docs</code>로 접속을 시도할 때 필요한 아이디, <code>SWAGGER_PASSWORD</code>는 비밀번호로 <code>.env</code>에 환경변수로 저장해 두었다. (노출되면 ❌) 여기까지 설정하면 아래 캡처처럼 진입할 때 로그인을 해야 한다.</p>
<p><img src="https://images.velog.io/images/bin-lee/post/e9b874a6-e2b2-442b-9e75-7604e10d4e7b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT(JSON Web Token)의 구조와 생성]]></title>
            <link>https://velog.io/@bin-lee/JWTJSON-Web-Token%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@bin-lee/JWTJSON-Web-Token%EC%9D%98-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Sat, 15 Jan 2022 12:22:46 GMT</pubDate>
            <description><![CDATA[<h1 id="jwt">JWT</h1>
<p>JWT(JSON Web Token)은 JSON 형식으로 사용자에 대한 정보를 저장하는 웹 토큰이다. 디지털 서명이 되어 있으므로 안전하고 신뢰할 수 있다. 정보를 안전하게 전달할 때, 유저의 권한을 체크할 때 사용하는 유용한 모듈이다.</p>
<p>보통 로그인을 할 때 많이 사용된다. 로그인한 고유 유저를 위한 JWT 토큰을 생성하여 안전하게 로그인하고 또 권한을 확인하는 용도다.</p>
<br>

<h3 id="jwt의-구조">JWT의 구조</h3>
<p><img src="https://images.velog.io/images/bin-lee/post/b3c1717e-ac01-4897-b2c0-eec0e8a47562/JWT.jpg" alt="">
<code>Header</code>, <code>Payload</code>, <code>Signiture</code> 세 파트가 합쳐진 구조를 띈다. </p>
<ol>
<li><p><span style="color: #ff0066"><strong>Header</strong></span>
토큰에 대한 메타 데이터를 포함하고 있다. 메타 데이터란 타입, 해싱 알고리즘, SHA256 등을 뜻한다.</p>
</li>
<li><p><span style="color: #cc33ff"><strong>Payload</strong></span>
실제적으로 담긴 데이터다. 유저 정보, 만료 기간 등이 담겨 있다. 필요한 데이터만 넣는 것이 중요하나, 보안의 위험 때문에 중요한 데이터 삽입은 지양하는 게 좋다.</p>
</li>
<li><p><span style="color: #00ccff"><strong>Signiture</strong></span>
토큰이 보낸 사람에 의해 시크릿키로 서명되었으며 조작되지 않았음을 확인하는 데 사용된다. 위 사진에서 볼 수 있듯이 base64로 인코딩된 <code>Header</code>와 <code>Payload</code> + 서명 알고리즘 + 시크릿키(혹은 퍼블릭키)가 조합되어 생성된다.</p>
</li>
</ol>
<p>이 세 세그먼트가 모여서 하나의 JWT 토큰이 된다.</p>
<br>

<h3 id="jwt의-흐름">JWT의 흐름</h3>
<p><img src="https://images.velog.io/images/bin-lee/post/ad6c4a5f-35ed-4655-85d5-a588cac4125e/jwt2.jpg" alt=""></p>
<br>

<h3 id="jwt-설치">JWT 설치</h3>
<p>Nest 환경에서 JWT의 인증 처리를 더 간편하게 사용하기 위해 <code>Passport</code> 모듈도 같이 설치해 준다. 네 개 모두 설치.</p>
<pre><code>npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt

npm install --save @nestjs/passport passport
npm install --save-dev @types/passport-local</code></pre><br>

<h3 id="jwt-사용---로그인-과정">JWT 사용 - 로그인 과정</h3>
<h4 id="1-jwt-모듈-등록">(1) JWT 모듈 등록</h4>
<p>새로 생성한 <code>auth.module</code>에서 사용하기 때문에 imports에 JwtModule을 넣어 준다.</p>
<pre><code>JwtModule.register({
  secret: `${process.env.JWT_SECRET}`,
  signOptions: { expiresIn: &#39;1y&#39; },
})</code></pre><p><code>secret</code>은 노출되면 안 되는 시크릿 키라서 환경 변수로 따로 저장했다. 이렇게 환경 변수를 이용할 때는 꼭 <code>ConfigModule.forRoot()</code>를 imports 해 줘야 한다. imports 없이 사용하면 찾을 수 없어서 오류가 발생한다.</p>
<p><code>signOptions</code>의 <code>expiresIn</code>는 토큰의 유효 기간이다. 나는 우선 1년으로 지정했다. </p>
<br>

<h4 id="2-passport-모듈-등록">(2) passport 모듈 등록</h4>
<p>다음으로는 <code>auth.module.ts</code>에 passport 모듈을 등록한다. 마찬가지로 imports 해 주면 된다.</p>
<pre><code>PassportModule.register({ defaultStrategy: &#39;jwt&#39;, session: false })</code></pre><p>session을 사용하지 않을 거라서 false로 두었다.</p>
<br>

<h4 id="3-jwtservice-주입">(3) jwtService 주입</h4>
<p>추후 서비스단에서 토큰을 생성하려면 <code>jwtService</code>가 필요하다. <code>jwtService</code>는 별도의 서비스단을 만들지 않고, 사용할 서비스단에 바로 주입하는 방식으로 사용 가능하다. 로그인 서비스를 구현할 <code>Auth.service.ts</code> 파일에 <code>jwtService</code>를 주입했다.</p>
<pre><code>@Injectable()
export class AuthService {
  constructor(
    private readonly userRepository: UserRepository,
    private jwtService: JwtService,
  ) {}</code></pre><br>

<h4 id="4-토큰-생성">(4) 토큰 생성</h4>
<p>로그인에 필요한 유효성 검사를 거쳐 로그인에 성공하면 유저 토큰이 생성되도록 한다.</p>
<pre><code>  const payload = { id: id, sub: user._id };
  return {
    token: this.jwtService.sign(payload),
  };</code></pre><p> 토큰의 payload에는 유니크값인 <code>id</code>와 토큰의 제목(sub)으로 <code>user._id</code> 고유값을 담았다. <code>sign</code>메서드로 payload를 담아서 토큰을 생성한다. 로그인이 성공적으로 완료되면 클라이언트는 이 토큰을 보관한다.</p>
 <br>

<h3 id="jwt-사용---인증-과정">JWT 사용 - 인증 과정</h3>
<h4 id="1-유효한-토큰인지-확인">(1) 유효한 토큰인지 확인</h4>
<pre><code>@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly userRepository: UserRepository) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: `${process.env.JWT_SECRET}`,
      ignoreExpiration: false,
    });
  }</code></pre><p><code>jwt.strategy.ts</code>를 생성하고 다른 곳에서도 인증 과정을 사용할 수 있게끔 <code>@Injectable()</code> 데코레이터를 달아 준다. <code>userRepository</code>를 주입받는 걸 확인할 수 있는데, 인증 요청이 날아온 토큰의 payload 데이터와 DB의 데이터가 상응하는지 확인하고 가져오기 위함이다.</p>
<p>super 안의 과정은 토큰이 Bearer 타입으로 넘어오는 걸 가져와서 시크릿 키로 유효한지 확인하는 것이다. 이때의 시크릿 키는 토큰 생성 시에 사용한 시크릿 키와 그 값이 동일해야 한다. 생성과 인증으로 용도만 다를 뿐이지 시크릿 키 자체가 달라지는 게 아니다.</p>
<br>

<h4 id="2-데이터베이스에서-값-가지고-오기">(2) 데이터베이스에서 값 가지고 오기</h4>
<p>위의 과정으로 유효한 토큰이라는 게 확인되면 <code>validate</code> 메서드에서 데이터베이스 검증 과정을 거쳐 <code>boolean</code> 타입으로 리턴한다.</p>
<pre><code>  async validate(payload: Payload) {
    const user = await this.userRepository.findUserByIdWithoutPwd(payload.sub);

    if (user) {
      return user; // request.user안에 user가 들어감
    } else {
      throw new UnauthorizedException(&#39;접근 오류입니다&#39;);
    }
  }</code></pre><p>payload에는 유저의 id 값이 <code>id</code>로, _id 값이 <code>sub</code>로 저장되어 있다. 둘 다 유니크하기 때문에 어떤 값을 이용하든 단 한 명의 유저를 뽑아낼 수 있다. 데이터베이스와 연결된 <code>userRepository</code>에서 payload의 <code>sub</code>를 이용해 유저를 찾은 후, 유저가 존재하면 그 정보를 <code>user</code> 객체에 담는다. 객체를 리턴해 주면 <code>request</code> 객체 안에서 <code>user</code>를 사용할 수 있게 된다.</p>
<br>

<h4 id="3-모듈에-담기">(3) 모듈에 담기</h4>
<p>위에서 만든 클래스 <code>JwtStrategy</code>를 사용하기 위해 providers에 담아 준다. 다른 곳에서도 사용해야 하므로 exports도 해 준다. </p>
<pre><code>@Module({
  imports: [
    ConfigModule.forRoot(),
    // jwt 모듈 등록
    JwtModule.register({
      secret: `${process.env.JWT_SECRET}`,
      signOptions: { expiresIn: &#39;1y&#39; },
    }),
    // passport 모듈 등록
    PassportModule.register({ defaultStrategy: &#39;jwt&#39;, session: false }),

    // 두 모듈이 서로를 import하고 있기 때문에 순환 모듈 문제를 아래처럼 해결
    forwardRef(() =&gt; UserModule),
  ],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService, JwtStrategy],
})
export class AuthModule {}</code></pre><br> 

<h4 id="4-user-객체-사용하기">(4) user 객체 사용하기</h4>
<p>나는 현재 로그인된 유저가 누구인지 알아보기 위해 <code>user.controller.ts</code> 파일에서 로그인 유저 인증을 필요로 했다. 현재 로그인된 유저는 <code>request</code> 객체에 담겼다고 (2)에서 말했는데, 이 유저를 사용하기 위해 <code>@UseGuards(JwtAuthGuard)</code>를 이용한다. </p>
<p>가드는 인증 미들웨어로, 지정된 경로로 통과할 수 있는 사람과 허용되지 않는 사람을 서버에 알려 준다. 또한 가드가 실행되면 <code>JwtStrategy</code>를 자동으로 실행시켜 인증이 진행되게 도와준다.</p>
<pre><code>  @ApiOperation({ summary: &#39;현재 유저 가지고 오기&#39; })
  @UseGuards(JwtAuthGuard)
  @Get()
  getCurrentUser(@CurrentUser() user) {
    return user.readOnlyUser;
  }</code></pre><p>나는 커스텀 데코레이터를 이용해서 <code>@Req() req</code>로 <code>req.user</code>를 얻지 않고 바로 user라는 파라미터를 가져왔다. <code>@CurrentUser()</code>가 커스텀 데코레이터다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS의 스키마 기본 개념
]]></title>
            <link>https://velog.io/@bin-lee/NestJS%EC%9D%98-%EC%8A%A4%ED%82%A4%EB%A7%88-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@bin-lee/NestJS%EC%9D%98-%EC%8A%A4%ED%82%A4%EB%A7%88-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Mon, 10 Jan 2022 15:05:55 GMT</pubDate>
            <description><![CDATA[<h1 id="스키마">스키마</h1>
<p>몽구스의 모든 것은 스키마에서 파생된다. 각 스키마는 몽고디비 컬렉션에 매핑되고 해당 컬렉션 내의 문서 모양을 정의한다. <strong>스키마는 모델을 정의하는 데 사용</strong>되는데, 모델이란 몽고디비 데이터베이스에서 문서를 만들고 읽는 역할을 한다. 스키마를 만들 때에는 <code>@Schma()</code> 데코레이터를 사용한다.</p>
<pre><code>import { Prop, Schema, SchemaFactory } from &#39;@nestjs/mongoose&#39;;
import { Document, SchemaOptions } from &#39;mongoose&#39;;

const options: SchemaOptions = {
  timestamps: true,
};

@Schema(options)
export class Cat extends Document {   // 몽구스의 Document를 상속받음

export const CatSchema = SchemaFactory.createForClass(Cat);</code></pre><p>위 코드는 <code>cat.schma</code> 파일에 스키마를 데코레이터로 정의한 것이다. Cat이라는 클래스는 <code>SchemaFactory.createForClass</code>를 통해 <code>CatSchema</code>라는 이름의 스키마가 된다. options는 스키마에 대한 옵션인데 timestamps 옵션은 만들어진 시간과 업데이트된 시간을 찍어 준다.</p>
<br>

<h3 id="💫-스키마의-컬럼">💫 스키마의 컬럼</h3>
<p>스키마의 컬럼은 각각의 <code>@Prop</code>으로 정의할 수 있다. 그리고 추가적인 옵션을 설정해 준다. 이를 통해 반드시 필요한 속성인지 여부를 나타내거나 기본값을 지정하거나 할 수 있다. 아래의 컬럼들 중 email, catname, password는 <code>required</code> 속성이 <code>true</code>가 된다. 이미지의 <code>required</code> 값은 <code>false</code>이므로 생략할 수 있다. (기본값이 <code>false</code>이다)</p>
<pre><code>⁝
@Schema(options)
export class Cat extends Document {   // 몽구스의 Document를 상속받음
  @Prop({
        required: true,
        unique: true,
  })
  email: string;

  @Prop({
        required: true,
  })
  catname: number;

  @Prop({
        required: true,
  })
  password: string;

 @Prop()
  imgUrl: string;
}

export const CatSchema = SchemaFactory.createForClass(Cat);</code></pre><h3 id="💫-class-validation">💫 class-validation</h3>
<p>이제 컬럼에 validation을 추가해 줄 차례다. 예를 들어 이메일이 디비에 저장될 때 이메일의 형식이 아닌 경우를 막기 위해 validation을 해 주는 것이다. 이를 위해 라이브러리를 하나 더 설치하자. <code>npm i --save class-validator class-transformer</code>으로 설치 가능하다. class validator의 자세한 사용법은 <a href="https://github.com/typestack/class-validator">여기</a>에서 확인할 수 있다.</p>
<pre><code>⁝
@Schema(options)
export class Cat extends Document {   // 몽구스의 Document를 상속받음
  @Prop({
        required: true,
        unique: true,
  })
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @Prop({
        required: true,
  })
  @IsString()
  @IsNotEmpty()
  name: number;

  @Prop({
        required: true,
  })
  @IsString()
  @IsNotEmpty()
  password: string;

 @Prop()
  imgUrl: string;
}

export const CatSchema = SchemaFactory.createForClass(Cat);</code></pre><p>사용된<code>@IsEmail()</code>, <code>@IsNotEmpty()</code>, <code>@IsString()</code> 데코레이터들이 validator다. </p>
<p>이제 class validation의 원활한 사용을 위해 <code>main</code> 파일에서 <code>app.useGlobalPipes(new ValidationPipe());</code>로 등록까지 마치면 스키마 설계가 끝난다. 💁🏻‍♀️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS 상속으로 재사용성 증가시키기]]></title>
            <link>https://velog.io/@bin-lee/NestJS-%EC%83%81%EC%86%8D%EC%9C%BC%EB%A1%9C-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1-%EC%A6%9D%EA%B0%80%EC%8B%9C%ED%82%A4%EA%B8%B0</link>
            <guid>https://velog.io/@bin-lee/NestJS-%EC%83%81%EC%86%8D%EC%9C%BC%EB%A1%9C-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1-%EC%A6%9D%EA%B0%80%EC%8B%9C%ED%82%A4%EA%B8%B0</guid>
            <pubDate>Sat, 08 Jan 2022 11:44:02 GMT</pubDate>
            <description><![CDATA[<p><code>cats.schema.ts</code>, <code>cats.request.dto.ts</code>, <code>cat.dto.ts</code>는 중복되는 코드들이 있다. class를 사용하면 상속으로 재사용성을 증가시킬 수 있기 때문에 <code>cats.schema.ts</code>의 <code>Cat</code> 클래스를 상속받아 보자.</p>
<pre><code>// cats.schema.ts

@Schema(options)
export class Cat extends Document {
  @ApiProperty({
    example: &#39;abc@naver.com&#39;,
    description: &#39;email&#39;,
    required: true,
  })
  @Prop({
    required: true,
    unique: true,
  })
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @ApiProperty({
    example: &#39;김가나&#39;,
    description: &#39;name&#39;,
    required: true,
  })
  @Prop({
    required: true,
  })
  @IsString()
  @IsNotEmpty()
  name: string;

  @ApiProperty({
    example: &#39;pass4034&#39;,
    description: &#39;password&#39;,
    required: true,
  })
  @Prop({
    required: true,
  })
  @IsString()
  @IsNotEmpty()
  password: string;

  @Prop()
  @IsString()
  imgUrl: string;

  readonly readOnlyData: { id: string; email: string; name: string };
}</code></pre><p>위의 코드를 <code>cats.request.dto.ts</code>가 상속받으면 아래와 같다.</p>
<pre><code>// cats.request.dto.ts

export class CatRequestDto extends Cat {}</code></pre><p><code>cat.dto.ts</code>가 상속받으면 아래와 같다.</p>
<pre><code>// cat.dto.ts

export class ReadOnlyCatDto extends Cat {
  @ApiProperty({
    example: &#39;34230004&#39;,
    description: &#39;id&#39;,
  })
  id: string;
}</code></pre><br>

<p>그런데 이렇게 Cat을 상속받으면 <code>cats.request.dto.ts</code>의 경우 password 값이 포함되어 버린다. 이때 <code>PickType</code>을 사용하면 Cat이라는 클래스에서 필요한 부분만 가져올 수 있다. <code>PickType</code>을 사용한 코드는 각각 아래와 같다.</p>
<pre><code>// cats.request.dto.ts

export class CatRequestDto extends PickType(Cat, [
  &#39;email&#39;,
  &#39;name&#39;,
  &#39;password&#39;,
] as const) {}</code></pre><pre><code>// cat.dto.ts

export class ReadOnlyCatDto extends PickType(Cat, [&#39;email&#39;, &#39;name&#39;] as const) {
  @ApiProperty({
    example: &#39;34230004&#39;,
    description: &#39;id&#39;,
  })
  id: string;
}</code></pre><p>이렇게 하면 객체 지향의 패턴을 잘 활용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[회원가입 서비스 로직 기록 (NestJS)]]></title>
            <link>https://velog.io/@bin-lee/%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%A1%9C%EC%A7%81-%EA%B8%B0%EB%A1%9D-NestJS</link>
            <guid>https://velog.io/@bin-lee/%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%A1%9C%EC%A7%81-%EA%B8%B0%EB%A1%9D-NestJS</guid>
            <pubDate>Fri, 07 Jan 2022 07:23:38 GMT</pubDate>
            <description><![CDATA[<p>회원가입 서비스를 만들 때 service단이 좀 헷갈려서 정리할 겸 포스팅을 작성하려고 한다. 뭘 import하고 뭘 설치하는지 항상 헷갈려서 기록하고자.. 그래도 포스팅을 하면 확실히 정리가 잘되는 것 같다. 아마도.. 😇</p>
<hr>
<p>클라이언트에서 받은 body 데이터를 DTO 처리하고 service단으로 보냈다.  </p>
<pre><code>@Injectable()
export class CatsService {
  signUp(body: CatRequestDto) {
  }
}</code></pre><p>이제 <code>body</code>로 받은 데이터를 유효성 검사와 패스워드 암호화를 걸쳐 DB에 저장하는 과정을 거쳐야 한다. DB에 저장하려면 쿼리를 써야 하므로 스키마를 서비스단 안에서 사용하기 위해 DI를 해 주자. </p>
<pre><code>@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private readonly catModel: Model&lt;Cat&gt;) {}

  signUp(body: CatRequestDto) {
  }
}</code></pre><p><code>constructor(@InjectModel(Cat.name) private readonly catModel: Model&lt;Cat&gt;) {}</code>가 스키마를 사용하기 위해 DI 해 준 코드이다. 그런데 그냥 이렇게만 하면 사용할 수 없다. <code>cat.module</code>에 가서 아래처럼 import를 해 줘야 한다.</p>
<pre><code>@Module({
  imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }])],
})</code></pre><p>이제 <code>Cat</code>모델을 서비스단 안에서 사용 가능하다.</p>
<br>

<h3 id="🍡-signup-로직">🍡 signUp 로직</h3>
<p><code>body</code>에 email, name, password 데이터가 들어오면 유효성 검사, 패스워드 암호화를 거쳐 DB에 저장해야 한다.</p>
<h4 id="1-유효성-검사">(1) 유효성 검사</h4>
<p><code>const isCatExist = await this.catModel.exists({ email });</code>
이메일은 중복되면 안 되므로 중복 검사를 한다. <code>exists</code> 쿼리 메서드는 DB의 email 필드를 검색하고 해당 이메일과 일치하는 값이 존재하는지 검사한 후 <code>Promise&lt;boolean&gt;</code>으로 return 한다. 그리고 이 return 값이 <code>true</code>라면 조건문을 통해 아래와 같은 오류 코드를 발생시킨다.</p>
<pre><code>  if (isCatExist) {
    throw new UnauthorizedException(&#39;해당 메일이 이미 존재합니다.&#39;); 
    // 403 에러를 발생시켜주는 자동화된 클래스
    // throw new HttpException(&#39;해당 메일이 이미 존재합니다.&#39;, 403);와 동일
  }
    }</code></pre><br>

<h4 id="2-비밀번호-암호화">(2) 비밀번호 암호화</h4>
<p>비밀번호 암호화를 위한 bycript 라이브러리가 있다. 암호화, 즉 hash를 해 주는 라이브러리로 별도의 설치가 필요하다. npm으로 아래 두 개를 모두 설치해 준다.</p>
<pre><code>npm i bcrypt
npm i -D @types/bcrypt</code></pre><p>설치 후 <code>import * as bcrypt from &#39;bcrypt&#39;</code> 해 주면 bcrypt를 사용할 수 있다! 이제 <code>body</code>에 있는 password를 bcrypt로 hashing하여 hashedPassword에 할당한다. 암호화된 비밀번호는 <code>hashedPassword</code>에 담겨 있다.</p>
<pre><code>const hashedPassword = await bcrypt.hash(password, 10);</code></pre><br>

<h4 id="3-버추얼-필드">(3) 버추얼 필드</h4>
<p>비밀번호 암호화는 성공했으나, 추후 DB에 저장하면 httpException 필터 형식에 따라 password의 암호화된 값이 노출되어 반환될 것이다. </p>
<pre><code>{
&quot;success&quot;: true,
&quot;data&quot;: {
    &quot;_id&quot;: &quot;~~~&quot;
    &quot;name&quot;: &quot;~~~&quot;
    &quot;password&quot;: 암호화된 값 노출!!
    ⁝
    ⁝
  }
}</code></pre><br>

<p>이를 막기 위해 버추얼 필드를 만들어 주면 된다. 몽구스에서 제공하는 버추얼 필드는 패스워드 값을 숨길 수 있게 해 준다. 실제로 DB에 저장되는 필드는 아니고 비지니스 로직에서 사용할 수 있도록 제공해 주는 가상의 필드다. 이는 <code>schema</code> 단계에서 <code>virtual(&#39;필드네임&#39;)</code> 메소드를 사용하여 작성한다.</p>
<pre><code>CatSchema.virtual(&#39;readOnlyData&#39;).get(function (this: Cat) {
  return {
    id: this.id,
    email: this.email,
    name: this.name,
  };
});</code></pre><p><code>readOnlyData</code>는 필드네임, <code>this</code>는 schema 클래스 <code>Cat</code> 그 자체인 Model이다. return되는 값들은 클라이언트한테 보여줄 데이터로, 패스워드는 노출될 필요가 없기 때문에 제외했다. 버추얼은 가상으로 필터링해서 나간다는 개념으로 이해하면 된다. 단순히 사용자가 보게되는 영역이며 DB에 존재하지 않는다. 때문에 schema <code>class Cat</code> 내부에 추가를 해 줄 때에도 readonly로, <code>readonly readOnlyData</code> 이렇게 추가한다.</p>
<br>

<h4 id="4-db-저장">(4) DB 저장</h4>
<p>유효성 검사 때 <code>catModel</code>의 <code>exists</code> 메서드를 사용했던 것처럼 이번에는 <code>catModel</code>의 <code>create</code> 메서드를 사용한다. <code>create</code> 메서드를 이용하여 DB에 값을 저장할 수 있다. 회원가입 서비스에서 DB에 저장해야 하는 데이터는 email, name, 암호화된 hashedPassword이다. 값들을 object 형식으로 나타내면 아래와 같다.</p>
<pre><code>const cat = await this.catModel.create({
  email,
  name,
  password: hashedPassword,
});

return cat.readOnlyData;</code></pre><p>password가 들어 있지 않은 cat.readOnlyData 버추얼 필드를 return 해 주면 회원가입 서비스 로직 1차적으로 끝! </p>
<br>

<pre><code>@Post()
  async signUp(@Body() body: CatRequestDto) {
    return await this.catModel.signUp(body); // 프로미스를 리턴해서 await</code></pre><p>controller에서 미완성된 코드를 완성시키고 postman에서 실행하면 mongoDB Compass에 데이터가 들어가 있는 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS의 DTO 기본 개념]]></title>
            <link>https://velog.io/@bin-lee/NestJS%EC%9D%98-DTO-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@bin-lee/NestJS%EC%9D%98-DTO-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Fri, 07 Jan 2022 00:37:32 GMT</pubDate>
            <description><![CDATA[<h1 id="dtodata-transfer-object">DTO(Data Transfer Object)</h1>
<p>DTO는 Nest의 디자인 패턴 중 하나로, <strong>계층간 데이터 교환을 위한 객체</strong>이다. 즉, <strong>데이터를 송수신할 때의 규격</strong>이라고 생각할 수 있다. Request용 DTO와 Response용 DTO는 View를 위한 클래스가 된다.</p>
<p><img src="https://images.velog.io/images/bin-lee/post/8145e1ad-7fc1-4679-a8a2-656a530e5493/%EB%84%A4%EC%8A%A4%ED%8A%B8-%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-DTO.jpg" alt=""> 위 그림처럼 클라이언트가 body에 실어서 보내는 데이터를 DTO 객체로 만들어서 validation을 하고 타이핑 검사를 한 후 안전하게 Controller로 보낸다. 이 DTO는 Service를 거쳐 DB까지 도착한다.</p>
<br>

<pre><code>class CatRequestDto {
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @IsString()
  @IsNotEmpty()
  password: string;

  @IsString()
  @IsNotEmpty()
  name: string;
}</code></pre><p>src 내 프로젝트 폴더 안에 dto라는 폴더를 만들고 <code>cats.request.dto</code>파일을 만들었다. <code>CatRequestDto</code> 객체에는 회원가입할 때 필요한 정보인 email과 password, name이 있다. 이 <code>CatRequestDto</code>를 controller의 회원가입 메서드 인자에 타이핑해 준다.</p>
<pre><code>... signUP(@Body() body: CatRequestDto) {
  return this.catsService.signUp(body) ...</code></pre><p>이제 body에 실어 보내진 데이터는 DTO 객체로 만들어져서 validation과 타이핑 검사를 통해 안전하게 받아진다. 만약 email을 email 형식으로 작성하지 않았거나 입력하지 않았으면 에러가 발생할 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS와 MongoDB 연결하기 (feat. 환경변수와 Mongoose)]]></title>
            <link>https://velog.io/@bin-lee/NestJS%EC%99%80-MongoDB-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0-feat.-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98%EC%99%80-Mongoose</link>
            <guid>https://velog.io/@bin-lee/NestJS%EC%99%80-MongoDB-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0-feat.-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98%EC%99%80-Mongoose</guid>
            <pubDate>Thu, 06 Jan 2022 01:26:07 GMT</pubDate>
            <description><![CDATA[<p>Nest는 SQl, NoSQL 모든 데이터베이스와 통합할 수 있다. 그런데 유독 nodejs는 NoSQL인 MongoDB를 사용하는 경우가 많다. MongoDB가 JSON과 유사한 BSON 객체로 데이터를 저장한다는 점, MongoDB의 ODM 중 가장 유명한 Mongoose가 nodejs 위에서 동작한다는 점 때문에 그런 것 같다. MongoDB는 계정 생성만 해 두고 사용해 본 적이 없어서 겸사겸사(?) NoSQL도 사용해 볼 겸 MongoDB를 연결해 보려고 한다.</p>
<h4 id="odm">ODM?</h4>
<p>ODM은 <strong>Object Document Mapping</strong>의 줄임말이다. 객체와 문서를 1대1 매칭한다는 뜻이다. Object는 자바스크립트의 객체, Document는 몽고디비의 문서이다. ODB은 문서를 DB에서 조회할 때 자바스크립트 객체로 바꿔 준다. 그리고 몽고디비의 ODM으로 가장 많이 사용되는 게 바로 몽구스다. 🐭</p>
<br>

<h3 id="🧀-몽구스-설치와-몽고db-연결">🧀 몽구스 설치와 몽고DB 연결</h3>
<pre><code>npm install --save @nestjs/mongoose mongoose</code></pre><pre><code>⁝
import { MongooseModule } from &#39;@nestjs/mongoose&#39;;

@Module({
  imports: [CatsModule, MongooseModule.forRoot(&#39;몽고디비URI&#39;)],
⁝</code></pre><p>몽구스 설치가 완료되었으면 몽구스의 module을 <code>app.module</code>에 import 해 주고 몽고디비 URI를 넣어 준다. URI를 얻는 방법은 아래와 같다.</p>
<ol>
<li>몽고디비 <code>cluster</code> 들어가기</li>
<li><code>connect</code> 버튼 클릭</li>
<li><code>Connect your application</code> 버튼을 클릭</li>
<li>이미지의 uri를 복사한다
<img src="https://images.velog.io/images/bin-lee/post/6ba3826f-550c-4e7f-8ced-5d4300e72361/image.png" alt=""></li>
</ol>
<br>

<h3 id="🧀-환경-변수-설정하기">🧀 환경 변수 설정하기</h3>
<p>그런데 URI를 복사하여 <code>MongooseModule.forRoot(&#39;몽고디비URI&#39;)</code>에 붙여넣으면 URI가 날것의 주소 그대로 노출되고 만다. URI는 반드시 노출을 막아야 하기 때문에 환경 변수를 설정해 줄 필요가 있다. </p>
<p>환경 변수 설정을 위해서는 별도의 설치와 import가 필요하다. (공식 홈페이지 TECHNIQUES의 Configuration 참고)</p>
<pre><code>$ npm i --save @nestjs/config</code></pre><pre><code>⁝
import { ConfigModule } from &#39;@nestjs/config&#39;;

@Module({
  imports: [ ConfigModule.forRoot(), MongooseModule.forRoot(&#39;몽고디비URI&#39;)]
⁝</code></pre><p>설치와 import를 마치면 환경변수 관리 파일 <code>.env</code>를 사용할 수 있다. <code>.env</code> 파일은 프로젝트 폴더 가장 상위 루트에서 생성해 주면 된다. 이렇게 생성된 파일에 아까 복사했던 몽고디비 URI 값을 <code>MONGODB_URI = &quot;복사했던 몽고디비 URI 값&quot;</code> 형태로 저장한다. </p>
<p>날것의 URI가 들어 있던 <code>app.module</code> 파일의 <code>MongooseModule.forRoot(&#39;몽고디비URI&#39;)</code> 중 <code>&#39;몽고디비URI&#39;</code> 부분을 <code>process.env.MONGODB_URI</code>로 수정해 준다. 첫 번째 인자 설정을 마쳤으면 이제 두 번째 인자를 넣어 줄 차례다. </p>
<p>완성된 코드로 보면 아래와 같다.</p>
<pre><code>⁝
@Module({
  imports: [
    ConfigModule.forRoot(),
    MongooseModule.forRoot(process.env.MONGODB_URI, {
      useNewUrlParser: true,    // 몽구스에서 필요로 하는 두 번째 인자 -1
      useUnifiedTopology: true,    // 몽구스에서 필요로 하는 두 번째 인자 -2    
    }),
    CatsModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
  ⁝</code></pre><br>

<p>몽고디비 URI 외에도 포트 넘버 등을 환경 변수에 넣어 관리할 수 있다. <code>main</code>에서 사용하고 있는 포트 넘버를 <code>.env</code>에 넣고 설정을 해 보았다. </p>
<pre><code>async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  const PORT = process.env.PORT; // .env에 저장된 포트 넘버
  await app.listen(PORT);
}</code></pre><br>

<h3 id="🧀-console에-몽구스-쿼리-출력-설정">🧀 console에 몽구스 쿼리 출력 설정</h3>
<p>개발할 때 몽구스의 쿼리를 로그로 찍어 줄 수 있다. <code>app.module</code> 파일에 몽구스를 <code>import * as mongoose from &#39;mongoose&#39;;</code>로 import한다. 그리고 AppModule 클래스에서 export 하는 코드 <code>mongoose.set(&#39;debug&#39;, true)</code>를 작성한다. 이렇게 하면 쿼리가 로그로 찍힌다. </p>
<p>하지만 프로덕션 배포를 하게 될 때엔 해당 로그가 필요없다. 개발 모드와 프로덕션 모드를 구분해 주기 위해서 환경변수를 사용해 준다. <code>.env</code>에 <code>MODE = &#39;dev&#39;</code>로 구분해 주는 코드를 작성했다. </p>
<p><code>app.module</code>로 다시 이동해서 <code>processe.env.MODE</code>가 &#39;dev&#39;일 때만 로그가 찍히도록 삼항 조건 연사자를 이용한다. 완성된 클래스 코드와 찍힌 쿼리는 아래와 같다.</p>
<pre><code>export class AppModule implements NestModule {
  private readonly idDev: boolean = process.env.MODE === &#39;dev&#39; ? true : false;
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes(&#39;*&#39;);
    mongoose.set(&#39;debug&#39;, this.idDev);
  }
}</code></pre><pre><code>// 찍힌 쿼리 적기</code></pre><p>개발할 때는 <code>true</code>가, 아닐 때에는 <code>false</code> 되어서 쿼리가 찍히지 않게 된다.</p>
<hr>
<p><strong>느낀점</strong>
포스팅을 정리하면서 환경 변수를 왜 사용하는지에 대한 이해를 할 수 있었다. 노출되면 안 될 중요한 정보들은 환경 변수에 담자. </p>
<p>몽고디비를 연결할 때, 몽구스의 쿼리를 보고자 설정할 때 모두 <code>app.module</code>과 관련이 있다. <code>.env</code>에서 환경변수를 설정하고 <code>app.module</code>로 가자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS 인터셉터(Interceptors) & AOP 패턴]]></title>
            <link>https://velog.io/@bin-lee/NestJS-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0Interceptors-AOP-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@bin-lee/NestJS-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0Interceptors-AOP-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Wed, 05 Jan 2022 04:42:32 GMT</pubDate>
            <description><![CDATA[<h1 id="인터셉터interceptors">인터셉터(Interceptors)</h1>
<p>인터셉터는 의존성 주입이 가능한 <code>@Injectable()</code> 데코레이터 주석이 달린 클래스이며 <code>NestInterceptor</code> 인터페이스를 구현해야 한다.
<img src="https://images.velog.io/images/bin-lee/post/cdd495d8-7573-474c-89ea-d7f0cb82e9aa/image.png" alt=""> 인터셉터는 AOP(Aspect Oriented Programming)  기술에서 영감을 받은 유용한 기능 세트가 있다. 관점 지향 프로그래밍(AOP)은 cross-cutting concern으로 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임이다.
<img src="https://images.velog.io/images/bin-lee/post/37c8953a-94a0-4996-8e25-5bb24cb8b6b4/%EB%AC%B4%EC%A0%9C-1.jpg" alt=""></p>
<p>Request lifecycle에 따르면 request가 들어오고 middleware, guards 그리고 <strong>pre-controller 인터셉트</strong> 순으로 실행된다. controller 실행 전에 시작되는 것이다. 그 이후 pipe, controller, service, 다시 <strong>post-request 인터셉트</strong>가 실행된다. 만약 끝까지 도달 전에 예외가 발생하면 바로 예외 필터로 나간다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS Request lifecycle (생명 주기)]]></title>
            <link>https://velog.io/@bin-lee/NestJS-Request-lifecycle-%EC%83%9D%EB%AA%85-%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@bin-lee/NestJS-Request-lifecycle-%EC%83%9D%EB%AA%85-%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Wed, 05 Jan 2022 04:42:25 GMT</pubDate>
            <description><![CDATA[<p>공식 홈페이지 참고</p>
<hr>
<h3 id="summary">Summary</h3>
<h4 id="in-general-the-request-lifecycle-looks-like-the-following">In general, the request lifecycle looks like the following:</h4>
<p>Incoming request
Globally bound middleware
Module bound middleware
Global guards
Controller guards
Route guards
Global interceptors (pre-controller)
Controller interceptors (pre-controller)
Route interceptors (pre-controller)
Global pipes
Controller pipes
Route pipes
Route parameter pipes
Controller (method handler)
Service (if exists)
Route interceptor (post-request)
Controller interceptor (post-request)
Global interceptor (post-request)
Exception filters (route, then controller, then global)
Server response</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS의 파이프(Pipe)]]></title>
            <link>https://velog.io/@bin-lee/NestJS%EC%9D%98-%ED%8C%8C%EC%9D%B4%ED%94%84Pipe</link>
            <guid>https://velog.io/@bin-lee/NestJS%EC%9D%98-%ED%8C%8C%EC%9D%B4%ED%94%84Pipe</guid>
            <pubDate>Wed, 05 Jan 2022 04:42:20 GMT</pubDate>
            <description><![CDATA[<h1 id="파이프pipe">파이프(Pipe)</h1>
<p>파이프는 <code>@Injectable()</code> 데코레이터 주석이 달린 클래스다. 파이프의 일반적인 사용 사례는 다음과 같다.</p>
<ul>
<li>변환 : 입력 데이터를 원하는 형식으로 변환(예: 문자열에서 정수로)</li>
<li>유효성 검사 : 입력 데이터가 사용자가 정한 기준에 유효하지 않은 경우 예외를 던짐.</li>
</ul>
<p><img src="https://images.velog.io/images/bin-lee/post/af8a9bab-1af6-4c60-b88a-516be65c1636/image.png" alt=""> 파이프는 클라이언트 요청에서 들어오는 데이터를 유효성 검사 및 변환을 수행하여 서버가 원하는 데이터를 얻을 수 있도록 도와주는 클래스다. 하나의 인풋이 들어오면 특정 로직을 걸쳐 아웃풋을 뱉는다. 아웃풋은 또 다음 함수로 들어가서 또 다시 아웃풋을 뱉는다. 각각의 함수들은 자신들의 자리에서 기능을 수행한 후 다음 함수에게 넘겨 주는 역할을 한다. 위 그림상에서 Task B가 필요없어진다면 바로 빼 버릴 수 있다. 이렇게 유지 보수가 편리하다는 장점이 있다.</p>
<p>입력 데이터를 원하는 형식으로 변환하는 &#39;변환&#39; 역할은 미들웨어와 비슷하게 보인다. 미들웨어도 요청과 응답에 변형을 가하는 동작을 한다. 단, 미들웨어는 현재 요청이 어떤 핸들러에서 수행되는지, 어떤 파라미터를 가지고 있는지에 대한 실행 컨텍스트를 알 수 없다. 이 차이점이 곧 파이프를 사용하는 목적과도 같다.</p>
<br>

<h3 id="🌼-바인딩-사용-예시">🌼 바인딩 사용 예시</h3>
<pre><code>  @Get(&#39;:id&#39;)
  getOneCat(@Param() param) {
    console.log(param); / { id: &#39;243&#39; }
    return &#39;one cat&#39;;
  }</code></pre><p><code>http://localhost:3000/cats/243</code>로 요청을 보내면 console에 param 값으로 오브젝트 형식의 <code>{ id: &#39;243&#39; }</code>를 응답한다. 여기에 Param의 인자로 명확한 key값을 알려 주게 되면 파라미터는 id값의 value인 243을 뱉는다.</p>
<pre><code>  @Get(&#39;:id&#39;)
  getOneCat(@Param(&#39;id&#39;) param) {
    console.log(param);     / 243
    console.log(typeof param);  / string
    return &#39;one cat&#39;;
  }</code></pre><p>console에 찍힌 243의 타입은 string이다. 하지만 id 값은 string 보다 number로 사용하는 경우가 많다. string을 number로 바꿔 주는 역할을 파이프가 한다. 그때 사용할 수 있는 게 <code>ParseIntPipe</code>이다.</p>
<pre><code>  @Get(&#39;:id&#39;)
  getOneCat(@Param(&#39;id&#39;, ParseIntPipe) param: number) {
    console.log(param);     / 243
    console.log(typeof param);  / number
    return &#39;one cat&#39;;
  }</code></pre><p>console에 찍힌 타입이 number로 변환된 것을 확인할 수 있다. 만약 동적 라우팅에 abc 같은, number로 변환될 수 없는 값으로 호출이 된다면 예외 처리 메시지(Validation)를 자동으로 띄워 준다.</p>
<br>

<h3 id="🌼-파이프-직접-만들어-보기">🌼 파이프 직접 만들어 보기</h3>
<pre><code>import { Injectable, PipeTransform } from &#39;@nestjs/common&#39;;

@Injectable()
export class PositiveIntPipe implements PipeTransform {
  transform(value: number) {
    return value;
  }
}</code></pre><p>이 파일의 <code>transform()</code> 함수가 위 그림의 Task다. 이 클래스를 <code>@Param(&#39;id&#39;, ParseIntPipe, PositiveIntPipe)</code>에 적용시킬 수 있다. Task A는 <code>ParseIntPipe</code>, Task B는 <code>PositiveIntPipe</code>가 되는 것이다. A에서 나온 결과값은 B의 value로 들어간다. 예를 들어 2.2라는 값이 A를 지나면서 2로 변경되고 이 값을 B가 받는 것이다. 이렇게 파이프들을 쭉 타고 가다 보면 최종 결과값인 param을 받게 된다. </p>
<br>

<pre><code>import { Injectable, PipeTransform, HttpException } from &#39;@nestjs/common&#39;;

@Injectable()
export class PositiveIntPipe implements PipeTransform {
  transform(value: number) {
    if (value &lt; 0) {
      throw new HttpException(&#39;value가 0보다 작습니다.&#39;, 400);
    }
    return value;
  }
}</code></pre><p>이렇게 exception을 낼 수도 있다. <code>http://localhost:3000/cats/-24.3</code> 은 0보다 작다는 예외 처리에 걸려서 작성해 놓은 오류 코드를 낸다. 파이프는 이렇게 동작한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS 예외 처리(Exception filters)]]></title>
            <link>https://velog.io/@bin-lee/NestJS-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%ACException-filters</link>
            <guid>https://velog.io/@bin-lee/NestJS-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%ACException-filters</guid>
            <pubDate>Tue, 04 Jan 2022 07:58:58 GMT</pubDate>
            <description><![CDATA[<h1 id="예외-처리">예외 처리</h1>
<p>Nest에는 모든 예외를 처리하는 자체 예외 레이어를 가지고 있다. 애플리케이션 코드에서 예외 처리를 하지 않아도 알아서 보기 쉬운 형식으로 에러를 변환하여 전송한다.</p>
<br>

<h3 id="🌼-표준-예외-처리">🌼 표준 예외 처리</h3>
<p>각각 400과 500의 에러 코드를 Nest가 보낸 결과는 아래와 같다.</p>
<pre><code>{
  &quot;statusCode&quot;: 404,
  &quot;message&quot;: &quot;Cannot Get /dfesd&quot;,
  &quot;error&quot;: &quot;Not Found&quot;
}</code></pre><pre><code>{
  &quot;statusCode&quot;: 500,
  &quot;message&quot;: &quot;Internal server error&quot;
}</code></pre><p>기본적으로 이 작업은 내장된 전역 예외 필터에 의해 수행된다. 전역 예외 필터는 <code>HttpException</code>의 예외를 다루며, 인식할 수 없는 에러(HttpException도 아니고, HttpException를 상속받지도 않은 에러)의 경우 내장 예외 필터가 위와 같은 기본 JSON 응답을 생성한다.</p>
<p>만약 아래와 같은 라우트 핸들러에서 예외를 던지면 에러를 다음처럼 응답한다.</p>
<pre><code>  @Get()
  getAllCat() {
    throw new HttpException(&#39;api is broken&#39;, 401);
    return &#39;all cat&#39;;
  }</code></pre><pre><code>{
  &quot;statusCode&quot;: 401,
  &quot;message&quot;: &quot;api is broken&quot;
}</code></pre><p><code>statusCode</code>와 <code>message</code>가 강제되어서 출력되는 것을 확인할 수 있다. Nest가 제공하는 대로 예외를 처리할 수도 있지만 서비스의 형태에 따라 에러를 핸들링할 때 커스텀을 할 수도 있다.</p>
<br>

<ul>
<li><code>statusCode</code> : <code>status</code>인수에 제공된 HTTP 상태 코드</li>
<li><code>message</code> : 해당 HTTP 오류에 대한 간략한 설명 </li>
</ul>
<p>기본적으로 JSON 응답 본문에는 위의 두 가지 속성이 포함된다. JSON 응답 본문의 메시지 부분을 재정의하려면 응답 인수에 문자열을 입력하면 된다. 전체 JSON 응답 본문을 재정의하고 싶다면 아래의 코드처럼 응답 인수에 객체를 전달한다. Nest는 전달받은 객체를 직렬화하고 JSON 응답 본문으로 반환한다.</p>
<pre><code>  @Get()
  getAllCat() {
    throw new HttpException({ success: false, message: &#39;api is broken!&#39; });
    return &#39;all cat&#39;;
  }</code></pre><pre><code>{
  &quot;success&quot;: false,
  &quot;message&quot;: &quot;api is broken!&quot;
}</code></pre><p>이렇게 원하는 형식으로 바꿔 커스텀을 하면 오버라이딩되어 출력이 된다.</p>
<br>

<h3 id="🌼-예외-필터">🌼 예외 필터</h3>
<p>예외 처리의 재사용성을 고려하여 하나로 모여 필터링을 걸친 후 response로 반환해 주는 형식으로 예외 필터를 만들 수 있다. 예외 필터를 만들기 위해 공식 홈페이지를 참고하여 <code>http-exceiption.filter.ts</code>파일을 생성했다.</p>
<pre><code>import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from &#39;@nestjs/common&#39;;
import { Request, Response } from &#39;express&#39;;

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse&lt;Response&gt;();
    const request = ctx.getRequest&lt;Request&gt;();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}</code></pre><p>이렇게 생성된 필터를 적용하는 방법에는 두 가지가 있다. <strong>(1)</strong> <code>@UseFilter()</code> 데코레이터로 controller에 각각 적용하는 방법 <strong>(2)</strong> 전역에서 적용하는 방법이 있는데 보통 전역 필터 하나만 가지는 게 더 일반적인 방법이라고 한다.</p>
<h4 id="1-controller에-각각-적용하는-방식">(1) controller에 각각 적용하는 방식</h4>
<pre><code>  @Get()
  @UseFilters(HttpExceptionFilter)
  getAllCat() {
    throw new HttpException(&#39;api is broken&#39;, 401);
    return &#39;all cat&#39;;
  }</code></pre><p><code>@UseFilters(HttpExceptionFilter)</code>를 데코레이터로 넣으면 <code>throw new ~</code>에서 발생된 에러가 <code>HttpExceptionFilter</code>에서 필터링되어 response에 전달된다.</p>
<pre><code>{
   &quot;statusCode&quot;:401,
   &quot;timestamp&quot;:&quot;2022-01-06T02:54:26.555Z&quot;,
   &quot;path&quot;:&quot;/cats&quot;
}</code></pre><p>이전과는 예외 처리의 JSON과는 조금 다르게 찍힌 것을 확인할 수 있다. 이렇게 나타난 이유는 바로 <code>HttpExceptionFilter</code>에서 필터링을 거쳤기 때문이다. 해당 클래스를 보면 <code>response.status(status).json</code>으로 위 세 가지 속성을 받는다. 즉, 여기서 변경하는 것으로 커스텀이 가능하다. </p>
<br>

<p>&#39;api is broken&#39; 메세지를 예외 필터에 포함시키려면 <code>exception.getResponse()</code> 메서드를 이용할 수 있다. &#39;api is broken&#39; 인자가 getResponse로 전달되어 에러 메시지가 찍힌다.</p>
<pre><code>import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from &#39;@nestjs/common&#39;;
import { Request, Response } from &#39;express&#39;;

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse&lt;Response&gt;();
    const request = ctx.getRequest&lt;Request&gt;();
    const status = exception.getStatus();
    const error = exception.getResponse();

    response.status(status).json({
      success: false,
      timestamp: new Date().toISOString(),
      path: request.url,
      error,
    });
  }
}</code></pre><p>error는 key와 value가 똑같기 때문에 생략했다. 이 상태에서 요청을 보내면 아래처럼 에러 코드가 발생한다.</p>
<pre><code>{   
   &quot;success&quot;:false,
   &quot;timestamp&quot;:&quot;2022-01-06T03:06:04.940Z&quot;,
   &quot;path&quot;:&quot;/cats&quot;,
   &quot;error&quot;:&quot;api is broken&quot;
}</code></pre><p>이처럼 controller 내부 라우터 각각에 <code>@UseFilters(HttpExceptionFilter)</code>를 데코레이터로 넣을 수도 있고 아래처럼 <code>@Controller</code> 데코레이터 바로 밑에 딱 하나만 넣어 줄 수도 있다.</p>
<pre><code>@Controller(&#39;cats&#39;)
@UseFilters(HttpExceptionFilter)
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

...</code></pre><p>실행 내용은 당연히 둘이 동일하다.</p>
<br>

<h4 id="2-전역으로-적용하는-방식">(2) 전역으로 적용하는 방식</h4>
<p><code>http-exceiption.filter.ts</code> 파일에 <code>HttpExceptionFilter</code> 클래스를 만드는 것까지는 동일하다.</p>
<pre><code>import { HttpExceptionFilter } from &#39;src/http-exception.filter&#39;;
import { NestFactory } from &#39;@nestjs/core&#39;;
import { AppModule } from &#39;./app.module&#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());  // 전역 필터 적용
  await app.listen(3000);
}
bootstrap();</code></pre><p><code>useGlobalFilters</code>를 추가해 주면 된다. 404 에러는 아래와 같이 뜬다.</p>
<pre><code>{
  &quot;success&quot;:false,
  &quot;timestamp&quot;: &quot;2021-10-04T09:53:19.086Z&quot;,
  &quot;path&quot;: &quot;/cats&quot;,
  &quot;error&quot;: {
    &quot;statusCode&quot;: 404,
    &quot;message&quot;: &quot;Cannot GET /cat324234&quot;,
    &quot;error&quot;: &quot;api is broken&quot;
  }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS에서의 미들웨어]]></title>
            <link>https://velog.io/@bin-lee/NestJS%EC%97%90%EC%84%9C%EC%9D%98-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4</link>
            <guid>https://velog.io/@bin-lee/NestJS%EC%97%90%EC%84%9C%EC%9D%98-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4</guid>
            <pubDate>Tue, 04 Jan 2022 07:58:54 GMT</pubDate>
            <description><![CDATA[<h1 id="미들웨어middleware">미들웨어(middleware)</h1>
<p><img src="https://images.velog.io/images/bin-lee/post/3a8c6117-7e5e-418e-8905-933117ff6a7a/image.png" alt="">미들웨어는 라우트 핸들러 이전에 호출되는 컴포넌트다. 라우트 핸들러란 엔드포인트마다 동작을 수행하는 컴포넌트를 뜻한다. 라우트 핸들러는 요청 경로와 컨트롤러를 매핑해 주고, 미들웨어는 이런 라우트 핸들러 이전에 실행된다. </p>
<p>미들웨어는 다음과 같은 동작을 할 수 있다.</p>
<ul>
<li>어떤 코드든 실행 가능하다</li>
<li>요청과 응답에 변형을 가할 수 있다</li>
<li>요청-응답 생명주기를 끝낼 수 있다</li>
<li>미들웨어는 순서가 있다. 여러 개의 미들웨어를 사용 중이라면 next()로 다음 미들웨어에게 제어권을 전달한다.</li>
</ul>
<p>요청-응답 생명주기를 끝낸다는 것은 (1) 응답을 보내거나 (2) 에러 처리를 하는 것을 뜻한다. 생명주기를 끝내지 않을 때에는 next() 호출로 다음 미들웨어에게 제어권을 주어야 한다.</p>
<br>

<p>미들웨어로 수행하는 작업들은 보통 다음과 같다.</p>
<ul>
<li>쿠키 파싱 : 쿠키를 파싱하여 사용하기 쉬운 데이터 구조로 변경한다.</li>
<li>세션 관리 : 세션 쿠키를 찾고 상태를 조회해서 요청에 세션 정보를 추가한다.</li>
<li>인증/인가 : 사용자가 서비스에 접근 가능한 권한이 있는지 확인한다.</li>
<li>body 파싱 : POST/PUT 요청으로 들어오는 json 타입의 본문 및 파일 스트림과 같은 데이터들을 유형에 따라 해석하여 파라미터에 넣는 작업을 한다.</li>
</ul>
<br>

<h3 id="🌼-cli를-통한-미들웨어-생성과-구현">🌼 CLI를 통한 미들웨어 생성과 구현</h3>
<pre><code>$ nest g middleware 미들웨어명</code></pre><p>일전에 모듈을 생성했던 것과 같은 방식으로 미들웨어를 생성할 수 있다. 생성한 미들웨어의 기본 코드 기반으로 logger를 구현해 봤다.</p>
<pre><code>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.method} ${res.statusCode}`,
        req.originalUrl,
      );
    });
    next();
  }
}</code></pre><p>첫 번째 특징으로는 express에서 봤었던 <code>use</code>와 <code>next()</code>다. 실제로 Nest의 미들웨어는 기본적으로 express 미들웨어와 동일하다. Nest 역시 express의 미들웨어처럼 use를 이용해서 미들웨어를 사용할 수 있다.</p>
<p>두 번째 특징으로는 서비스단에서 봤었던 <code>@Injectable()</code>가 미들웨어에서도 보인다는 점이다. 즉, 의존성 주입으로 모듈단에서 공급자로 취급되었던 서비스단처럼 미들웨어 역시 의존성 주입을 할 수 있다. </p>
<p>이렇게 생성된 미들웨어는 바로 사용할 수 없다. 공급자는 만들어졌으나, <code>app.module</code> 모듈에 보내 주어야(의존성 주입을 해 주어야) 실행할 수 있다. 다만 다른 공급자들처럼 <code>@module()</code> 데코레이터 내에 넣는 게 아니라 모듈 클래스의<code>configure()</code> 메서드를 통해 미들웨어를 설정한다. 공식 홈페이지에서 제공하는 코드를 적용한 예는 다음과 같다.</p>
<pre><code>import { CatsModule } from &#39;./cats/cats.module&#39;;
import { MiddlewareConsumer, Module, NestModule } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { AppService } from &#39;./app.service&#39;;
import { LoggerMiddleware } from &#39;./logger.middleware&#39;;

@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes(&#39;cats&#39;);
  }
}</code></pre><p><code>LoggerMiddleware</code>는 위 코드에서 볼 수 있듯이 의존성 주입이 가능한 제공자(provider)다. 그리고 <code>consumer.apply(LoggerMiddleware)</code> 는 말 그대로 consumer 소비자에게 <code>LoggerMiddleware</code>를 제공해 준다는 뜻이다. <code>NestModule</code>은 약속을 추가해 준 인터페이스를 의미한다.</p>
<p><code>forRoutes(&#39;cats&#39;)</code>은 <code>cats</code> 라우터에 바인딩을 해 주는 것이다. <code>&#39;*&#39;</code> 와일드카드를 넣으면 전체 경로를 뜻하게 된다. 이렇게 <code>.forRoutes</code> 뒤에 오는 경로로 들어오는 요청을 수행하면 <code>logger.middleware</code>의 use 안의 내용이 실행되는 것을 확인할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS 모듈(module)]]></title>
            <link>https://velog.io/@bin-lee/NestJS-%EB%AA%A8%EB%93%88module</link>
            <guid>https://velog.io/@bin-lee/NestJS-%EB%AA%A8%EB%93%88module</guid>
            <pubDate>Mon, 03 Jan 2022 04:34:40 GMT</pubDate>
            <description><![CDATA[<h1 id="모듈module">모듈(module)</h1>
<p>NestJS 공식 사이트에 따르면 모듈은 <code>@Module()</code> 데코레이터가 주석으로 달린 클래스다. <code>@Module()</code> 데코레이터는 메타데이터를 제공하고, 이 메타데이터는 애플리케이션 구조를 구성하는 데 사용한다.</p>
<p><img src="https://images.velog.io/images/bin-lee/post/43bb887f-0aab-401f-b8d1-3b4bfe0b19f4/image.png" alt=""> 일반적으로 모듈은 조그만 클래스나 함수처럼 한 가지 일만 수행하는 소프트웨어 컴포넌트가 아니라, 여러 컴포넌트를 조합하여 작성한 좀 더 큰 작업을 수행하는 단위이다. 즉, 하나의 루트 모듈이 존재하고 이 루트 모듈은 다른 여러 개의 모듈들로 구성된다. 사진처럼 하나의 App Module, 루트 모듈로 향하는 형태이다.</p>
<p>이론적으로 루트 모듈 하나만 존재하는 것도 가능하지만 Nest는 기능별로 모듈을 나눠 사용하는 것을 더 추구한다. 모듈을 쪼개서 사용하는 이유는 여러 모듈에게 각자의 책임을 나누고 응집도를 높이기 위함이다.  </p>
<pre><code>import { CatsModule } from &#39;./cats/cats.module&#39;;
import { Module } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { AppService } from &#39;./app.service&#39;;
import { UsersModule } from &#39;./users/users.module&#39;;

@Module({
  imports: [CatsModule, UsersModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}</code></pre><p><code>app.module.ts</code> 파일(루트 모듈)을 보면 이렇게 여러 모듈이 import되어 있다. 그럼 이제 <code>CatsModule</code>과 <code>UsersModule</code>에서 export한 서비스들을 <code>AppController</code>나 <code>AppService</code>에서 사용할 수 있게 된다. 단, 해당 모듈들은 반드시 export된 상태여야 한다. export되어 있지 않다면 캡슐화로 인해 모듈의 공급자들(서비스)을 주입할 수 없게 된다.</p>
<br>

<h3 id="🌼-cli를-통한-모듈-생성">🌼 CLI를 통한 모듈 생성</h3>
<pre><code>$ nest g &lt;schematic&gt; &lt;name&gt; [options]</code></pre><p>인수에는 다양한 Schematic이 올 수 있다. (참고: <a href="https://docs.nestjs.com/cli/usages">https://docs.nestjs.com/cli/usages</a>) Schematic 중 하나가 module이다. <code>$ nest g module 모듈명</code>으로 생성 가능하다. 모듈명에 제한은 없지만 공식 사이트에서는 복수형으로 짓고 있으니 복수형이 좀 더 좋은 선택(?)이 아닐까 생각해 본다.</p>
<p>명령어 사용을 하지 않고 하나하나 입력해서 모듈을 만들 수도 있지만, <code>$ nest g module</code> CLI 방법으로 모듈을 생성하게 되면 <code>app.module.ts</code> 파일에 생성한 모듈이 자동으로 import된다.</p>
<br>

<h3 id="🌼-모듈과-캡슐화">🌼 모듈과 캡슐화</h3>
<p>모듈은 기본적으로 공급자를 캡슐화한다. 모듈에서 내보낸 공급자는 모듈의 공개 인터페이스 또는 API로 간주할 수 있다. 이 말은 곧 <strong>(1)</strong> 현재 모듈의 일부가 아니거나 <strong>(2)</strong> 가져온 모듈에서 export되지 않은 공급자는 주입할 수 없다. 
<br></p>
<pre><code>import { Module } from &#39;@nestjs/common&#39;;
import { CatsController } from &#39;./cats.controller&#39;;
import { CatsService } from &#39;./cats.service&#39;;

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [],
  // exports: [CatsService], 으로 변경해야 사용 가능
})
export class CatsModule {}</code></pre><p>위의 코드는 <code>cat.module.ts</code>파일의 모듈 코드이다. <code>CatsService</code>가 공급자로 있지만 export가 되어 있지 않은 상태다. 이 경우 해당 공급자는 루트 모듈(<code>app.module.ts</code>)에서 import 되었다 한들 사용이 불가능하다. 캡슐화가 되어 있기 때문에 접근할 수 없는 것이다. 모듈 사용을 위해서는 반드시 <code>CatsService</code>를 export 해 주어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS 공급자(providers)와 의존성 주입(DI)의 관계]]></title>
            <link>https://velog.io/@bin-lee/NestJS-%EA%B3%B5%EA%B8%89%EC%9E%90providers%EC%99%80-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85DI%EC%9D%98-%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@bin-lee/NestJS-%EA%B3%B5%EA%B8%89%EC%9E%90providers%EC%99%80-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85DI%EC%9D%98-%EA%B4%80%EA%B3%84</guid>
            <pubDate>Sun, 02 Jan 2022 06:44:21 GMT</pubDate>
            <description><![CDATA[<h1 id="공급자providers와-의존성-주입di">공급자(providers)와 의존성 주입(DI)</h1>
<pre><code>@Controller(&#39;cats&#39;)
export class AppController {
  constructor(private readonly appService: AppService) {}

  // localhost:3000/cats/info
  @Get(&#39;info/:id/:name&#39;)
  getHello(
    @Req() req: Request,
    @Body() Body,
    @Param() param: { id: string; name: string },
  ): string {
    console.log(param);
    return this.appService.getHello();
  }
}</code></pre><p>NestJS는 클래스 내부에서 생성자로 초기화를 한 다음 이를 (this 초기화 없이) 바로 인자로 받아서 사용한다. 이러한 패턴이 의존성 주입(Injectable) 패턴이다. 의존성 주입은 공급자와 깊은 연관이 있다. <strong>공급자의 주요 특성</strong> 중 하나가 <strong>종속성이 있는 제품들을 주입할 수 있다는 것</strong>이기 때문이다. 즉, controller는 이들을 주입받아서 사용하는 것이다. 또한 <strong>Nest 클래스의 대부분은 공급자로 취급</strong>될 수 있다. (서비스, 리포지토리, 팩토리 등 / 이것들은 종속성을 가진다는 의미와도 같다) 공급자로 취급되는 것들은 <code>@Injectable()</code> 데코레이터로 표현한다. </p>
<p>아래 코드는 <code>AppController</code>의 공급자로 사용되고 있는 <code>app.service.ts</code> 파일 코드다. 의존성 주입 데코레이터가 달린 것을 확인할 수 있다.</p>
<pre><code>import { Injectable } from &#39;@nestjs/common&#39;;

@Injectable()
export class AppService {
  getHello(): string {
    return &#39;Hello World!&#39;;
  }
}
</code></pre><br>

<p><code>AppController</code>를 소비자, <code>appService</code>를 제품이라고 가정할 때, <code>AppController</code>가 공급된 제품을 <code>appService</code>라는 인스턴스로 의존성 주입을 받아서 해당 제품을 사용할 수 있다. 이처럼 제품을 공급해 주는 공급자(providers)는 <code>app.module.ts</code>안에서 확인할 수 있다.ㅤ</p>
<pre><code>@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}</code></pre><p>만약 <code>providers</code> 안에 있는 <code>[AppService]</code>를 제거하면 <code>AppController</code>의 의존성인 <code>appService</code>가 resolve되지 않아 오류가 발생한다. 제품(appService)에 대한 공급자(AppService)를 찾아가고 제품을 받아 소비자(AppController)에게 넘겨주어야 하는데, 제품의 공급자가 없으니 controller에서 제품을 사용할 수 없게 되는 것이다. 관계를 순전히 이해하기 편하게 만들면 아래의 표와 같다.</p>
<table>
<thead>
<tr>
<th align="center">ㅤㅤㅤ공급자ㅤㅤㅤ</th>
<th align="center">ㅤㅤㅤ공급자가 제공해 준 제품ㅤㅤㅤ</th>
<th align="center">ㅤㅤㅤㅤ소비자ㅤㅤㅤㅤ</th>
</tr>
</thead>
<tbody><tr>
<td align="center">AppService</td>
<td align="center">appService</td>
<td align="center">AppController</td>
</tr>
</tbody></table>
<br>

<h3 id="의존성-주입을-사용하는-이유는">의존성 주입을 사용하는 이유는?</h3>
<p>NestJS은 객체 지향 프로그래밍이다. 객체 지향 프로그래밍의 핵심 목표는 실생활과 유사하게 코드를 짜는 것이다. 의존성 주입을 하게 되면 실생활과 같은 공급자-소비자의 관계가 명료하게 나타난다. 이렇게 함으로써 유지보수도 간편해지고 확장성 있게 더 탄탄한 백엔드 애플리케이션을 짤 수 있게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS의 package.json과 controller]]></title>
            <link>https://velog.io/@bin-lee/NestJS%EC%9D%98-package.json%EA%B3%BC-controller</link>
            <guid>https://velog.io/@bin-lee/NestJS%EC%9D%98-package.json%EA%B3%BC-controller</guid>
            <pubDate>Sun, 02 Jan 2022 06:44:16 GMT</pubDate>
            <description><![CDATA[<h1 id="packagejson">package.json</h1>
<p>NestJS의 package.json에는 엄청 많은 확장 모듈이 기본적으로 깔려 있다. dependencies 살펴 보면 그 기능은 다음과 같다.</p>
<pre><code>&quot;dependencies&quot;: {
    &quot;@nestjs/common&quot;: &quot;^8.0.0&quot;,
    &quot;@nestjs/core&quot;: &quot;^8.0.0&quot;,
    &quot;@nestjs/platform-express&quot;: &quot;^8.0.0&quot;,
    &quot;reflect-metadata&quot;: &quot;^0.1.13&quot;,
    &quot;rimraf&quot;: &quot;^3.0.2&quot;,
    &quot;rxjs&quot;: &quot;^7.2.0&quot;
  }</code></pre><ol>
<li><code>@nestjs/common</code> <code>@nestjs/core</code> <code>@nestjs/platform-express</code> : NestJS 안에서 자체적으로 동작하는 라이브러리.</li>
<li><code>reflect-metadata</code> : 데코레이터 사용과 관련된 패키지.</li>
<li><code>rimraf</code> : 지정한 파일이나 폴더를 지우는 rm --rf 명령어를 윈도우에서 사용할 수 있게 해주는 패키지.</li>
<li><code>rxjs</code> : 비동기 및 이벤트 기반 프로그래밍을 작성하기 위한 라이브러리.</li>
</ol>
<br>

<h1 id="controller">controller</h1>
<p>controller(app.controller.ts)는 들어온 <strong>요청을 처리</strong>하고 클라이언트에 <strong>응답을 반환</strong>한다. 클라이언트에서 HTTP request를 보내면 controller가 이를 받아서 응답을 하는 구조다.</p>
<pre><code>@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}</code></pre><p>controller를 들어가 보면 AppController 클래스 안에서 appService를 사용하기 위해 의존성 주입을 한 걸 볼 수 있다. 그래서 해당 클래스에서 appService를 사용할 수 있고, appService 내부 메서드의 return 값이 controller에 내부에서 실행되어 반환된다. </p>
<p>물론 controller에서도 바로 return을 할 수 있지만 Nest는 그것을 지양한다. NestJS는 controller를 비지니스 로직과 구분 짓고 싶어 한다. 즉, controller는 그저 url을 매핑하고, 리퀘스트를 받고, 쿼리나 Body 등의 것들을 넘기는 역할만을 할 뿐이다. <strong>비지니스 로직, 일반적인 실제의 기능 함수는 service단으로 간다.</strong></p>
<p>조금 복잡하지만 유지보수와 가독성에 더 유리하다..고 한다. 😇</p>
<br>

<h3 id="🌼-라우팅-routing">🌼 라우팅 (Routing)</h3>
<p>또한 controller의 데코레이터에서 라우팅을 할 수 있다. (아래처럼)</p>
<pre><code>@Controller(&#39;customers&#39;)
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(&#39;profile&#39;)
  getHello(): string {
    return this.appService.getHello();
  }
}</code></pre><p>라우팅 경로는 <code>@Get</code> 데코레이터의 인자가 된다. 이렇게 라우팅을 설정하면 <code>localhost:포트넘버/customers</code>로 <strong>경로 접두사(prefix)를 지정</strong>할 수 있다. <code>customers</code> 경로 접두사와 데코레이터 <code>@Get(&#39;profile&#39;)</code>을 결합하면 <code>GET /customers/profile</code>로 경로가 매핑된다. 데코레이터에서 경로 접두사를 사용하면 반복적인 코드를 최소화할 수 있고 쉬운 그룹화가 가능하다.</p>
<br>

<h3 id="🌼-요청-객체-request-object">🌼 요청 객체 (Request object)</h3>
<pre><code>import { Controller, Get, Req } from &#39;@nestjs/common&#39;;

  @Get(&#39;profile&#39;)
  getHello(@Req() req: Request): string {
    console.log(req);
    return this.appService.getHello();
  }</code></pre><p>NestJS에서는 req, res 같은 요청 객체를 데코레이터 패턴으로 사용할 수 있다. 전용 데코레이터를 함수 인자에 넣어 주면 되는데, 반드시 import 한 후 사용해야 한다. (아니면 빨간 줄 뜬다) 타입스크립트를 사용할 경우 Request 타입도 지정해 주면 좋다. 이렇게 사용할 수 있는 전용 데코레이터들은 다음과 같다.</p>
<table>
<thead>
<tr>
<th align="center">ㅤㅤㅤ제공하는 전용 데코레이터ㅤㅤㅤ</th>
<th align="center">ㅤ 이들이 나타내는 일반적 개체 목록ㅤ</th>
</tr>
</thead>
<tbody><tr>
<td align="center">@Request(), @Req()</td>
<td align="center">req</td>
</tr>
<tr>
<td align="center">@Response(), @Res()*</td>
<td align="center">res</td>
</tr>
<tr>
<td align="center">@Next()</td>
<td align="center">next</td>
</tr>
<tr>
<td align="center">@Session()</td>
<td align="center">req.session</td>
</tr>
<tr>
<td align="center">@Param(key?: string)</td>
<td align="center">req.params / req.params[key]</td>
</tr>
<tr>
<td align="center">@Body(key?: string)</td>
<td align="center">req.body / req.body[key]</td>
</tr>
<tr>
<td align="center">@Query(key?: string)</td>
<td align="center">req.query / req.query[key]</td>
</tr>
<tr>
<td align="center">@Headers(name?: string)</td>
<td align="center">req.headers / req.headers[name]</td>
</tr>
<tr>
<td align="center">@Ip()</td>
<td align="center">req.ip</td>
</tr>
<tr>
<td align="center">@HostParam()</td>
<td align="center">req.hosts</td>
</tr>
</tbody></table>
<br>

<h3 id="🌼-라우트-파라미터">🌼 라우트 파라미터</h3>
<p>&#39;lee&#39;라는 고객의 프로필을 가지고 오기 위해 <code>@Get(&#39;profile/lee&#39;)</code>와 같이 요청해야 한다고 가정해 보자.</p>
<p>여기서 <code>/lee</code>에 해당되는 부분은 동적으로 구성된다. 즉, 경로 구성에 사용되는 <strong>라우트 파라미터</strong>가 된다. 전달받은 파라미터는 함수 인자에 @Param 데코레이터로 주입(의존성 주입)받을 수 있다. 복수의 라우트 파라미터를 받는 상황이라면 아래 코드처럼 파라미터를 따로따로 받는다. 이때, @Param 데코레이터 괄호 안의 &amp;&amp;는 <del>~</del></p>
<pre><code>  @Get(&#39;profile/:name/:cusNum&#39;)
  getHello(
   @Param(&#39;name&#39;) name: string,
   @Param(&#39;cusNum&#39;) cusNum: number,
  ): string {
    console.log(req);
  }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[express에서의 미들웨어]]></title>
            <link>https://velog.io/@bin-lee/express-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4</link>
            <guid>https://velog.io/@bin-lee/express-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4</guid>
            <pubDate>Fri, 31 Dec 2021 02:40:06 GMT</pubDate>
            <description><![CDATA[<h1 id="미들웨어">미들웨어</h1>
<h4 id="💨-미들웨어의-기본-코드">💨 미들웨어의 기본 코드</h4>
<pre><code>app.use([&#39;실행될 특정 url&#39;](req, res, next) =&gt; {
    모든 요청에 실행하고 싶은 코드;
    next();
})</code></pre><p>코드의 req부터 next까지의 함수를 미들웨어라고 부른다. 이 미들웨어를 use에 장착했다고 생각하면 더 편하다. (use 자체가 미들웨어 ❌)</p>
<h4 id="💨-미들웨어의-특성">💨 미들웨어의 특성</h4>
<p>미들웨어에 작성한 코드는 모든 라우터(메소드와 주소가 있는 것)에서 다 실행된다. express는 기본적으로 위에서 아래로 코드가 실행되지만, <strong>미들웨어는 next()를 반드시 해 줘야만 다음 코드로 넘어간다.</strong></p>
<p>이때 next는 반드시 다음 라우터가 실행된다는 의미가 아니라 순서는 아래로 향하되 요청에 따른 응답을 한다. 만약 next 다음에 와일드카드 라우터가 있을 경우 예상 외의 결과가 발생할 수도 있기 때문에(와일드 카드 라우터보다 아래에 있는 라우터 대신 실행된다는 등) 와일드카드 라우터나 범위가 넓은 라우터들은 라우터의 가장 아래에 둔다.</p>
<h4 id="💨-에러-처리의-미들웨어">💨 에러 처리의 미들웨어</h4>
<p>express는 에러가 발생했을 경우 기본적으로 에러 처리를 해 주지만 노출되는 에러문 구가 보안상 문제될 수도 있기 때문에 따로 에러 처리를 해 준다. 에러 처리 역시 미들웨어다.</p>
<pre><code>app.use((err, req, res, next) =&gt; {
    console.error(err);
    res.[status(500).]send(&#39;에러 메시지&#39;);
})</code></pre><p>에러 미들웨어는 반드시 <code>err</code>, <code>req</code>, <code>res</code>, <code>next</code> 네 개의 매개변수를 모두 작성해야 한다. <code>next</code>가 빠지게 되면 아예 다른 함수로 인지한다. res 뒤에는 기본값으로 <code>.status(200)</code>이 생략되어 있기 때문에 따로 status 값을 지정해 주지 않으면 클라이언트 상에서는 200 코드가 뜬다. 즉, 에러가 아니라고 뜬다. 에러 코드는 오히려 보안상 문제가 일어날 수 있어서🙄 실무에서는 보통 200번대 위주로 사용한다고 한다. 에러 코드는 조심해서 사용할 것.</p>
<p>404 에러의 경우 라우터들 최하단, 에러 미들웨어 위에 새로운 미들웨어를 만들어 404 에러 처리 미들웨어로 사용할 수 있다. 이 경우 에러는 아니고 404 처리 미들웨어 정도로 생각하면 된다.</p>
<pre><code>app.use((req, res, next) =&gt; {
    res.[status(404).]send(&#39;404 error&#39;);
    next();
})</code></pre><h4 id="💨-static-미들웨어">💨 static 미들웨어</h4>
<pre><code>app.use(&#39;/&#39;, express.static(path.join(__dirname, &#39;public&#39;)));
// app.use(&#39;요청 경로&#39;, express.static(path.join(&#39;실제 경로&#39;)));</code></pre><p>static 미들웨어는 정적인 파일들(css, html, js)를 처리할 때 사용된다. 요청 경로와 실제 경로는 다르다. </p>
<p>예를 들어, localhost:3000/bin-lee.html은 요청 경로다. 하지만 실제로 이 html 파일이 public이라는 폴더 안에 있다면 실제 경로는 public/bin-lee.html이 된다.</p>
<p>요청 경로와 실제 경로가 다르기 때문에 클라이언트는 실제 경로를 알 수 없게 된다. 때문에 static을 사용하면 보안 면으로 유리한 면이 있다.</p>
<h4 id="💨-express-session-미들웨어">💨 express-session 미들웨어</h4>
<p>요청마다 개인의 저장 공간을 만들어 주는 게 express-session이다. </p>
<h4 id="💨-미들웨어-순서">💨 미들웨어 순서</h4>
<p>미들웨어 간에는 순서도 중요한데, 요청 주소에 따라 미들웨어가 어디까지 실행되느냐가 달라진다. 순서 배치에 따라 실행될 필요도 없는 미들웨어가 실행되면서 비효율적으로 동작할 수 있다. 정해진 건 없고 서비스에 따라 조정하면 된다.</p>
<p>내가 찾아본 코드는 <code>morgan</code> &gt; <code>static</code> &gt; <code>cookieParser</code> &gt; <code>session</code> &gt; <code>express.json</code> &gt; <code>express.urlencoded</code> &gt; <code>multer</code>의 순서로 사용했었다.</p>
<hr>
<h4 id="에러zip">에러.zip</h4>
<ol>
<li>한 라우터에서 <code>res.send</code>, <code>res.json</code>을 여러 번 보내면 에러 발생한다. 주의하자.</li>
<li>express에서 http 방식인 <code>writeHead</code>와 <code>end</code>를 사용하지 않는 편이 좋다. express 전용인 <code>send</code>를 씁시다.</li>
<li>자바스크립트는 return을 해야 함수가 끝난다. <code>res.json</code> 밑에 console을 찍어도 실행된다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[자주 쓰이는 npm 명령어]]></title>
            <link>https://velog.io/@bin-lee/%EC%9E%90%EC%A3%BC-%EC%93%B0%EC%9D%B4%EB%8A%94-npm-%EB%AA%85%EB%A0%B9%EC%96%B4</link>
            <guid>https://velog.io/@bin-lee/%EC%9E%90%EC%A3%BC-%EC%93%B0%EC%9D%B4%EB%8A%94-npm-%EB%AA%85%EB%A0%B9%EC%96%B4</guid>
            <pubDate>Fri, 31 Dec 2021 02:39:52 GMT</pubDate>
            <description><![CDATA[<p><code>npm outdated</code> : 패키지에 기능 변화가 생겼는지 알 수 있다.
<code>npm uninstall 패키지명</code> : 패키지를 삭제한다.
<code>npm search 패키지명</code> : npm.js 사이트에 있는 패키지 검색 결과를 보여 준다.
<code>npm info 패키지명</code> : 패키지의 세부 정보를 보여 준다.
<code>npm adduser</code> : npm에 로그인한다.
<code>npm whoami</code> : 현재 사용자가 누구인지 알려 준다.
<code>npm logout</code> : 로그인된 npm 계정에서 로그아웃한다.
<code>npm version [major/minor/patch]</code> : package.json의 버전을 올린다.
<code>npm ls 패키지명</code> : 해당 패키지가 현재 사용되고 있는지 알려 준다. dependency 관계도 알 수 있다. </p>
<p><code>npm deprecate [패키지명/버전/메시지]</code> : 패키지를 설치할 때 경고 메시지를 띄운다.
<code>npm publish</code> : 자신이 만든 패키지 배포
<code>npm unpublish</code> : 자신이 만든 패키지 배포 중단 (72시간 내에만)</p>
<p><a href="https://docs.npmjs.com%EC%97%90%EC%84%9C">https://docs.npmjs.com에서</a> 더 많은 CLI Commands에서 확인 가능!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[npm과 package.json]]></title>
            <link>https://velog.io/@bin-lee/npm%EA%B3%BC-package.json</link>
            <guid>https://velog.io/@bin-lee/npm%EA%B3%BC-package.json</guid>
            <pubDate>Fri, 31 Dec 2021 02:00:36 GMT</pubDate>
            <description><![CDATA[<h1 id="npm이란">npm이란</h1>
<p>파이썬의 pip처럼 노드에서 사용하는 패키지 매니저다. 다른 사람들이 이미 만들어 둔 소스 코드를 npm으로 설치한 후 사용할 수 있다.</p>
<br>

<h2 id="packagejson">package.json</h2>
<p>현재 프로젝트에 대한 정보와 사용 중인 패키지에 대한 정보를 담은 파일이다. 같은 패키지라도 버전에 따라 기능이 다르고, 다른 버전을 사용할 경우 문제가 생길 수 있기 때문에 package.json에 버전을 기록해 둬야 한다. 📌 노드 프로젝트 시작 전 <strong>npm init</strong>으로 package.json을 만들고 시작했다.</p>
<h4 id="💨-npm-init으로-packagejson-생성">💨 npm init으로 package.json 생성</h4>
<p><code>package name</code>: 패키지의 이름 작성
<code>uthor</code>: 작성자 이름
<code>license</code>: 오픈 소스인 MIT를 많이 사용</p>
<p><code>npm init</code> 말고 json 형식을 따라서 직접 작성하는 것도 가능하다. 단, 직접 작성의 경우 꼭 마지막에 <code>npm i</code>를 해 주어야 설치가 마무리 된다!</p>
<h4 id="💨-packagejson의-구조">💨 package.json의 구조</h4>
<p>생성할 때 입력했던 정보들이 JSON 형식으로 들어가 있다.
<code>&quot;scripts&quot;</code> 안에 있는 명령어들은 <code>npm run 명령어명</code>으로 사용할 수 있다. start라는 시작 실행 명령어가 자주 쓰인다. (start는 run을 제외 가능하다)</p>
<br>

<h2 id="npm-i">npm i</h2>
<h4 id="💨-npm-i로-패키지-설치">💨 npm i로 패키지 설치</h4>
<p><code>npm i 패키지명</code>으로 패키지 설치를 할 수 있다. 설치를 마치면 package.json 파일에 <code>&quot;dependencies&quot;</code>가 추가된다.
이 안에는 패키지들의 버전이 기록되어 있다. 버전이 정확하게 기록되어 있어야 해당 버전으로 오류 없이 설치된다.</p>
<p><code>npm i -D</code>로 -D 옵션을 붙이면 <code>&quot;devDependencies&quot;</code>가 추가된다. devDependencies는 개발할 때만 사용하는 패키지, dependencies는 배포 및 서비스까지 쓰이는 패키지로 구분하여 설치한다.</p>
<h4 id="💨-npm-i--g로-패키지-설치">💨 npm i -g로 패키지 설치</h4>
<p>-g는 글로벌로 설치(전역 설치)한다는 의미이다. 명령어로 쓸 수 있는 패키지들은 글로벌로 설치하는 편이다. 설치를 해도 dependencies에 따로 기록이 안 되기 때문에 협업자가 해당 패키지를 사용하는지, 사용하지 않는지 알 수 없다.
그래서 요즘은 처음부터 전역 설치를 하는 걸 기피하고 <code>npm i</code> 혹은 <code>npm i -D</code>로 설치한 후 <code>npx</code>를 붙여 사용한다. <code>npx</code>를 붙이면 글로벌 명령어로써 사용된다. 관리가 훨씬 용이해진다. </p>
<br>

<h2 id="nodemodules">node.modules</h2>
<p>node.modules에는 다운받은 패키지들이 들어가 있다. 그런데 설치하지 않은 패키지들이 섞여 들어 있는 경우가 있다. 이는 설치한 패키지가 dependencies로 필요로 하는 패키지들이 설치된 것이다. 
이것저것 설치되어 있어 용량이 크기 때문에 배포할 때는 지워서 배포하고, 배포 후 <code>npm i</code>로 그 서버에서 재설치하는 방식으로 사용한다.</p>
<br>

<h2 id="package-lockjson">package-lock.json</h2>
<p>dependencies로 설치했던 것들의 dependencies들까지 모든 버전을 정확하게 기록해 놓은 파일이다. package-lock.json은 잘 건드리지 않는 편이다.</p>
<hr>
<h3 id="-semver-버저닝">+ SemVer 버저닝</h3>
<p>노드 패키지의 버전은 SemVer을 따라서 버전을 세 자리 수로 표현한다. Major -&gt; Minor -&gt; Patch 순이며 하위 버전과 호환되지 않는 수정, 하위 버전과 호환되는 수정, 버그 해결이 되었을 때 각각 버전을 올린다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[map과 reduce의 차이점]]></title>
            <link>https://velog.io/@bin-lee/map-reduce</link>
            <guid>https://velog.io/@bin-lee/map-reduce</guid>
            <pubDate>Wed, 29 Dec 2021 06:23:55 GMT</pubDate>
            <description><![CDATA[<p><code>forEach</code> <code>map</code> <code>reduce</code>는 모두 <strong>배열</strong>에서만 사용 가능하며 for문을 돌린다는 공통점을 가진다. 차이점은 목적에 있다.</p>
<h4 id="차이점">차이점</h4>
<ol>
<li><code>forEach</code> for문을 간편하게 돌린다.</li>
<li><code>map</code> for문을 돌려서 새로운 배열을 만든다.</li>
<li><code>reduce</code> for문을 돌려서 최종적으로 다른 무언가를 만든다.<br>

</li>
</ol>
<p>이렇게 <code>reduce</code>로 갈수록 하위 개념이며 좁은(명확한) 목적을 가지게 된다. 
특히 <code>map</code>과 <code>reduce</code>는 새로운 배열 혹은 무언가를 만드는 목적이 있기 때문에 <strong>함수 내에 <code>return</code>을 필수로 가진다.</strong></p>
<hr>
<h2 id="map">map</h2>
<p><img src="https://images.velog.io/images/bin-lee/post/f77462ea-f3d2-4b92-a141-bc01e2ea9600/image.png" alt=""> <strong>console</strong>
value는 a가 순차적으로 출력된 <code>1, 2, 3</code>
i는 인덱스 순서대로 <code>0, 1, 2</code>
arr는 a 자체를 뜻하므로 <code>[ 1, 2, 3 ]</code>
this는 <code>[10]</code>이다.</p>
<p><strong>return값</strong>
this인 10에 value값을 더해 <code>[ 11, 12, 13 ]</code>이 출력된다.</p>
<br>

<h2 id="reduce">reduce</h2>
<pre><code>배열.reduce((누적값, 현재값, 인덱스, 요소) =&gt; {return 결과}, 초기값)
배열.reduce(function(누적값, 현재값, 인덱스, 요소){return 결과}, 초기값)</code></pre><p>reduce는 누적값이라는 개념이 있다. 누적값의 시작값은 초기값이다. 만약 초기값을 5으로 두면 누적값 역시 5로 시작하게 된다.</p>
<p><img src="https://images.velog.io/images/bin-lee/post/31b90482-bd0c-42c2-b245-c743f7522779/image.png" alt=""> <strong>console</strong>
acc는 누적값 <code>10</code>부터 시작하여 현재값을 더한 값으로 누적된다. 때문에 첫 번째 누적값은 <code>10</code>이고, 두 번째 누적값은 10에 현재값 1을 더한 <code>11</code>, 세 번째 누적값은 11에 현재값 2를 더한 <code>13</code>이 된다.
cur은 배열에 들어 있는 현재값 <code>1, 2, 3</code>이다.
i는 인덱스 <code>0, 1, 2</code>이다.</p>
<p>이렇게 reduce는 누적값에 현재값을 더한 값이 다음 누적값으로 누적된다.</p>
<p><strong>return값</strong>
acc인 <code>13</code>에 cur <code>3</code>이 더해져서 <code>16</code>이 출력된다.</p>
<br>

<p>그런데 초기값은 필수값이 아니다.
초기값을 지정하지 않을 때는 아래 코드처럼 동작한다.</p>
<p><img src="https://images.velog.io/images/bin-lee/post/074c686f-639c-4435-8c58-500ab4d622be/image.png" alt=""><strong>console</strong>
초기값이 없으면 0번째 인덱스의 값이 초기값으로 지정된다. 0번째 인덱스 값은 <code>1</code>이므로 첫번째 누적값 역시 <code>1</code>이다. 이후 누적되는 과정은 위와 동일하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[literal과 template literal]]></title>
            <link>https://velog.io/@bin-lee/template-literal</link>
            <guid>https://velog.io/@bin-lee/template-literal</guid>
            <pubDate>Wed, 29 Dec 2021 01:53:47 GMT</pubDate>
            <description><![CDATA[<p>템플릿 리터럴이 등장하는 강의를 듣다가 문득 리터럴에 대한 개념이 두루뭉실하게 느껴져서🤔.. 리터럴과 템플릿 리터럴을 정리할 겸 포스트를 작성한다.</p>
<hr>
<h3 id="리터럴literal">리터럴(literal)</h3>
<p>먼저 아래는 리터럴을 사용한 값의 예시들이다. 예시만 보면 전혀 어렵지 않고 익히 알고 있는 내용이다.</p>
<blockquote>
</blockquote>
<p><code>100</code> 정수 리터럴 
<code>&#39;Hi!&#39;</code> 문자열 리터럴 
<code>false</code> 불리언 리터럴 
<code>{ name: &#39;Kim&#39;, age: 11 }</code> 객체 리터럴 
<code>[ 0, 1, 2 ]</code> 배열 리터럴 
<code>function() {}</code> 함수 리터럴 
...</p>
<p>리터럴은 &lt;사람이 이해할 수 있는 문자 / 또는 약속된 기호를 사용해 / 값을 생성하는 표기법&gt;이다. 문장을 나눠서 보면 좀 더 편하다.</p>
<p>사람이 이해할 수 있는 문자는 <strong>영어, 한국어, 숫자 등</strong>이
약속된 기호로는 <strong>중괄호, 소괄호, 대괄호, 홑따옴표 등</strong>이 있다.</p>
<p>이런 것들로 값을 표기하는 방법이 리터럴인 것이다. 즉, 값 생성을 위해 미리 약속된 표기법이다.</p>
<br>

<h3 id="템플릿-리터럴template-literal">템플릿 리터럴(template literal)</h3>
<p>템플릿 리터럴은 ES6에서 새롭게 추가된 <strong>문자열 표기법</strong>이다. 기존 문자열 표기는 <code>쌍따옴표(&quot;)</code>와 <code>홑따옴표(&#39;)</code>로만 표현이 가능했는데, 템플릿 리터럴이 도입되면서 <code>백틱(``)</code>을 사용할 수 있게 되었다.</p>
<pre><code>let a = &#39;abc&#39;
let b = &quot;abc&quot;
let c = `abc`

a === b
b === c
a === c
</code></pre><p>위 결과를 보면 백틱 기호를 사용한 템플릿 리터럴과 스트링 리터럴(문자열 리터럴) 값이 같다는 것을 알 수 있다. </p>
<p>그런데 왜! 얘는 혼자 템플릿으로 이름이 다를까? 분명 다른 특징이 있기 때문에 이름이 다를 것이고, 템플릿 리터럴이 독자적으로 가지고 있는 특징은 다음과 같다.</p>
<h4 id="템플릿-리터럴의-특징">템플릿 리터럴의 특징</h4>
<ol>
<li>멀티라인 문자열<pre><code>var a = &#39;abc\n&#39; + &#39;def&#39;</code></pre></li>
</ol>
<p>기존의 스트링 리터럴이 위와 같은 이스케이프 시퀀스를 이용해 개행을 했다면, 템플릿 리터럴에선 아래처럼 엔터만으로 개행이 가능하다. </p>
<pre><code>let b = `abc
def`</code></pre><p>다만 템플릿 리터럴은 백틱 사이에 들어간 모든 뎁스를 전부 문자열로 인식하기 때문에 그 점을 고려해야 한다. 고운 정렬(?)이 좋다면 백틱 기호 안에 이스케이프 시퀀스를 동일하게 작성해 주면 된다.</p>
<pre><code>let b = `abc\n` +
    `def`</code></pre><p>이렇게 되면 기존 방법이랑 다를 바가 없기 때문에 애매한 느낌이 들지만, ${}을 이용해서 보간을 할 계획이라면 백틱이 더 좋은 선택일 수 있겠다.</p>
<br>

<ol start="2">
<li>표현식 삽입<pre><code>const a = 10
const b = 20
</code></pre></li>
</ol>
<p>const before = a + &#39; + &#39; + b + &#39; = &#39; + ( a + b ); 
const after = <code>${a} + ${b} = ${ a + b }</code></p>
<p>```
before과 after의 과정은 다르지만 모두 <code>10 + 20 = 30</code>이라는 같은 결과가 나온다. ${} 안에는 값과 식이 들어올 수 있기 때문에 이를 이용해서 이전보다 훨씬 쉬운 표현식 삽입이 가능하다. </p>
]]></description>
        </item>
    </channel>
</rss>