nest 공식 강의를 참고하였으며,
소스코드는 https://github.com/jujube0/nest-study 에서 확인 가능하다.
새로운 프로젝트 만들기 : nest new {project-name}
모듈 생성하기 : 터미널에서 nest g module user
nest g controller user), service(nest g service user)도 만들 수 있다.또는
nest g resource [name]를 이용하면 기본적인 CRUD가 존재하는 폴더가 생성된다.
$npm install --save @types/express - type definition for express
import { Controller, Get } from '@nestjs/common';
@Controller('user')
export class UserController {
  @Get()
  findAll(): string {
    return 'returns all users';
  }
}
@Controller('user') : /user 의 path prefix를 갖도록 한다.@Get() : HTTP request method decorator,/user 뒤에 올 route path를 추가할 수 있다.@Get('profile') 을 접근하기 위해서는 GET /user/profile 로 접근한다.Manipulating Responses
built-in method를 통해 object나 array를 리턴하면 JSON으로 바꾸어 보내준다. status code는 200(POST는 201)이 자동으로 설정된다.@HttpCode(...)데코레이터를 이용하여 변경할 수 있다.
Library-specific
express 등의 라이브러리를 이용할 수 있다. method handler의 input으로@Res데코레이터를 넣어줄 수 있다(e.g.,findAll(@Res() response)).
전송은response.status(200).send()
handler signature에 @Req()를 추가한다. 
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('user')
export class UserController {
  @Get()
  findAll(@Req() request: Request): string {
    console.log(request);
    return 'returns all users';
  }
}
@Get()
findAll(@Query() paginationQuery) {
    const { limit, offset } = paginationQuery;
    return `This action returns all coffees Limit: ${limit}, offset: ${offset}`;
}
request object는 request query string, parameter, HTTP headers, body property를 가진다.
@Query(key?: string), @Param(key?: string), @Body(key?: string) 등의 decorator로 property에 직접 접근도 가능하다.
POST 를 사용하는 방법은 다음과 같다.import { Controller, Get, Post, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('user')
export class UserController {
  ...
  @Post()
  create(): string {
    return 'This action adds a new user';
  }
}
@Get(), @Post(), @Put(), 등의 HTTP Method를 이용할 수 있다. @All() 은 모든 것들을 다루는 endpoint를 생성한다.@Post()
@HttpCode(204)
create() {
  return 'This action adds a new user';
}
@nestjs/common package@Res 를 이용한 library-specific method를 이용하자.@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} user`;
}
@Get('profile/:id')
findProfile(@Param('id') id: string): string {
  console.log(id);
  return `This action returns a #${id} profile`;
}
@Get()
async findAll(): Promise<any[]> {
  return [];
}

@Body() 데코레이터를 이용하여 POST route가 클라이언트가 보낸 request body를 읽을 수 있도록 만들어보자.
일단 DTO(Data Transfer Object)를 만들어줘야 한다. 클래스나 인터페이스를 이용하여 만들 수 있는데, 클래스가 선호된다(클래스는 런타임에도 존재하지만 인터페이스는 제거됨).
cli nest generate class coffees/dto/create-coffee.dto --no-spec
export class CreatePostDto {
  title: string;
  content: string;
}
@Post()
create(@Body() createPostDto: CreatePostDto): string {
  return 'This action adds a new post';
}
import { Body, Controller, Get, HttpStatus, Post, Res } from '@nestjs/common';
import { CreatePostDto } from './create-post.dto';
import { Response } from 'express';
@Controller('post')
export class PostController {
  // library specific approach
  @Get()
  findAll(@Res() res: Response) {
    res.status(HttpStatus.CREATED).send();
  }
  @Post()
  create(@Body() createPostDto: CreatePostDto, @Res() res: Response) {
    res.status(HttpStatus.OK).json(createPostDto);
  }
}
@HttpCode(), @Header(), interceptors)을 이용할 수 없음 → 이를 고치기 위해서는 @Res({ passthrough: true}) 를 추가해준다.-> it can inject dependency
nestjs/common을 이용하자.
const coffee =  this.coffees.find(item => item.id === +id);
if (!coffee) {
    throw new HttpException(`Coffee #${id} not found`, HttpStatus.NOT_FOUND);
}
return coffee;
if (!coffee) {
    throw new NotFoundException(`Coffee #${id} not found`);
}
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap() 에 app.useGlobalPipe() 를 추가해준다. 
npm i class-validator class-transformer
import { IsString } from "class-validator";
export class CreateCoffeeDto {
    @IsString()
    readonly name: string;
    @IsString()
    readonly brand: string;
    @IsString({ each: true })
    readonly flavors: string[];
}
이제 정상적인 Request body를 보내지 않으면 다음과 같은 json을 리턴한다.
{
  "statusCode": 400,
  "message": [
    "name must be a string",
    "each value in flavors must be a string"
  ],
  "error": "Bad Request"
}
npm i @nestjs/mapped-types
import { PartialType } from "@nestjs/mapped-types";
import { CreateCoffeeDto } from "./create-coffee.dto";
export class UpdateCoffeeDto extends PartialType(CreateCoffeeDto){ }
version: "3"
services:
  db:
    image:  postgres
    restart: always 
    ports:
      - "5432:5432"
    environment:
       POSTGRES_PASSWORD: pass123
// Start containers in detached / background mode
docker-compose up -d
// Stop containers
docker-compose down
event.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Event {
    @PrimaryGeneratedColumn()
    id: number;
  
    @Column()
    type: string; 
  
    @Column()
    name: string; 
  
    @Column('json')
    payload: Record<string, any>;
}
@Injectable()
export class CoffeesService {
  constructor(
    ...
    private readonly connection: Connection,
  ) {}
  ...
  async recommendCoffee(coffee: Coffee) {
    const queryRunner = this.connection.createQueryRunner();
    
    await queryRunner.connect();
    await queryRunner.startTransaction(); 
    try {
      coffee.recommendations++;
      
      const recommendEvent = new Event();
      recommendEvent.name = 'recommend_coffee';
      recommendEvent.type = 'coffee';
      recommendEvent.payload = { coffeeId: coffee.id };
    
      await queryRunner.manager.save(coffee); 
      await queryRunner.manager.save(recommendEvent);
      
      await queryRunner.commitTransaction();
    } catch (err) {
      await queryRunner.rollbackTransaction();
    } finally {
      await queryRunner.release();
    }
  }
}
ormconfig.js 파일 만든 후,
shell에서
npx typeorm migration:create -n CoffeeRefactor
-> src/migration 에 migration file을 만들어줌

coffee entity의
name칼럼을title로 바꾸는 상황을 가정해보자.
단순히 name을 title로 바꿔버린다면, name에 있던 데이터들은 모두 날아가버릴 것이다.
migrations file을 바꿔준다.
import {MigrationInterface, QueryRunner} from "typeorm";
export class CoffeeRefactor1626325978666 implements MigrationInterface {
    public async up(queryRunner: QueryRunner): Promise<void> { // what needs to be changed and how
        await queryRunner.query(
            `ALTER TABLE "coffee" RENAME COLUMN "name" TO "title"`
        )
    }
    public async down(queryRunner: QueryRunner): Promise<void> { // undo or roll back
        await queryRunner.query(
            `ALTER TABLE "coffee" RENAME COLUMN "title" TO "name"`
        )
    }
}
num run build
npx typeorm migration:run
(npx typeorm migration:revert)
typeorm은 db의 entity와 현재 entity 파일을 비교하여 직접 migration 파일을 만들게 할 수도 있다.
npm run build
npx typeorm migration:generate -n SchemaSync
npx typeorm migration:run

CoffeesService export@Module({
    ...
    exports: [CoffeesService],
})
CoffeesModule importimport { Module } from '@nestjs/common';
import { CoffeesModule } from 'src/coffees/coffees.module';
import { CoffeeRatingService } from './coffee-rating.service';
@Module({
  imports: [CoffeesModule],
  providers: [CoffeeRatingService]
})
export class CoffeeRatingModule {}
CoffeesService importimport { Injectable } from '@nestjs/common';
import { CoffeesService } from 'src/coffees/coffees.service';
@Injectable()
export class CoffeeRatingService {
    constructor(private readonly coffeeService: CoffeesService) {}
}
모든 module은 provider들을 encapsulate한다 → 다른 module에서 이를 이용하려면 exported 에 직접 명시해주어 API에 포함시켜줘야한다. 
좀 더 복잡한 provider를 이용할 때를 생각해보자.
@Module({
    imports: [TypeOrmModule.forFeature([Coffee, Flavor, Event])],
    controllers: [CoffeesController], 
    providers: [{ provide : CoffeesService, useValue: CoffeesService}],
    exports: [CoffeesService],
})
실제 provider 구조는 다음과 같다. 우리가 쓰는 것은 shorthand
그래서
class MockCoffeeService {}
@Module({
    imports: [TypeOrmModule.forFeature([Coffee, Flavor, Event])],
    controllers: [CoffeesController], 
    providers: [{ provide : CoffeesService, useValue: new MockCoffeeService()}],
    exports: [CoffeesService],
})
export class CoffeesModule {}
이렇게 이용할 수 있다는 말.
@Module({
    ...
    providers: [CoffeesService, { provide: 'COFFEE_BRANDS', useValue: ['buddy brew', 'nescafe']}],
})
이용할 때에는
@Injectable()
export class CoffeesService {
  constructor(
    ...
    @Inject('COFFEE_BRANDS') coffeeBrands: string[],
  ) {}
@Inject() 데코레이터 안에 TOKEN을 넣으면 된다.
typo 오류 등을 방지하기 위해서 TOKEN들을 다른 파일에 저장해 놓는 것이 좋다. 
 providers: [
        CoffeesService, 
        {
            provide: ConfigService,
            useClass: process.env.NODE_ENV === 'development' ? DevelopmentConfigService: ProductionConfigService
        }
    ],
@Module({
    imports: [TypeOrmModule.forFeature([Coffee, Flavor, Event])],
    controllers: [CoffeesController], 
    providers: [
        CoffeesService,
        CoffeeBrandsFactory, 
        {
            provide: COFFEE_BRANDS,
            useFactory: (brandsFactory: CoffeeBrandsFactory) => brandsFactory.create(),
            inject: [CoffeeBrandsFactory]
        }
    ],
    exports: [CoffeesService],
})
// Asynchronous "useFactory" (async provider example)
{
  provide: 'COFFEE_BRANDS',
  // Note "async" here, and Promise/Async event inside the Factory function 
  // Could be a database connection / API call / etc
  // In our case we're just "mocking" this type of event with a Promise
  useFactory: async (connection: Connection): Promise<string[]> => {
    // const coffeeBrands = await connection.query('SELECT * ...');
    const coffeeBrands = await Promise.resolve(['buddy brew', 'nescafe'])
    return coffeeBrands;
  },
  inject: [Connection],
},
해당 provider에 의존하는 클래스를 instantiate하기 전에 위 promise를 먼저 resolve하게 된다.
위에서 다룬 모듈들은 static module이었다.
static modules can't have their providers be configured by a module that is consuming it
// Generate a DatabaseModule
nest g mo database
// Initial attempt at creating "CONNECTION" provider, and utilizing useValue for values */
{
  provide: 'CONNECTION',
  useValue: createConnection({
    type: 'postgres',
    host: 'localhost',
    port: 5432
  }),
}
// Creating static register() method on DatabaseModule
export class DatabaseModule {
  static register(options: ConnectionOptions): DynamicModule {  }
}
// Improved Dynamic Module way of creating CONNECTION provider
export class DatabaseModule {
  static register(options: ConnectionOptions): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        {
          provide: 'CONNECTION', // 👈
          useValue: createConnection(options), 
        }
      ]
    }
  }
}
// Utilizing the dynamic DatabaseModule in another Modules imports: []
imports: [
  DatabaseModule.register({ // 👈 passing in dynamic values
    type: 'postgres',
    host: 'localhost',
    password: 'password',
  })
]


@Injectable({ scope: Scope.DEFAULT })
export class CoffeesService {
}
bootstrap() 되면,모든 singleton provider가 instantiate됨.두가지 scope option이 있다.
1. Transient 
@Injectable({ scope: Scope.TRANSIENT })
export class CoffeesService {
TRANSIENT 로 바꾸면, 해당 provider를 inject하는 컨수머들은 모두 새로운 인스턴스를 갖게된다.npm run start 를 해도 instance 가 만들어지지 않는다. // Injecting the ORIGINAL Request object
@Injectable({ scope: Scope.REQUEST })
export class CoffeesService {
  constructor(@Inject(REQUEST) private request: Request) {} // 👈
}
npm i @nestjs/config.env 파일을 만들어주자.
TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: +process.env.DATABASE_PORT,
      username: process.env.DATABASE_USER,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      autoLoadEntities: true,
      synchronize: true,
    }),
.env 파일을 찾는다. 이를 바꿔주려면?ConfigModule.forRoot({
  envFilePath: '.environment’,
});
ignoreEnvFile: true 를 옵션으로 넣어주자.// Install neccessary dependencies
$ npm install @hapi/joi
$ npm install --save-dev @types/hapi__joi
// Use Joi validation
ConfigModule.forRoot({
  validationSchema: Joi.object({
    DATABASE_HOST: Joi.required(),
    DATABASE_PORT: Joi.number().default(5432),
  }),
}),
Joi.required()를 이용하여 DATABASE_HOST 를 필수로 만들었다.Joi.number 로 number로 parsable한 조건을 추가하고, 디폴트 값을 5432로 설정했다.get() @Module({
    imports: [TypeOrmModule.forFeature([Coffee, Flavor, Event]), ConfigModule],
    ...
})
/* Utilize ConfigService */
import { ConfigService } from '@nestjs/config';
constructor(
  private readonly configService: ConfigService, // 👈
) {}
/* Accessing process.env variables from ConfigService */
const databaseHost = this.configService.get<string>('DATABASE_HOST');
console.log(databaseHost);
/* /src/config/app.config.ts File */
export default () => ({
  environment: process.env.NODE_ENV || 'development',
  database: {
    host: process.env.DATABASE_HOST,
    port: parseInt(process.env.DATABASE_PORT, 10) || 5432
  }
});
/* Setting up "appConfig" within our Application */
import appConfig from './config/app.config';
@Module({
  imports: [
    ConfigModule.forRoot({
      load: [appConfig], // 👈
    }),
  ],
})
export class AppModule {}
// ---------
/**
 * Grabbing this nested property within our App 
 * via "dot notation" (a.b)
 */
const databaseHost = this.configService.get('database.host', 'localhost');
load 옵션을 추가해준다./* /src/coffees/coffees.config.ts File */
export default registerAs('coffees', () => ({ // 👈
  foo: 'bar', // 👈
}));
/* Partial Registration of coffees namespaced configuration */
@Module({
  imports: [ConfigModule.forFeature(coffeesConfig)], // 👈
})
export class CoffeesModule {}
// ---------
// ⚠️ sub optimal ways of retrieving Config ⚠️
/* Grab coffees config within App */
const coffeesConfig = this.configService.get('coffees');
console.log(coffeesConfig);
/* Grab nested property within coffees config */
const foo = this.configService.get('coffees.foo');
console.log(foo);
// ---------
// 💡 Optimal / Best-practice 💡
constructor(
  @Inject(coffeesConfig.KEY)
  private coffeesConfiguration: ConfigType<typeof coffeesConfig>, 
) {
  // Now strongly typed, and able to access properties via:
  console.log(coffeesConfiguration.foo); 
}
-> 실제 도메인과 가까운 곳에 config파일을 위치시킬 수 있다.
@Module({
  imports: [
    ConfigModule.forRoot({
      load: [appConfig]
    }),
    CoffeesModule,
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: +process.env.DATABASE_PORT,
      username: process.env.DATABASE_USER,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      autoLoadEntities: true,
      synchronize: true,
    }),
    CoffeeRatingModule,
  ],
forRootAsync() method를 이용할 수 있다. /* forRootAsync() */
TypeOrmModule.forRootAsync({ // 👈
  useFactory: () => ({
    type: 'postgres',
    host: process.env.DATABASE_HOST,
    port: +process.env.DATABASE_PORT,
    username: process.env.DATABASE_USER,
    password: process.env.DATABASE_PASSWORD,
    database: process.env.DATABASE_NAME,
    autoLoadEntities: true,
    synchronize: true,
  }),
}),
UsePipes

// Generate Filter with Nest CLI 
nest g filter common/filters/http-exception
// Catch decorator
@Catch(HttpException)
/* HttpExceptionFilter final code */
import { Catch, HttpException, ExceptionFilter, ArgumentsHost } from "@nestjs/common";
import { Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter<T extends HttpException> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();
    const exceptionResponse = exception.getResponse();
    const error =
      typeof response === 'string'
        ? { message: exceptionResponse }
        : (exceptionResponse as object);
    
    response.status(status).json({
      ...error,
      timestamp: new Date().toISOString(),
    });
  }
}
request에 permission/roles/ACLs 등이 필요할 때
AUthentication / Authorization
authorization Header에 API_KEY가 존재하는지 확인하고,
접근한 route가 public인지 확인해보자
// Generate ApiKeyGuard with Nest CLI
nest g guard common/guards/api-key
// Apply ApiKeyGuard globally
app.useGlobalGuards(new ApiKeyGuard());
/* ApiKeyGuard code */
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Request } from 'express';
import { Observable } from 'rxjs';
@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const authHeader = request.header('Authorization');
    return authHeader === process.env.API_KEY;
  }
}
canActivate : 현 request가 허용되었는지의 여부를 boolean으로 리턴한다.@SetMetadata('key', 'value');/* public.decorator.ts FINAL CODE */
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
/* ApiKeyGuard FINAL CODE */
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class ApiKeyGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly configService: ConfigService,
  ) {}
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const isPublic = this.reflector.get(IS_PUBLIC_KEY, context.getHandler());
    if (isPublic) {
      return true;
    }
    const request = context.switchToHttp().getRequest<Request>();
    const authHeader = request.header('Authorization');
    return authHeader === this.configService.get('API_KEY');
  }
}
useGlobalGuard 는 다른 모듈에 의존성을 갖지 않을 때만 이용이 가능하다.interceptor : 코드를 수정하지 않으면서 기능을 추가할 수 있다
모든 response에 결과가 data property로 들어가길 원한다고 가정해보자.
rxjs : alternative to Promise or callbacks
// Generate WrapResponseInterceptor with Nest CLI 
nest g interceptor common/interceptors/wrap-response
/* WrapResponseInterceptor FINAL CODE */
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class WrapResponseInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');
    return next.handle().pipe(map(data => ({ data })));
  }
}
// Apply Interceptor globally in main.ts file
app.useGlobalInterceptors(new WrapResponseInterceptor());
/* Generate TimeoutInterceptor with Nest CLI */
nest g interceptor common/interceptors/timeout
/* Apply TimeoutInterceptor globally in main.ts file */
app.useGlobalInterceptors(
  new WrapResponseInterceptor(), 
  new TimeoutInterceptor(), // 👈
);
/* Add manual timeout to force timeout interceptor to work */
await new Promise(resolve => setTimeout(resolve, 5000));
/* TimeoutInterceptor FINAL CODE */
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
  RequestTimeoutException,
} from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(3000),
      catchError(err => {
        if (err instanceof TimeoutError) {
          return throwError(new RequestTimeoutException());
        }
        return throwError(err);
      }),
    );
  }
}
Pipes have two typical use cases:
Transformation: where we transform input data to the desired output
& validation: where we evaluate input data and if valid, simply pass it through unchanged. If the data is NOT valid - we want to throw an exception.
In both cases, pipes operate on the arguments being processed by a controller’s route handler. 
NestJS triggers a pipe just before a method is invoked.
Pipes also receive the arguments meant to be passed on to the method. Any transformation or validation operation takes place at this time - afterwards the route handler is invoked with any (potentially) transformed arguments.
// Generate ParseIntPipe with Nest CLI
nest g pipe common/pipes/parse-int
/* ParseIntPipe FINAL CODE */
import {
  ArgumentMetadata,
  BadRequestException,
  Injectable,
  PipeTransform,
} from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform {
  transform(value: string, metadata: ArgumentMetadata) {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException(
        `Validation failed. "${val}" is not an integer.`,
      );
    }
    return val;
  }
}
Middleware functions have access to the request and response objects, and are not specifically tied to any method, but rather to a specified route PATH.
Middleware functions can perform the following tasks:
When working with middleware, if the current middleware function does not END the request-response cycle, it must call the next() method, which passes control to the next middleware function.
Otherwise, the request will be left hanging - and never complete.
// Generate LoggingMiddleware with Nest CLI
nest g middleware common/middleware/logging
// Apply LoggingMiddleware in our AppModule 
consumer
  .apply(LoggingMiddleware)
  .forRoutes(‘*’);
/* LoggingMiddleware FINAL CODE */
import {
  Injectable,
  NestMiddleware,
} from '@nestjs/common';
@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    console.time('Request-response time');
    console.log('Hi from middleware!');
    
    res.on('finish', () => console.timeEnd('Request-response time'));
    next(); 
  }
}
@Module({
  imports: [ConfigModule],
  providers: [{ provide: APP_GUARD, useClass: ApiKeyGuard }]
})
export class CommonModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggingMiddleware).forRoutes('*');
    //OR
    //     consumer.apply(LoggingMiddleware).forRoutes({ path: 'coffees', method: RequestMethod.GET});
  }
}
// Using the Protocol decorator
@Protocol(/* optional defaultValue */)
/* @Protocal() decorator FINAL CODE */
import {
  createParamDecorator,
  ExecutionContext,
} from '@nestjs/common';
export const Protocol = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.protocol;
  },
);
@Protocol(data)에 넣은 data는 createParamDecorator의 첫번째 인자가 된다.-> use the OpenAPI specification. The OpenAPI specification is a language-agnostic definition format used to describe RESTful APIs.
An OpenAPI document allows us to describe our entire API, including:
/**
 * Installing @nestjs/swagger
 * & Swagger UI for Express.js (which our application uses)
 * 💡 Note: If your application is using Fastiy, install `fastify-swagger` instead
 */
npm install --save @nestjs/swagger swagger-ui-express
// Setting up Swagger document main.ts bootstrap()
const options = new DocumentBuilder()
  .setTitle('Iluvcoffee')
  .setDescription('Coffee application')
  .setVersion('1.0')
  .build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
/** 
 * With the App running (npm run start:dev if not)
 * To view the Swagger UI go to:
 * http://localhost:3000/api
 */
// For unit tests
npm run test 
// For unit tests + collecting testing coverage
npm run test:cov
// For e2e tests
npm run test:e2e
*.spec.tsimport { Test, TestingModule } from '@nestjs/testing';
import { CoffeesController } from './coffees.controller';
describe('CoffeesController', () => {
  let controller: CoffeesController;
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [CoffeesController],
    }).compile();
    controller = module.get<CoffeesController>(CoffeesController);
  });
  it('should be defined', () => {
    expect(controller).toBeDefined();
  });
});
describe() : blockbeforeEach() : test 전에 할 행동/* 
  coffees-service.spec.ts - FINAL CODE
*/
import { Test, TestingModule } from '@nestjs/testing';
import { CoffeesService } from './coffees.service';
import { Connection, Repository } from 'typeorm';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Flavor } from './entities/flavor.entity';
import { Coffee } from './entities/coffee.entity';
import { NotFoundException } from '@nestjs/common';
type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;
const createMockRepository = <T = any>(): MockRepository<T> => ({
  findOne: jest.fn(),
  create: jest.fn(),
});
describe('CoffeesService', () => {
  let service: CoffeesService;
  let coffeeRepository: MockRepository;
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        CoffeesService,
        { provide: Connection, useValue: {} },
        {
          provide: getRepositoryToken(Flavor),
          useValue: createMockRepository(),
        },
        {
          provide: getRepositoryToken(Coffee),
          useValue: createMockRepository(),
        },
      ],
    }).compile();
    service = module.get<CoffeesService>(CoffeesService);
    coffeeRepository = module.get<MockRepository>(getRepositoryToken(Coffee));
  });
  it('should be defined', () => {
    expect(service).toBeDefined();
  });
  describe('findOne', () => {
    describe('when coffee with ID exists', () => {
      it('should return the coffee object', async () => {
        const coffeeId = '1';
        const expectedCoffee = {};
        coffeeRepository.findOne.mockReturnValue(expectedCoffee);
        const coffee = await service.findOne(coffeeId);
        expect(coffee).toEqual(expectedCoffee);
      });
    });
    describe('otherwise', () => {
      it('should throw the "NotFoundException"', async (done) => {
        const coffeeId = '1';
        coffeeRepository.findOne.mockReturnValue(undefined);
        try {
          await service.findOne(coffeeId);
          done();
        } catch (err) {
          expect(err).toBeInstanceOf(NotFoundException);
          expect(err.message).toEqual(`Coffee #${coffeeId} not found`);
        }
      });
    });
  });
});