트센::강의정리

YP J·2023년 2월 21일
0

Nestjs

목록 보기
6/6

express

  • express 는 자유로운 대신 사람마다 구조가 다양하다

  • nest는 정형화된 구조가 있다.

  • passport쓰는 이유

    • return done(null, user) :성공시 자동으로 세션과 연결해서
    • req.user로 만들어준다.
  • node생태계에서는 passport가 저명하다.

  • ORM 쓰면서 동시에 복잡하거나 성능상 문제가 있으면 쌩 쿼리로~

  • 1:1 1:다 다:다: 테이블 마다 관계들 associate(db)로 정의 해준다.

  • ORM 쓸때도 그에 상응하는 SQL 도 알아야 잘 쓸수 있다. ( 씨퀄라이즈 보단 ORM이 쿼리에 가깝다)

  • 트렌젝션이 이 뭔지 , join 여러번 안 할수 있는 경우 해결법

  • express 는 middleware?가 다다.

  • middleware : 요청과 응답을 조작해주는 역할.


nestjs Admin -> 간단한 CRUD 구축 해봐라.
https://nestjs-admin.com/


환경세팅

  • npm i -g @nestjs/cli

    • 전역으로 nestjs/cli 설치

      • 이런 경고 떴다.
        npm WARN deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
    • 해결책

      • npm install --save-dev @jridgewell/sourcemap-codec
    • 이유

      • npm WARN deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
  • nest new fast(아무 프로젝트이름)

  • tsconfig.json 에서

    • "esModuleInterop": true, // import * as React from 'react'; 를 안 해도된다. 추가
  • package.json 에서

    • 'test': jest -> 유닛테스트
    • 'test:e2e' -> end to end 테스트
    • 'supertest' -> api 테스트. 요청도 응답도 가짜로 해보는~
  • 아무것도 안 하고 그냥

  • npm run start 해보면 -> nest start 로 치환돼서 실행됨.

    • localhost:3000 치면 hello world 보인다.
    • 개발자 툴에서 Network 보면
    • X-Powered-By: Express 인데
      • express 위에 nest 가있어서 그렇다.
      • express 대신 fastfy(다음세대의 서버 프레임워크) 써도 되는데
      • nest + express 쓰면 fastfy 설계적으로 우월한 부분을 nest 가 어느정도 express의 설계결함을 잡아준다 그래서 굳이 ~?
  • 에디터 마다 eslint,pretter ? 설정 해주는방법이 다르다.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const port = process.env.PORT || 3000; 
  await app.listen(port);
  console.log(`listening on port ${port}`)
}
bootstrap();
  • 코디 바뀔때마다 다시 띄워주는명령어 (hot reloading )
    • npm i --save-dev webpack-node-externals run-script-webpack-plugin webpack

Controller

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller('abc')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('hello') // GET /abc/hello
  getHello(): string {
    return this.appService.getHello();
  }
}
  • GET method 의 helllo 를 abc 와 연결하는 코드를 안 짯는데 Nest 가 우리 코드를 찾아서 연결해준다.

    • 데코레이터, 어노테이션 ( 제어의 역전 ?)
    • 스프링 생태계와 비슷하다.다만 app.modules.ts 에 module을 직접 구성해야 한다는 점에서 스프링보다 IoC(제어의 역전) 이 약하다.

      blackbox: 입려값을 넣으면 출력값이 어떻게 나오는지 모를경우
      whitebox : 알고리즘 처럼 논리적으로 입력 값이 출력값이 나오는경우.

  • Controller 는 라우터랑 매칭이 된다고 생각.


    Services

  • 얘 는 요청과 응답에 대해서는 모른다.

    • 그래서 Services에는 req,res가 없는게 좋다.
  • 순수하게 동작만 하고 결과값만 컨트롤러한테 주고 컨트롤러는(req,res를 알아야 겠지) 그걸 응답해주고 또는 요청을 받아서 서비스에 넘기고

  • 실질적 로직이 돌아가는부분.

  • 트렌젝션 단위로 짜준다.

  • 근데 왜 컨트롤러와 Services로 나눠 놓냐 ?

    • 트렌젝션 단위? 로 나눠 놓은것. 나중에 편함.
  • 라우터 같은곳에서 한명의 유저 찾는게 중복 되는게 아니라 서비스에서만 유저 찾아서 여러번 되는게 중복 제거 된다?

  • req,res 를 모르기 때문에 독립적으로 쓸수있다.

    • 원래 테스트 할때 (req, res , next) => { ...
    • 여기서 req,res,next를 목킹(가짜 객체) 를 해줘야 한다.(그래서 안 쓸수 있으면 안쓰는 게 좋다.)
    • 근데 nest는 목킹 필요없이 하나의 함수만 테스트 할수있다.

    ConfigModule 사용하기(dotenv 진화판)

  • npm i --save @nestjs/config

  • dotenv 를 nest 의 모듈로 만들어서 다시 또 연결 해야한다

  • 그럴 바엔 그냥 nestjs/config 처럼 dotenv 같은거를 모듈로 감싼걸 가져다 쓰자.

    app.modules.ts

    import { Module } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    
    @Module({
     imports: [ConfigModule.forRoot()],
     controllers: [AppController],
     providers: [AppService],
    })
    export class AppModule {}
    ```` `
  • 여기서 forRoot(), forFeature() 들은 거기 안에다 설정을 넣어줄 얘들을 그렇게 해준다 그게 아닐경우 그냥 아래처럼 AppContorller 이렇게만 .

  • .env.development / .env.production 같이 파일을 만들어서 환경변수 별로 불러오는기능.

    • .env의 기능이 아니라 다른 패키지의 기능 이다.
  • 근데 app.services.ts 에있는 return process.env.SECRET;

  • 이부분에 직접 하는것보단 configServices 를 만들어서 하는게 좋다.

  • app.services.ts 에
    constructor(private readonly configService: ConfigService){}

  • 추가 해주고

  • app.modules.ts 에

@Module({
  imports: [ConfigModule.forRoot({isGlobal:true})],
  controllers: [AppController],
  providers: [AppService,ConfigService],
})
  • 넣어주고
  • 근데 왜 이렇게 하냐 ?
  • process.env.KEY 에서 process는 nest와 아예 상관 없는 것인데
  • configService 만들어서 nest로 엮어 준다.

.env 같은 환경변수들은 AWS비밀 저장소나 구글클라우드 플랫폼 보안 비밀 관리자에서 불러와서 process.env , 환경변수로 만드는 작업을 많이 한다 .

  • 그러면 Config는 비동기 이다.( 외부에서 요청을 보내서 데이터를 가져와서 내 config로 만드닌까)

  • 그럴땐

const getEnv = async () => {
  const response = awiat axios.get('/외부서버로 부터 비밀키요청')
  return {
    response.data
  }
}
@Module({
  imports: [ConfigModule.forRoot({isGlobal:true, load:[getEnv]} )],
  • 이렇게 하면 외부서버로 부터 가져온 비밀키들이 우리 process.env 가 된다 .

loggerMiddleware 로 Morgan 처럼 로깅하기

  • src 안에 middlewares폴더 안에 logger.middleware.ts 만든다.
export class LoggerMiddleware implements NestMiddleware {
  • 여기서 implements 는 반드시 구현 해야한는 강제사항이 생긴다.
Class 'LoggerMiddleware' incorrectly implements interface 'NestMiddleware<any, any>'.
  Property 'use' is missing in type 'LoggerMiddleware' but required in type 'NestMiddleware<any, any>'.ts(2420)
nest-middleware.interface.d.ts(7, 5): 'use' is declared here
  • use 라는걸 구현 해야한다.
    use(request: Request, response: Response, next: NextFunction): void {
        
    }
  • 근데 위의 코드부분만 보면 express 의 middleware와 같다.
import { Injectable,Logger, NestMiddleware } from "@nestjs/common";
import { NextFunction, Request, Response } from "express";

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
    private logger = new Logger('HTTP')
    use(request: Request, response: Response, next: NextFunction): void {
        const{ ip, method, originalUrl } = request;
        const userAgent = request.get('user-agent') || ''; // 요청의 헤더에서 가져오는것 없으면 빈문자열
    
    
    response.on('finish', () => {
        const {statusCode} = response;
        const contentLength = response.get('content-length');
        this.logger.log(
            `${method}${originalUrl}${statusCode}${contentLength} - ${userAgent}${ip}`,
        );
    });
  }
}
  • 근데 여기서 this.logger.log 를
  • Logger.log로 쓸수있다.
  • 개발 하다보면 console.log 가 쌓이는데 그러면 추적이 힘들다. (내가 어떤거랑 연결 되어있나 를 )
  • 이걸 해결하기 위해 debug 라는 패키지를 많이 쓴다.
    https://www.npmjs.com/package/debug

-실무에선 nest morgan패키지를 쓴다.


  • implements 문법은 왜 쓰나 ?

    • 무조건 구현해야하는 강제성 이 오류를 잡아줄수도 있다. 도와주는것.
  • injectable() ?? DI dependency injection

    • injectable() 쓰고 있으면 providers에 넣어줘야한다.
  • 그럼 굳이 providers는 왜 있냐 ?

    • 의존성 주입을 할때 providers에 있는걸 보고 주입해준다.
func a(b,c)
	return b+c;

a(1+2)   = 3
a(2+2)   = 4 
  • 즉 이렇게 사용자가 입력을 넣게 하는것.
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {
    //원래는 생성자에 this.appService = AppService
  }
  
  @Get()
  getHello() {
    return this.appService.getHello();
    //return new AppService().getHello();
  }
  • 여기서 new AppService() 을 생성해서 던져주는건.

    func a(5,c)
    	return 5+c; 
  • 이렇게 해주는것이다 new로 생성해서 넣어주면 의존성이 없어진다.

    나중에
    new AppController(new AppService());
    new AppController(new TestAppService());

  • 이렇게 아래와 같이 테스트를 할때

  • AppService()가 생성자에 들어가서 this.appService가 된다.

  • 그럼 TestAppService가 들어가면 이게 this.TestAppService가 된다.

  • constructor(private readonly TestAppService: AppService)

  • this.TestAppService 의 getHello() 를 불러온다.

근데 주의할게 websocket 같은경우에 의존성 주입 될때마다 웹소켓 서버가 생기는것이다.
그래서 프론트에서 채팅 하나 날렸는데 10번 서버에서 처리해야할수도있다 이럴때 글로벌로 선언해서 싱글톤 처럼 써줄수있다 ?

@Module({
  imports: [ConfigModule.forRoot({isGlobal:true})],
  controllers: [AppController],
  //providers: [AppService,ConfigService],
  providers : [
  {
  	provide : AppService[고유한키 (클래스면 알아서 클래스의 이름을 고유한 키로 쓴다.)],
  	useClass: AppService,
    //useValue:'123'
  },
  {
  	provide: 'CUSTOM_KEY',
    useValue: 'CUSTOM_VALUE'
  },
    ],
})
  • 원래 이런식이였는데 줄여 쓰는것.
@Inject('CUSTOM_KEY') private readonly customValue,
  • 이렇게 Inject 해주면 커스텀 벨류에 키가 되겠죠 ?

import { Controller,Get,Post,Req,Res } from '@nestjs/common';

@Controller('users')
export class UsersController {
    @Get()
    getUsers(@Req() req) {
        return req.user;
    }

    @Post()
    postUsers() {

    }

    @Post('login')
    logIn() {

    }
    @Post('logout')
    logOut(@Req() req, @Res() res) {
        req.logOut();
        res.clearCookie('connect.sid', {httpOnly:true})
        res.send('ok')
    }


}
  • Controller 에서도 Req, Res 쓰지 않는것이 좋다
    • 즉 내가 쓰는 프레임워크에 의존 하지 않는게 좋다 .
    • Req, Res는 Express 프레임워크에 의존 하는것이다.
  • 근데 로그아웃은 어쩔수없다
    @Get()
    getUsers(@Req() req) {
        return req.user;
    }
  • 여기서 req 없애는 것을 연습해보자 그전에 DTO

    res.locals.jwt 와 같이 jwt 토큰으로 해도 res를 쓰는것
    해결법 커스텀 데코리이터를 쓰는것 ! ~~

    @Post()
    postUsers(@Body() data: JoinRequestDto) {
        
    }
  • 요청에 Body에 들어있는 데이터가 data 에 담긴다.


  • API 설계 잘하려면 잘 만들어진 API를 많이 써봐라 .


    Swagger

  • 공홈 openapi - introduction

  • npm install --save @nestjs/swagger

       @ApiOperation({summary:'내 정보 조회'})
       @Get()
       getUsers(@Req() req) {
           return req.user;
       }
       @ApiOperation({summary:'회원 가입'})
       @Post()
       postUsers(@Body() data: JoinRequestDto) {
           this.usersService.postUsers(data.email, data.nickname, data.password)
           return ;
       }
       @ApiOperation({summary:'로그인'})
       @Post('login')
       logIn() {
    
       }
  • @ApiOperation({summary:'로그인'}) 이런식으로 해서 하면

  • localhost:3030/api 들어가면

  • Users에 dto가 들어가는지는 자동으로 확인해준다
  • 근데 그 dto에 대한 거는 직접 써줘야한다.
 @ApiProperty({
        example : 'junyopar@gmail.com',
        description: '이메일',
        required: true,
    })
    public email: string;
  • @ApiProperty 로~

  • 근데 swagger 는 쿼리스트링은 인식이 안된다. 직접 추가해주자

@ApiTags('DM')
@Controller('api/workspaces/:url/dms')
export class DmsController {
    @ApiParam( {
        name: 'url',
        required: true,
        description: '워크스페이스 url',
    })
    @ApiParam( {
        name: 'id',
        required: true,
        description: '사용자 id',
    })
    @ApiQuery({
        name: 'perPage',
        required: true,
        description: '한 번에 가져오는 개수',
    })
    @Get(':id/chats')
    getChat(@Query() query, @Param() param) {
        console.log(query.perPage, query.page);
        console.log(param.id, param.url)
    }
  • @ApiTags('DM') 이렇게 최 상단에 해주면 그룹으로 묶인다.

외부 dto 클래스 가져올땐 dto class 에 export class 라고 붙여야 가져올수 있다.

  • 응답 여러개 가능 (근데 같은 코드에는 같은것만 가능.)
    @ApiResponse({
        status:500,
        description:'서버에러',
    })
    @ApiResponse({
        status:200,
        description:'성공',
        type: UserDto
    })
    @ApiOperation({summary:'내 정보 조회'})
    @Get()
    getUsers(@Req() req) {
        return req.user;
    }


커스텀 데코레이터

import { createParamDecorator, ExecutionContext } from "@nestjs/common";

export const User = createParamDecorator(
    (data : unknown, ctx:ExecutionContext) => {
        const request = ctx.switchToHttp().getRequest();
        return request.user;
    }
)
  • 이제 user.controller.ts 에서
    @ApiOperation({summary:'내 정보 조회'})
    @Get()
    getUsers(@Req() req) {
        return req.user;
    }
  • 를 아래와 같이 바꿀수 있다.
    @ApiOperation({summary:'내 정보 조회'})
    @Get()
    getUsers(@User() user) {
        return user;
    }
    • token 도 만들어보자
import { createParamDecorator, ExecutionContext } from "@nestjs/common";

export const Token = createParamDecorator(
    (data : unknown, ctx:ExecutionContext) => {
        const response = ctx.switchToHttp().getResponse();
        return response.locals.jwt;
    }
)
  • 토큰은 Response 에 의 로컬에 있다 ?
  • 이렇게 하면 나중에 Token 쓸대 @Token() token 으로 사용 가능

이렇게 왜 Req, Res 를 사용하는걸 지양 해야하나?
나중에 테스트할때 종속 되는걸 막기 위해 .

  • ExcutionContext
    • 실행 컨텍스트 에는
    • Http, Rpc, Ws(websocket)에 대해 하나의 객체로 접근이 가능하다 excuteContext객체로
    • 실제로 채팅 같은경우엔 Http 와 Websocket이 같이 돌아간다.

인터셉터

  • 미들웨어 공부는 나중에 레퍼런스 보고 다시하자.

Typeorm entitiy

  • 다대다는 오류가 있어서 일대다를 2번 해주면 된다??
    ( ?이거 이해안가서 나중에 필요할때 찾아봐 )
  • softdelete 공부 // harddlete(row자체를 지우는거)

    • 사용자가 삭제후 복원을 요청할만한 데이터는 실제 삭제가 아니라 deleteAt이라는 거 만들어서 거기에 놔두는거 ?? 이해 안감

CRUD : nest g resource 로 자동으로 만들어준다


  • npm i typorm pg
    https://www.npmjs.com/package/typeorm

  • --save ( package.json 에 저장해 주는 옵션) 옵션이 이제 디폴트로 들어가서 따로 설정 안 해줘도 된다.

profile
be pro

0개의 댓글