NestJS Logo

원문을 읽으며 정리합니다.

Table of Contents

  1. Resolvers

Resolvers

Resolvers는 GraphQL operation(query, mutation, subscription)을 데이터로 변환하기 위한 명령을 제공한다. 그들은 우리의 스키마에서 명시한 것과 동일한 모양의 데이터를 반환한다(동기적이거나 그 모양의 결과를 resolve하는 promise). 일반적으로 resolver map을 수동으로 생성한다. 반면, @nestjs/graphql package는 class에 annotate하는데 사용한 decorators에 의해 제공된 메타데이터를 사용해 자동으로 resolver map을 생성한다. package feature를 사용하여 GraphQL API를 생성하는 프로세스를 증명하기 위해, 간단한 authors API를 만들어 보자.

Code first

code first approach에서, GraphQL SDL을 손으로 작성하여 GraphQL 스키마를 생성하는 일반적인 프로세스를 따르지 않는다. 대신, TypeScript class 정의에서 SDL을 생성하기 위해 TypeScript decorators를 사용한다. @nestjs/graphql package는 데코레이터를 통하여 정의된 메타데이터를 읽고 자동으로 스키마를 생성한다.

Object types

GraphQL 스키마 정의는 대부분 object types이다. 정의하는 각 object type은 클라이언트가 상호작용할 수 있는 도메인 객체를 나타냅니다. 예를 들어, 샘플 API에서 작성자와 게시물의 목록을 가져올 수 있어야 하므로, 이 기능을 지원하기 위해 Author type과 Post type을 정의해야 한다.

만약 schema first approach를 사용한다면, 아래와 같은 SDL 스키마를 정의할 수 있다.

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}

이 경우, code first approach를 사용하여, TypeScript classes와 TypeScript decorators를 사용하여 해당 클래스의 필드에 annotate할 수 있는 스키마를 정의한다. code first approach에서 위의 SDL은 아래와 동일하다.

// authors/models/author.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])
  posts: Post[];
}

TypeScript의 metadata reflection system은 클래스가 어떤 속성으로 구성되는지 확인하거나 제공된 속성이 optional인지 required인지 알아내는것을 불가능하게 하는 몇 가지 제한이 있다. 이러한 제한 때문에, 우리는 스키마 정의 클래스안에 @Field() 데코레이터를 사용하여 각 필드의 GraphQL type과 optionality를 제공해야 한다. 또는 CLI plugin을 사용하여 생성할 수 도 있다.

Author object type은 마치 다른 클래스처럼, 필드 집합으로 작성되며, 각 필드는 type을 선언한다. 필드의 type은 GraphQL type에 해당한다. 필드의 GraphQL type은 다른 object type or a scalar type 일 수 있다. GraphQL scalar type은 하나의 값으로 resolve하는 원시 값이다(ID, String, Boolean, Int).

GraphQL의 내장된 스칼라 type 외에도 커스텀 scalar type을 정의할 수 있다(read more).

위에서 Author object type definition은 NestJS가 위에서 봤던 SDL을 generate하도록 야기한다.

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}

@Field() decorator는 optional type funcion(e.g., type => Int)을 허용하며, optionally options object도 허용한다.

TypeScript type system과 GraphQL type system 사이에서 애매한 가능성이 있다면 type function이 필요하다. 특히: stringboolean type에는 필요하지 않다. number(GraphQL Int or Float으로 매핑되어야 함)에 필요하다. type function은 원하는 GraphQL type(이 챕터의 예제에서 본 다양항 종류처럼)을 반환하는 것이 좋다.

options object는 다음 key/value 쌍중에 어느 것이든 포함될 수 있다.

  • nullable: 필드가 null이 가능한지 여부를 지정하기 위해(SDL에서, 각 필드는 default로 non-nullable이다); boolean
  • description: field의 설명을 설정하기 위해; string
  • deprecationReason: 필드가 사용되지 않음으로 표시하기 위해; string

예제:

@Field({ description: `Book title`, deprecationReason: 'Not useful in v2 schema' })
title: string;

전체의 object type에 description 이랑 deprecate를 추가할 수 도 있다: @ObjectType({ decription: 'Author model' }).

field가 배열인 경우, 아래와 같이 Field() decorator의 type function에 array type임을 수동으로 표시해야 한다.

@Field(type => [Post])
posts: Post[];

array bracket 표기법( [ ] ) 을 사용하여, 배열의 깊이를 표시할 수 있다. 예를 들어, [[Int]]를 사용하여 integer matrix(2차원 배열?)를 나타낼 수 있다.

array의 items(array 자체가 아닌)를 nullable로 선언하려면, 아래와 같이 nullable property를 'items'로 설정하자.

@Field(type => [Post], { nullable: 'items' })
posts: Post[];

array와 배열의 items를 둘다 nullable로 하고 싶다면, nullableitemsAndList로 설정하자.

이제 Author object type이 생성되었으니, Post object type을 정의하자.

// posts/models/post.model.ts
import { Field, Int, ObjectType } from '@nestjs/graphql';

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

  @Field()
  title: string;

  @Field((type) => Int, { nullable: true })
  votes?: number;
}

Post object type은 SDL에서 GraphQL schema의 다음 부분이 생성될 것이다.

type Post {
  id: Int!
  title: String!
  votes: Int
}

Code first resolver

이 시점에서, 우리의 데이터 그래프에서 존재할 수 이는 objects(type definitions)를 정의 했지만 client가 이러한 객체들과 interact 할 수 있는 방법이 없다. 이 문제를 해결하기 위해, resolver class를 만들어야 한다. code first method 에서는, resolver class는 resolver functions 을 정의하고 and Query type을 생성한다. 아래 예제를 통해 이 작업은 분명해진다.

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

  @Query((returns) => Author)
  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 });
  }
}

모든 데코레이터(e.g., @Resolver, @ResolveField, @Args, etc.)는 @nestjs/graphql package에 있다.

여러가지 resolver classes를 정의할 수 있다. NestJS는 런타임에 이것들은 결합한다. 아래의 module section에서 code 구성에 대한 자세한 내용을 보자.

AuthorsService and PostsService class에서 logic은 단순하거나 필요에 따라 정교할 수 있다. 이 예제에서 요점은 resolver를 구성하는 방법과 resolver가 다른 provider와 상호작용 할 수 있는 방법을 보여주는 것이다.

위 예제에서, 하나의 query resolver function과 field resolver function을 정의하는 AuthorsResolver를 만들었다. resolver를 만들기 위해서는, resolver functions를 메서드로 사용하여 클래스를 만들고, @Resolver() decorator를 사용하여 클래스에 annotate 한다.

이 예제에서는, request에 보내진 id를 기반으로 autor object를 얻기 위한 쿼리 핸들러를 정의했다. 메서드가 query handler임을 명시하려면 @Query() decorator를 사용한다.

@Resolver() decorator에 전달된 인자는 선택사항이다. 하지만 우리의 graph(?)가 중요할때 사용한다. field resolver function이 object graph를 통과할 때 사용되는 parent object를 제공하기 위해 사용한다.

우리의 예제에서, class에는 field resolver function(Author 객체의 posts 속성을 위한)을 포함하기 때문에, 우리는 이 클래스 안에 정의된 모든 field resolver의 부모 type(i.e., 해당하는 ObjectType class name)을 나타내는 값을 @Resolver() decorator에 제공해야 한다. 명확한 예를 들면, field resolver function을 작성할 때, parent object(resolve 중인 filed가 member인 object)에 접근할 필요가 있다. 이러한 예에서, author의 id를 인자로 사용하는 service를 호출하는 field resolver로 작성자의 posts를 배열로 채운다. 따라서 @Resolver() decorator에 parent object를 식별해야 한다. @Parent() method parameter decorator를 사용하여 field resolver에 parent object에 해당하는 참조를 추출한다.

여러 @Query() resolver function(이 클래스 내에 또는 다른 resolver class)을 정의할 수 있다. 그리고 그들은 resolver map안의 적절한 entries와 함께 생성된 SDL의 단일 Query type 정의로 집계된다. 따라서 사용하는 모델 및 서비스에 가까이 쿼리를 정의하고, 모듈별로 잘 구성할 수 있다.

NestJS CLI는 all the boilerplate code를 자동으로 생성하는 generator (schematic)를 제공하여 이러한 모든 작업을 피하고, 개발자는 더 간편하게 경험하도록 지원한다. 이 기능에 대해서는 여기서 읽어보자.

Query type names

위 예제에서, @Query() decorator는 메서드 이름에 기반한 GraphQL schema query type 이름을 생성한다. 예를 들어, 위 예와 같이 다음처럼 구성했다고 하자.

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

이것은 schema(query type은 method name과 같은 name을 사용한다)에서 author query에 대해 다음 entry를 생성한다.

type Query {
  author(id: Int!): Author
}

GrpahQL query에 관한 더 많은 내용은 여기서 배우자.

일반적으로, 이러한 이름을 분리하는 것을 선호한다. 예를 들어, 쿼리 핸들러 메서드에 getAuthor() 처럼 이름을 사용하기를 선호하지만 query type name에 대해서는 여전히 author를 사용한다. field resolvers도 똑같이 적용할 수 있다. 아래 보이는 것처럼, 매핑할 이름을 @Query() and @ResolveField() decorator에 인자로 전달하면 쉽게 이 작업을 수행할 수 있다.

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

  @Query((returns) => Author, { name: 'author' })
  async getAuthor(@Args('id', { type: () => Int }) id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField('posts', (returns) => [Post])
  async getPosts(@Parent() author: Author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id });
  }
}

위의 getAuthor handler method는 SDL에서 GraphQL schema의 다음 부분을 생성한다.

type Query {
  author(id: Int!): Author
}

Query decorator options

@Query() decorator의 option 객체(위에서 전달한 { name: 'author' })는 다음과 같은 key/value의 쌍을 허용한다.

  • name: query의 이름; string
  • description: GraphQL schema documentation에 사용될 설명 (e.g., in GraphQL playground); string
  • deprecationReason: 쿼리가 사용되지 않는 것으로 표시되도록 쿼리 메타데이터를 설정 (e.g., in GraphQL playground); string
  • nullable: query가 null data response를 반환할 수 있는지 여부; boolean or 'items' or 'itemsAndList' ('items' and 'itemsAndList'에 대한 detail은 위에서 보자)

Args decorator options

method handler에서 사용한 request로부터 인자를 추출하기 위해 @Args() decorator를 사용한다. 이는 REST route parameter argument extraction와 매우 유사하게 작동한다.

일반적으로 @Args() decorator는 간단하며, 위 getAuthor() method에서 본 것처럼 객체 인자가 필요하지 않다. 예를 들어, 식별자의 type이 string인 경우, 다음과 같은 구성이면 충분하며, 메서드 인자로 사용하기 위해 inbound GraphQL request에서 명명된 필드를 간단하게 뽑는다.

@Args('id') id: string

getAuthor()의 경우, number type이 사용되며, 이는 challenge를 제시한다. number TypeScript type은 예상되는 GraphQL 표현에 대한 충분한 정보를 제공하지 않는다(e.g., Int vs. Float). 따라서 명확하게 type reference를 전달해야 한다. 아래 나온 것처럼 인자 옵션을 포함해 Args() decorator에 두번 째 인자를 전달함으로써 이를 수행한다.

@Query(returns => Author, { name: 'author' })
async getAuthor(@Args('id', { type: () => Int }) id: number) {
  return this.authorsService.findOneById(id);
}

option object는 선택적으로 다음과 같은 key/value 쌍을 지정할 수 있다.

  • type : GraphQL type을 리턴하는 function
  • defaultValue : default value; any
  • description : metadata 설명; string
  • deprecationReason : 필드를 더 이상 사용하지 않고 그 이유를 설명하는 메타데이터를 제공한다. string
  • nullable : field가 nullable인지 아닌지.

Query handler method는 여러가지 인자를 갖을 수 있다. firstNamelastName을 기준으로 author를 가져온다고 상상해보자. 이 경우, @Args를 두번 호출할 수 있다.

getAuthor(
  @Args('firstName', { nullable: true }) firstName?: string,
  @Args('lastName', { defaultValue: '' }) lastName?: string,
) {}

Dedicated arguments class

inline으로 @Args()를 호출하면, 위와 같은 코드가 방대해진다. 대신에, 다음과 같이 전용 GetAuthorArgs arguments class를 만들고 handler method에서 접근할 수 있다.

@Args() args: GetAuthorArgs

아래와 같이 @ArgsType()을 사용하여 GetAuthorArgs class를 만들자.

// authors/dto/get-author.args.ts
import { MinLength } from 'class-validator';
import { Field, ArgsType } from '@nestjs/graphql';

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

  @Field({ defaultValue: '' })
  @MinLength(3)
  lastName: string;
}

다시 말하지만, TypeScript의 metadata reflection system은 제한되므로, @Field decorator를 사용하여 type과 optionally를 수동으로 표시해주거나 CLI plugin을 사용해야 한다.

그러면 SDL에서 GrpahQL schema의 다음 부분이 생성된다.

type Query {
  author(firstName: String, lastName: String = ''): Author
}

GetAuthorArgs 같은 인자 클래스는 ValidationPipe와 함께 매우 잘 작동한다(read more).

Class inheritance

표준 TypeScript class 상속을 사용하여 확장 가능한 포괄적인 utility 기능(fields and field properties, validations, etc.)을 사용하여 base class를 만들 수 있다. 예를 들어, 항상 표준 offet and limit fields를 포함하는 페이지네이션 관련 인자들의 집합과 type-specific한 다른 index fields를 가질 수 있다. 다음과 같이 클래스 hierarchy를 설정할 수 있다.

Base @ArgsType class:

@ArgsType()
class PaginationArgs {
  @Field((type) => Int)
  offset: number = 0;

  @Field((type) => Int)
  limit: number = 10;
}

base @ArgsType() 클래스의 type specific sub-class:

@ArgsType()
class GetAuthorArgs extends PaginationArgs {
  @Field({ nullable: true })
  firstName?: string;

  @Field({ defaultValue: '' })
  @MinLength(3)
  lastName: string;
}

같은 접근 방식으로 @ObjectType() objects를 취할 수 있다. base class에 포괄적인 속성 정의:

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

  @Field()
  name: string;
}

sub-class에 type-specific 속성 추가:

@ObjectType()
class Warrior extends Character {
  @Field()
  level: number;
}

resolver와도 함께 상속을 사용할 수 있다. 상속과 TypeScript generics를 결합하여 안전한 type을 보장할 수 있다. 예를 들어, generic findAll query와 함께 base class를 만들고, 다음과 같이 사용한다.

function BaseResolver<T extends Type<unknown>>(classRef: T): any {
  @Resolver({ isAbstract: true })
  abstract class BaseResolverHost {
    @Query((type) => [classRef], { name: `findAll${classRef.name}` })
    async findAll(): Promise<T[]> {
      return [];
    }
  }
  return BaseResolverHost;
}

다음 사항을 유의하자.

  • 명시적 반환 타입(위에선 any)이 필요하다. 그렇지 않으면 TypeScript는 private class 정의의 사용에 대해 컴플레인을 건다. 권장: any를 사용하는 대신 인터페이스를 정의하자.
  • Type@nests/common package로부터 임포트 되었다.
  • isAbstract: true 속성은 이 클래스에 대해 SDL(Schema Definition Language statements)이 생성되지 않아야 함을 나타낸다. SDL 생성을 억제하기 위해 다른 유형에 대해서도 이 속성을 설정할 수 있다.

다음은 BaseResolver의 구체적인 sub-class를 생성하는 방법이다:

@Resolver((of) => Recipe)
export class RecipesResolver extends BaseResolver(Recipe) {
  constructor(private recipesService: RecipesService) {
    super();
  }
}

이 구조는 다음과 같은 SDL을 생성한다:

type Query {
  findAllRecipe: [Recipe!]!
}

Generics

위에서 generic의 한 가지 사용을 봤다. 이 강력한 TypeScript feature를 사용하여 유용한 추상화를 만들 수 있다. 예를 들어, this documentation에 기반한 cursor-based pagination 구현은 다음과 같다.

import { Field, ObjectType, Int } from '@nestjs/graphql';
import { Type } from '@nestjs/common';

export function Paginated<T>(classRef: Type<T>): any {
  @ObjectType(`${classRef.name}Edge`)
  abstract class EdgeType {
    @Field((type) => String)
    cursor: string;

    @Field((type) => classRef)
    node: T;
  }

  @ObjectType({ isAbstract: true })
  abstract class PaginatedType {
    @Field((type) => [EdgeType], { nullable: true })
    edges: EdgeType[];

    @Field((type) => [classRef], { nullable: true })
    nodes: T[];

    @Field((type) => Int)
    totalCount: number;

    @Field()
    hasNextPage: boolean;
  }
  return PaginatedType;
}

위의 base class를 정의하면, 이제 이 동작을 상속하는 특별한 유형을 쉽게 만들 수 있다. 예를 들어:

@ObjectType()
class PaginatedAuthor extends Paginated(Author) {}

Schema first

이전 장에서 언급했듯이, schema first 방식에서는 SDL에서 schema types를 수동으로 정의하는 것으로 시작한다(read more). 다음 SDL type 정의를 고려해보자.

이 장에서 편의를 위해, 모든 SDL를 하나의 위치(e.g., 아래 나온 것처럼 하나의 .graphql file)로 집계했다. 실제로, modular 유형에서 코드에 적절하게 구성하는 법을 찾을 수 있다. 예를 들어, 각 도메인 엔티티를 나타내는 type 정의와, 관련된 서비스, resolver code, NestJS module 정의 class와 함께 개별 SDL 파일을 해당 엔티티의 전용 디렉토리에 생성하는 것이 유용할 수 있다. NestJS는 런타임에서 모든 개별 스키마 type 정의를 집계한다.

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}

type Post {
  id: Int!
  title: String!
  votes: Int
}

type Query {
  author(id: Int!): Author
}

Schema first resolver

위의 스키마는 single query를 나타낸다 - author(id: Int!): Author.

더 많은 GraphQL query들은 여기서 배우자.

이제 author query를 resolve하는 AuthorsResolver class를 만들어 보자.

// authors/authors.resolver.ts
@Resolver('Author')
export class AuthorsResolver {
  constructor(
    private authorsService: AuthorsService,
    private postsService: PostsService
  ) {}

  @Query()
  async author(@Args('id') id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField()
  async posts(@Parent() author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id });
  }
}

모든 데코레이터(e.g., @Resolver, @ResolveField, @Args, etc.)는 @nestjs/graphql package에 있다.

AuthorsService and PostsService class에서 logic은 단순하거나 필요에 따라 정교할 수 있다. 이 예제에서 요점은 resolver를 구성하는 방법과 resolver가 다른 provider와 상호작용 할 수 있는 방법을 보여주는 것이다.

@Resolver() decorator가 요구된다. 이는 class의 이름을 선택적으로 문자열 인자로 갖는다. 이 클래스 이름은 decorate된 메서드가 parent type(현재 예제에서는 Author type)과 연관되어 있음을 NestJS에게 알리기 위해 @ResolveField() decorator를 포함할 때 마다 필요하다. 또는, 클래스의 맨 위에 @Resolver()를 설정하는 대신에, 각 메서드에 이 작업을 수행할 수 있다.

@Resolver('Author')
@ResolveField()
async posts(@Parent() author) {
  const { id } = author;
  return this.postsService.findAll({ authorId: id });
}

이 경우(@Resolver() decorator가 메서드 레벨에 위치할때), class안에 여러개의 @ResolveField()가 있는 경우, 모든 그것들에게 모두 @Resolver()를 추가해야 한다. 이는 추가 오버헤드가 발생하기 때문에 좋은 사례가 아니다.

@Resolver()에 전달된 클래스 이름 인자는 쿼리(@Query()) 또는 mutations(@Mutations())에 영향을 주지 않는다.

@Resolver decorator를 메서드 레벨에 사용할 때 code first 방식에서는 지원되지 않는다.

위의 예에서, @Query() and @ResolveField() decorator는 메서드 이름을 기반으로 GraphQL schema type과 연관된다. 예를 들어, 위의 예에서 다음과 같은 구성을 고려하자.

@Query()
async author(@Args('id') id: number) {
  return this.authorsService.findOneById(id);
}

스키마에서 author query에 대해 다음 entry를 생성한다(쿼리 type은 메서드 이름과 동일하다).

type Query {
  author(id: Int!): Author
}

전통적으로, resolver methods를 위해 getAuthor() or getPosts()와 같은 이름을 사용하여 이러한 이름을 분리하는 것이 좋다. 아래와 같이 매핑 이름을 decorator에게 인자로 전달하면 쉽게 이 작업을 수행할 수 있다.

// authors/authors.resolver.ts
@Resolver('Author')
export class AuthorsResolver {
  constructor(
    private authorsService: AuthorsService,
    private postsService: PostsService
  ) {}

  @Query('author')
  async getAuthor(@Args('id') id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField('posts')
  async getPosts(@Parent() author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id });
  }
}

NestJS CLI는 all the boilerplate code를 자동으로 생성하는 generator (schematic)를 제공하여 이러한 모든 작업을 피하고, 개발자는 더 간편하게 경험하도록 지원한다. 이 기능에 대해서는 여기서 읽어보자.

Generating types

schema first 방식을 사용하고 typings generation feature(with as shown
outputAs: 'class' in previous chapter)을 활성화 했다고 가정하면, 애플리케이션이 실행 할 때 다음 파일이 생성될 것이다(GraphQLModule.forRoot() method에 명시한 위치). 예를 들어 src/graphql.ts:

// graphql.ts
export class Author {
  id: number;
  firstName?: string;
  lastName?: string;
  posts?: Post[];
}

export class Post {
  id: number;
  title: string;
  votes?: number;
}

export abstract class IQuery {
  abstract author(id: number): Author | Promise<Author>;
}

클래스를 생성함(인터페이스 생성의 디폴트 기술 대신에)에 따라 schema first 방식과 함께 선언적 validation decorators를 사용할 수 있다. 이 방법은 매우 유용한 기술이다(read more). 예를 들어, 생성된 CreatePostInput 클래스에 class-validator 데코레이터를 추가할 수 있다. title 필드에 최소 및 최대 문자열 길이를 적용하려면 아래처럼:

import { MinLength, MaxLength } from 'class-validator';

export class CreatePostInput {
  @MinLength(3)
  @MaxLength(50)
  title: string;
}

input과 parameter에 자동 검증을 활성화 하려면 ValidationPipe를 사용하자. 더 많은 validationpipe를 읽어 보자.

그러나 자동 생성된 파일에 직접 decorator를 추가하면, 파일이 생성될 때마다 overwritten된다. 대신 별도의 파일을 만들고 생성된 클래스를 상속하면 된다.

import { MinLength, MaxLength } from 'class-validator';
import { Post } from '../../graphql.ts';

export class CreatePostInput extends Post {
  @MinLength(3)
  @MaxLength(50)
  title: string;
}

GraphQL argument decorators

전용 decorator를 사용하여 표준 GraphQL resolver 인자에 접근할 수 있다. 아래는 NestJS decorator들과 그것들이 나타내는 평범한 Apollo 파라미터의 비교이다.

@Root() and @Parent() root / parent
@Context(param?: string) context / context[param]
@Info(param?: string) info / info[param]
@Args(param?: string) args / args[param]

이러한 인자들은 다음과 같은 의미를 따른다:

  • root: 부모 field의 resolver로부터 반환된 결과를 포함하는 객체 또는 최상위 레벨의 Query 필드의 경우, 서버 구성에서 전달된 rootValue를 포함하는 객체.
  • context: 특정 쿼리에서 모든 resolver가 공유하는 객체; 일반적으로 요청별 상태를 포함하는데 사용한다.
  • info: 쿼리의 실행 상태에 대한 정보를 포함하는 객체.
  • args: 쿼리의 필드에 전달된 인자를 갖는 객체.

Module

위의 단계를 마치면, GraphQLModule이 resolver map을 생성하는데 필요한 모든 정보를 선언적으로 명시했다. GraphQLModule은 reflection을 사용하여 decorator를 통해 제공된 메타 데이터를 검사하고, 자동으로 클래스를 올바른 resolver map으로 변환한다.

다른 주의 사항은 resolver 클래스(i.e., 모듈에서 provider의 리스트목록)를 제공하고(AuthorsResolver), 모듈(AuthorsModule)을 어딘가에 임포트 하는 것이므로, NestJS는 이를 활용할 수 있다.

예를 들어, AuthorsModule에서 이 작업을 수행할 수 있으며, 이러한 맥락에서 필요한 다른 서비스도 제공할 수 있다. 어딘가에서 AuthorsModule을 import해야 한다(e.g., root module에서, 또는 root module에서 import하는 다른 모듈).

// authors/authors.module.ts
@Module({
  imports: [PostsModule],
  providers: [AuthorsService, AuthorsResolver],
})
export class AuthorsModule {}

domain model이라 불리는 것을 기준으로 코드를 구성하는 것이 좋다(REST API에서 entry point를 구성하는 방법과 유사함). 이러한 방식에서, domain model을 나타내는 NestJS module에서 모델(ObjectType class), resolver, service를 함께 유지한다. 이러한 모든 구성요소를 모듈당 하나의 폴더에 보관해라. 이 작업을 수행하고 Nest CLI를 사용하여 각 요소를 생성하면, NestJS가 이러한 부분을 함께 연결한다(적절한 폴더 위치에 파일 생성, providers and imports 배열의 항목 생성).

Next NestJS GraphQL(3) - Mutations
Intro NestJS GraphQL(0) - Intro