Nest.js code first

zenibako.lee
7 min readSep 4, 2021

--

Code First 방식에 따라 nest.js 에서
graphql 구현 시, Resolver, Model 를 어떻게 사용하는지 요약해본다.
설정에 관한 글은 아니고
셋팅이 되어 있다는 전제하에 빠르게 로직개발을 하기 위한 내용이다.
공식문서를 참고하여 개인적으로 공부하는 겸 작성해 보았다.

가장 먼저 DTO(…스키마,모델) 정의가 필요하다.

스키마 파일(SDL) 을 직접 작성하는 Schema First 방식, 그리고
nest.js 의 @nestjs/graphql 패키지의 데코레이터를 사용하여,
Resolver 파일에 작성된 메타데이터를 통해
스키마 파일을 자동으로 생성하는 Code First 방식이 있다.

필자는 기존에 사용하던 Schema First 방식의 단점을 느끼는 바,
Nest.js 를 통한 graphql api 구현 시, Code First 방식을 선택했다.

예를 들어, 스키마 우선 방식에서는 아래와 같이 리턴 타입을 먼저 작성하고

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

리졸버에서는 해당 타입을 리턴할 수 있도록 신경쓰는 방식이었다면,

Code First 방식에서는 model 클래스를 데코레이터를 이용하여 선언한다.
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[];
}

Resolver Class ( Code First Resolver )

위에서 데이터 그래프에 존재할 수 있는 객체 (유형 정의, DTO) 를 정의했지만,
실제로 해당 객체를 클라이언트 -서버간 주고받을 수 있는 로직이 존재하지 않는다. 리졸버(리졸버 클래스)가 이를 가능하게 한다.
(rest api 의 경우, Controller 클래스 사용)
(실제 비즈니스 로직은 Service 클래스에 작성한다)

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

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

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

해당 코드블록에서, 주목할 부분은
Author 모듈의 Resolver 클래스에서 외부(Posts) Service 를 사용하는 방식,
Resolver 데코레이터에 인자로 들어간 콜백의 의미
Query 데코레이터에 인자로 들어간 콜백의 의미 정도로 보인다.

@Resolver

먼저 Resolver decorator 함수의 인자를 보자,
첫번째 인자로 classType을 펑션으로 받게되는데,
인자는 상황에 따라 의무적으로 넘겨줘야 한다.
의무적으로 넣어줘야 하는 경우는 공식문서에 따르면
“when our graph becomes non-trivial”
우리의 그래프(리턴객체)가 사소해지지 않은 경우.
“ It’s used to supply a parent object used by field resolver functions”
즉, 필드 리졸버 펑션에서 부모 오브젝트로서 공급을 해줘야 하는 경우에 해당된다.

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

Author 리졸버의 필드 리졸버인 Post 펑션은 Parent 데코레이터를 통해, 부모 오브젝트 (author:Author) 를 공급받기 때문에,
Resolver 데코레이터에서 클래스 타입 펑션을 첫번째 인자로 넣어줘야 한다.
예제 코드블럭의 경우 애로우 펑션 형태로 넘기고 있다. (of) => Author

@Query

쿼리 리졸버 데코레이터는 typeFunc:ReturnTypeFunc 를 첫번째 인자로 받고있다

@Query(returns => Author)

예제에서는 returnTypeFunc 타입의 typeFunc 를 arrow function 형태로 넣고 있다. 쿼리 리턴 타입을 이런 방식으로 선언하나보다.

두번째 인자로 Query Resolver 의 옵션들을 넣을 수 있는데,
메소드 이름을 통해서 자동으로 생성하는 쿼리 타입 네임을 직접 지정할 수 있다.

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

첫번째 예시에서는 쿼리 핸들러 메소드의 이름을 통해 쿼리 타입 이름을 지정하는 반면,
두번째의 경우, 쿼리 핸들러 메소드 이름에는 get… 컨벤션을 사용,
쿼리 타입 네임은 author 를 명시적으로 지정한다.

특정한 코드 스타일을 유지하고 싶다면 유용하게 사용할 듯

nest.js 공식문서의 경우 쿼리 리졸버 옵션 중 자주 사용되는 예제들을 확인할 수 있다.

@Args

핸들러 메소드에서 요청에 담겨온 데이터에서, 특정 데이터를 추출/사용하기 위해 사용된다. 그리고 code first 방식에서는 해당 데코레이터를 기반으로 스키마의 인풋이 자동으로 정의된다.

기억할 만한 부분은 number 타입의 경우, 두번째 인자를 통해
typescript 에서는 명시하지 않는 graphQL 의 Int/Float 구분을 명시해줄 수 있다는 점?

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

--

--

zenibako.lee
zenibako.lee

Written by zenibako.lee

backend engineer, JS, Node, AWS

No responses yet