Nestjs Docs 요약

Jake_Young·2023년 8월 7일
0

DTO는 클래스로 짜야한다.

  1. interface는 runtime에 사용이 불가
  2. Class는 ES6에 정식 포함

. Code first vs Schema first

  1. TS로만 할꺼니까 따로 스키마나 타입 지정 안해도 되는 Code First 방식을 따를 예정
  2. 다른 언어로도 개발할꺼라면 Schema 자동 생성을 하지 않는 Schema First 방식을 써야함

Provider란?

  1. Many of the basic Nest classes may be treated as a provider – services, repositories, factories, helpers, and so on.
  2. The main idea of a provider is that it can be injected as a dependency

Middleware란?

  • Middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects, and the next() middleware function in the application’s request-response cycle.

Exception Filters란?

  1. When an exception is not handled by your application code, it is caught by this layer, which then automatically sends an appropriate user-friendly response.
  2. Out of the box, this action is performed by a built-in global exception filter, which handles exceptions of type HttpException (and subclasses of it)
  3. you may want to add logging or use a different JSON schema based on some dynamic factors.

인스턴스 직접 생성 vs 안생성

@Post()
@UseFilters(new HttpExceptionFilter())
// @UseFilters(HttpExceptionFilter)
// you may pass the class (instead of an instance), leaving responsibility for instantiation to the framework, and enabling dependency injection.
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

글로벌 적용 방식 2가지

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

//

import { Module } from "@nestjs/common";
import { APP_FILTER } from "@nestjs/core";

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

Pipes란?

  1. A pipe is a class annotated with the @Injectable() decorator, which implements the PipeTransform interface.
  2. Pipes have two typical use cases:
    • transformation: transform input data to the desired form (e.g., from string to integer)
    • validation: evaluate input data and if valid, simply pass it through unchanged; otherwise, throw an exception

Guards란?

  1. A guard is a class annotated with the @Injectable() decorator, which implements the CanActivate interface.
  2. This is often referred to as authorization. Authorization (and its cousin, authentication, with which it usually collaborates) has typically been handled by middleware in traditional Express applications. Middleware is a fine choice for authentication, since things like token validation and attaching properties to the request object are not strongly connected with a particular route context (and its metadata).
  3. But middleware, by its nature, is dumb. It doesn't know which handler will be executed after calling the next() function. On the other hand, Guards have access to the ExecutionContext instance, and thus know exactly what's going to be executed next. They're designed, much like exception filters, pipes, and interceptors, to let you interpose processing logic at exactly the right point in the request/response cycle, and to do so declaratively. This helps keep your code DRY and declarative.
  4. Guards are executed after all middleware, but before any interceptor or pipe.

Interceptors 1

1. An interceptor is a class annotated with the `@Injectable()` decorator and implements the `NestInterceptor` interface.
2. Interceptors have a set of useful capabilities which are inspired by the **[[Aspect Oriented Programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming)](https://en.wikipedia.org/wiki/Aspect-oriented_programming)** (AOP) technique. They make it possible to:
   - bind extra logic before / after method execution
   - transform the result returned from a function
   - transform the exception thrown from a function
   - extend the basic function behavior
   - completely override a function depending on specific conditions (e.g., for caching purposes)

Interceptors 2

```tsx
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log("Before...");

    const now = Date.now();
    return next
      .handle()
      .pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
  }
}
```

Interceptors 3

```tsx
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";

export interface Response<T> {
  data: T;
}

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>>
{
  intercept(
    context: ExecutionContext,
    next: CallHandler
  ): Observable<Response<T>> {
    return next.handle().pipe(map((data) => ({ data })));
  }
}
```

Execution Context란? 1

1. Utilities provide information about the current execution context which can be used to build generic **[[guards](https://docs.nestjs.com/guards)](https://docs.nestjs.com/guards)**, **[[filters](https://docs.nestjs.com/exception-filters)](https://docs.nestjs.com/exception-filters)**, and **[[interceptors](https://docs.nestjs.com/interceptors)](https://docs.nestjs.com/interceptors)** that can work across a broad set of controllers, methods, and execution contexts. We cover two such classes in this chapter: `ArgumentsHost` and `ExecutionContext`.
2. The `ArgumentsHost` class provides methods for retrieving the arguments being passed to a handler.

Execution Context란? 2

if (host.getType() === "http") {
  // do something that is only important in the context of regular HTTP requests (REST)
} else if (host.getType() === "rpc") {
  // do something that is only important in the context of Microservice requests
} else if (host.getType<GqlContextType>() === "graphql") {
  // do something that is only important in the context of GraphQL requests
}

Execution Context란? 3

const [req, res, next] = host.getArgs();

/* Switch context to RPC */
switchToRpc(): RpcArgumentsHost;
/* Switch context to HTTP */
switchToHttp(): HttpArgumentsHost;
/* Switch context to WebSockets */
switchToWs(): WsArgumentsHost;
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();

Execution Context란? 4

  • ExecutionContext extends ArgumentsHost

  • Nest provides an instance of ExecutionContext in places you may need it, such as in the canActivate() method of a guard and the intercept() method of an interceptor

export interface ExecutionContext extends ArgumentsHost {
  /* Returns the type of the controller class which the current handler belongs to */
  getClass<T>(): Type<T>;
  /* Returns a reference to the handler (method) that will be invoked next in the request pipeline */
  getHandler(): Function;
}

const methodKey = ctx.getHandler().name; // "create"
const className = ctx.getClass().name; // "CatsController"

Reflection and metadata?

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
}

// the better way is below

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

//

@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
}

//

@Injectable()
export class RolesGuard {
    constructor(private reflector: Reflector) {}
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
}

Lifecycle Events

- https://docs.nestjs.com/fundamentals/lifecycle-events
- graceful shotdown 같은 것 🙂

Configuration

  1. A good approach for using this technique in Nest is to create a ConfigModule that exposes a ConfigService which loads the appropriate .env file. While you may choose to write such a module yourself, for convenience Nest provides the @nestjs/config package out-of-the box.
  2. The above code will load and parse a .env file from the default location (the project root directory), merge key/value pairs from the .env file with environment variables assigned to process.env, and store the result in a private structure that you can access through the ConfigService. The forRoot() method registers the ConfigService provider, which provides a get() method for reading these parsed/merged configuration variables. Since @nestjs/config relies on dotenv, it uses that package's rules for resolving conflicts in environment variable names. When a key exists both in the runtime environment as an environment variable (e.g., via OS shell exports like export DATABASE_USER=test) and in a .env file, the runtime environment variable takes precedence.

GraphQL 1

  • mutation / subscription은 설명 생략
// model.ts

import { Field, Int, ObjectType } from "@nestjs/graphql";
import { Post } from "./post";

@ObjectType()
export class Author {
  @Field((type) => Int)
  id: number;

  @Field({ nullable: true })
  firstName?: string;

  @Field({ nullable: true })
  lastName?: string;

  @Field((type) => [Post]) // type 중복 지정 설명
  posts: Post[];
}

GraphQL 2

// resolver.ts

@Resolver((of) => Author) // parent
export class AuthorsResolver {
  constructor(
    private authorsService: AuthorsService,
    private postsService: PostsService
  ) {}

  @Query((returns) => Author) // return type
  // arg type 설명
  async author(@Args("id", { type: () => Int }) id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField()
  async posts(@Parent() author: Author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id });
  }
}
profile
자바스크립트와 파이썬 그리고 컴퓨터와 네트워크

2개의 댓글

comment-user-thumbnail
2023년 8월 7일

이렇게 유용한 정보를 공유해주셔서 감사합니다.

1개의 답글