반응형

NestJS 란?

NestJS는 효율적이고 확장 가능한 Node.js 서버측 애플리케이션을 구축하기 위한 프레임워크입니다.
Express 또는 Fastify 프레임워크를 래핑 하여 동작합니다. (기본으로 설치하면 Express를 사용합니다.) 
OOP (객체 지향 프로그래밍 Object Oriented Programming), FP (함수형 프로그래밍 Functional Programming) 및 FRP (함수형 반응형 프로그래밍 Functional Reactive Programming) 요소를 결합합니다.

NestJS의 특징

  • NestJS는 Angualr로부터 영향을 많이 받았습니다. 모듈/컴포넌트 기반으로 프로그램을 작성함으로써 재사용성을 높여줍니다.
  • IoC(Inversion of Control, 제어 역전), DI(Dependency Injection, 의존성 주입), AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)와 같은 객체지향 개념을 도입하였습니다.
  • 프로그래밍 언어는 타입스크립트를 기본으로 채택하고 있어 타입스크립트가 가진 타입 시스템의 장점을 가집니다.
  • Node.js는 손쉽게 사용할 수 있고 뛰어난 확장성을 가지고 있지만, 높은 자유도로 인해 Architecture 구성이 어렵습니다. 이러한 단점을 보완하기 위해 NestJS가 탄생하게 되었습니다.

NestJS의 장단점

  • NestJS는 데이터베이스, ORM, 설정(Configuration), 유효성 검사 등 수많은 기능을 기본 제공하고 있어 편리합니다.
  • 필요한 라이브러리를 쉽게 설치하여 기능을 확장할 수 있는 Node.js 장점도 그대로 가지고 있습니다.

 

NestJS를 사용하여 개발하면서 느낀 점

러닝 커브가 낮아서 쉽게 접근 가능한 프레임워크인 것 같습니다.
저도 처음에는 영어문서이긴 하지만 문서도 잘 나와있는 편이라서 혼자 문서 보면서 간단한 서비스 만드는 것부터 시작했습니다. (한글문서도 나왔었는데 갑자기 사라짐..)
하지만 아직 사용하는 사람이 많지 않아서 오류 발생 시 찾기가 수월하지는 않습니다.
그래도 점점 Nest를 공부하는 사람들이 많아지는 것 같아서 너무 좋습니다.
 
그리고 Java의 Spring과 Architecture 구성이 유사하기 때문에 Spring 경험이 있는 분들은 금방 익힐 수 있을 것 같습니다.
다음에는 Spring과 NestJS의 차이점을 들고 오도록 하겠습니다.
 

반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] E2E Testing  (0) 2022.02.27
[NestJS] Swagger 생성하기  (0) 2022.02.21
[NestJS] 유닛 테스트(Unit Testing)  (0) 2022.02.16
[NestJS] Docker 304 undefined 에러  (0) 2022.02.14
반응형

main.ts 에서 config, document 변수를 SwaggerModule에 넣는다.

// main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  ...

  const config = new DocumentBuilder()
    .setTitle('Unicorn')
    .setDescription('Unicorn API description')
    .setVersion('1.0')
    .addTag('unicorn')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  const port = process.env.PORT;
  await app.listen(port);
}
bootstrap();

 

Controller의 Tag 부여

@ApiTags('domains')
@Controller('domains')
export class DomainController {
	constructor(private readonly domainService: DomainService) {}
  ...
}

 

api 설명 부여하기

@ApiTags('scenario')
@Controller('domains')
export class ScenarioController {
	constructor(private readonly scenarioService: ScenarioService) {}

	@ApiOperation({ summary: '해당 도메인의 모든 시나리오 검색' })
	@Get('/:domain/scenarios')
	async getAllScenarios(@Param('domain') domain: string): Promise<Scenario> {
		return this.scenarioService.getAllScenarios(domain);
	}

	...
}

 

Schemas 적용

@ApiTags('authorization')
@Controller('authorization')
export class AuthorizationController {
	constructor(private readonly authorizationService: AuthorizationService) {}

	@ApiBody({ type: searchAdminDto })
	@Post()
	async searchAdmin(
		@Body('adminData') adminData: searchAdminDto,
		): Promise<boolean> {
		return this.authorizationService.checkAdmin(adminData);
	}
}

Body 로 들어오는 DTO를 ApiBody 데코레이터로 알려주고, DTO의 필요한 값들을 ApiProperty 데코레이터를 사용하면 된다.

 

export class searchAdminDto {
    @ApiProperty()
    @IsString()
    id: string;

    @ApiProperty()
    @IsString()
    pw: string;    
}

 

📌 ApiBody 데코레이터은 하나의 메서드 당 한 번만 사용 두 개 이상 사용 시 마지막 데코레이터로 적용된다.

 

@ApiProperty 데코레이터

export class searchAdminDto {
    @ApiProperty({
        type: String,
        default: 'default string',
        description: '사원번호'
    })
    @IsString()
    id: string;

    @ApiProperty({
        type: String,
        description: '비밀번호'
    })
    @IsString()
    pw: string;    
}

      •  

 

반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] NestJS 란?  (0) 2022.06.10
[NestJS] E2E Testing  (0) 2022.02.27
[NestJS] 유닛 테스트(Unit Testing)  (0) 2022.02.16
[NestJS] Docker 304 undefined 에러  (0) 2022.02.14
반응형

package.json

  • jest : 자바스크립트를 쉽게 테스팅하는 npm 패키지
  • test:cov : 코드가 얼마나 테스팅됐는지 알려줍니다.
{
	"test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
}

 

💡 새로 파일들을 생성하면 파일명 뒤에 .spec.ts 라고 붙은 파일들이 같이 생성되는데 그 파일들이 테스트를 포함한 파일입니다.

 

Nestjs에서는 jest 가. spec.ts 파일들을 찾아볼 수 있도록 설정되어있습니다.

 

  • npm run test:cov
    • 모든 .spec.ts 파일을 찾아서 몇 줄이 테스팅됐는지 알려줍니다.
  • npm run test:watch
    • 모든 테스트 파일들을 찾아서 거기서 무슨 일이 일어나는지 관찰합니다.

 

Unit Testing

  • 서비스에서 분리된 유닛을 테스트한다.
  • 모든 function 을 따로 테스트
    ex) movie.service.ts > getAll() 함수 하나만 테스트하고 싶을 때 사용

e2e Testing (end-to-end)

  • 모든 시스템을 테스팅한다.
    ex) 이 페이지로 가면 특정 페이지가 나와야 하는 경우 사용

Jest

  • 자바스크립트 테스팅 프레임워크

 

 

Unit Test

💡 afterAll() 안에는 데이터베이스를 모두 지우는 function을 넣을 수 있음 beforeEach, afterEach, beforeAll, afterAll 등 많은 훅이 있습니다.

 

간단한 프로젝트를 통해 unit test를 진행해보도록 하겠습니다.

 

movies.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { MoviesService } from './movies.service';

describe('MoviesService', () => { // 테스트를 묘사..?
  let service: MoviesService;

  beforeEach(async () => { // 테스트하기전에 실행
    const module: TestingModule = await Test.createTestingModule({
      providers: [MoviesService],
    }).compile();

    service = module.get<MoviesService>(MoviesService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });
});

맨 하단에 이런 함수를 넣어서 실행해봅니다.

...
it('should be 4', () => {
    expect(2+2).toEqual(4);
  })
...

npm run test:watch

&nbsp;a 를 누른다.

 

Unit Testing

getAll()

  • getAll() 메소드를 호출한 후 result 인스턴스가 배열인지 테스트
describe('getAll', () => {
    it('should return an array', () => {
      const result = service.getAll();
      expect(result).toBeInstanceOf(Array);
    });
  });

npm run test:watch

→ getAll : shold return an array

 

getOne()

  • getOne() 메소드가 잘 호출되는지 테스트
    • 999 id 값을 가진 movie 가 없을 때 에러 메시지를 던지는지 확인
describe('getOne', () => {
    // getOne() 을 테스트 할 때 Movie 가 create 되어 있지 않다면 문제가 생길 수 있음
    it('should return a movie', () => {
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      const movie = service.getOne(1);
      expect(movie).toBeDefined();
      expect(movie.id).toEqual(1); // movie id 값이 1이 맞는지 확인
    });

    it('should throw 404 error', () => {
      try{
        service.getOne(999);
      }catch(e){
        expect(e).toBeInstanceOf(NotFoundException);
        expect(e.message).toEqual('Movie with ID 999 not found.');
      }
    })
  });

 

반응형

deleteOne()

  • 먼저 movie 를 생성한다.
describe('deleteOne', () => {
    it('deletes a movie', () => {
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      console.log(service.getAll());
    });
  });

생성한 값들을 console log 로 출력

 

 

  • movie 생성 후 전체 movie의 length와 하나의 movie를 delete 한 후의 movie length를 비교한다.
    • afterDelete 가 beforeDelete 보다 작을 것이라고 예상
describe('deleteOne', () => {
    it('deletes a movie', () => {
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      const beforeDelete = service.getAll().length;
      service.deleteOne(1);
      const afterDelete = service.getAll().length;
      expect(afterDelete).toBeLessThan(beforeDelete); // afterDelete < beforeDelete
    });
    it('should return a 404', () => {
      try{
        service.deleteOne(999);
      }catch(e){
        expect(e).toBeInstanceOf(NotFoundException);
      }
    })
  });

 

create()

describe('create', () => {
    it('should create a movie', () => {
      const beforeCreate = service.getAll().length
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });
      const afterCreate = service.getAll().length
      console.log(beforeCreate, afterCreate);
      expect(afterCreate).toBeGreaterThan(beforeCreate);
    });
  });

 

update()

describe('update', () => {
    it('should update a movie', () => {
      //const beforeUpdate = service.getAll()
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      service.update(1, {
        year: 2025
      });

      //const afterUpdate = service.getAll()
      //console.log(beforeUpdate, afterUpdate)
      const movie = service.getOne(1)
      expect(movie.year).toEqual(2025);
    })
  })

 

describe('update', () => {
    it('should update a movie', () => {
      //const beforeUpdate = service.getAll()
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      service.update(1, {
        year: 2025
      });

      //const afterUpdate = service.getAll()
      //console.log(beforeUpdate, afterUpdate)
      const movie = service.getOne(1)
      expect(movie.year).toEqual(2025);
    })

    it('should throw a NotFoundException', () => {
      try{
        service.deleteOne(999);
      }catch(e){
        expect(e).toBeInstanceOf(NotFoundException);
      }
    })
  })

 

npm run test:cov

→ movies.service.ts 파일이 100%로 바뀌면 unit test 성공입니다!

 

반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] NestJS 란?  (0) 2022.06.10
[NestJS] E2E Testing  (0) 2022.02.27
[NestJS] Swagger 생성하기  (0) 2022.02.21
[NestJS] Docker 304 undefined 에러  (0) 2022.02.14
반응형

문제

API 배포 후 개발 서버에서 도커 로그 파일 확인 결과 304 undefined 이슈 발생

 
도커 로그 파일 확인 코드

docker logs -f [컨테이너 ID]

 

원인

chrome에서 동적 메소드 실행 시 캐시가 저장되기 때문에 발생한 이슈
 
 

해결

main.ts 코드 수정

// main.ts

const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 동적 요청에 대한 응답을 보낼 때 etag 생성을 하지 않도록 설정 (모든 캐시 저장 X)
app.set('etag', false);

또는

app.use(function (req, res, next) {
    res.header('x-powered-by', 'Blood, sweat, and tears.');
    next();
  });
반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] NestJS 란?  (0) 2022.06.10
[NestJS] E2E Testing  (0) 2022.02.27
[NestJS] Swagger 생성하기  (0) 2022.02.21
[NestJS] 유닛 테스트(Unit Testing)  (0) 2022.02.16

+ Recent posts