NestJS GraphQL 빠르게 배우기 01

NestJS GraphQL 빠르게 배우기 01

프로젝트에 도입할 그래프큐엘 서버를 구성하기 위해서 NestJS에서 그래프큐엘이 어떻게 구성되어야 하는지에 대해서 공부해보려고 한다. 이 시리즈는 NestJS 공식 문서에서 제공해주는 내용을 해석한 내용을 적당히 가감해서 정리한 글이 될 예정이다.

NestJS에서의 GraphQL

GraphQL은 API를 만들기 위한 쿼리 언어이다. RestAPI에서 생기는 몇 가지 문제들을 해결해 줄 수 있기도 하다.

이번 글에서는 GraphQL에 대한 기본적인 이해를 전제로 하고 있다. 그리고 어떻게 @nestjs/graphql을 사용할지에 대해 집중하고 있다. GraphQLModuleApollo 서버를 래핑하고 있는 모듈이다.

설치하기

1
yarn add @nestjs/graphql graphql-tools graphql

위 명령어로 의존성을 설치해줄 수 있다. 또한 express 기반의 서버라면 apollo-server-express를 추가로 설치해야 한다.

Overview

Nest는 GraphQL 앱을 만들 두 가지 방법을 제안하는데, 하나는 code first 방식이고, 다른 하나는 schema first 방식이다. 잘 맞는 방법 중 하나를 선택하면 된다.

code first 방식은 데코레이터와 타입스크립트의 클래스를 사용해서 GraphQL 스키마와 상응하는 것을 만들게 되는데, 이 방법은 타입스크립트만을 배타적으로 사용하기 위한 경우 알맞는 방식이다.

schema first 방식에서는 소스의 원천 (source of truth)가 GraphQL Schema Definition Language file (GraphQL SDL)이다. SDL은 특정 언어와 관계 없는 방향으로 스키마 파일들을 다른 플랫폼 사이에서 제공해준다. Nest는 자동적으로 타입스크립트 정의를 GraphQL 스키마에 기반해서 생성해주게 된다.

GraphQL & TypeScript 사용하기

패키지가 설치된 다음, GraphQLModule과 설정을 forRoot() 스태틱 메서드를 사용해서 임포트 할 수 있다.

1
2
3
4
5
6
7
import { Module } from "@nestjs/common";
import { GraphQLModule } from "@nestjs/graphql";

@Module({
imports: [GraphQLModule.forRoot({})]
})
export class AppModule {}

forRoot() 메서드는 옵션 오브젝트를 인자 값으로 받는데, 이 값은 Apollo 인스턴스를 통과하게 된다. 즉, 옵션들이 ApolloServer의 생성자로 들어가게 된다는 뜻이다. 예를 들어서 playground 기능과 debug모드를 끄고 싶으면 아래와 같이 옵션 값을 넘겨주면 된다.

1
2
3
4
5
6
7
8
9
10
11
import {Module} from "@nestjs/common";
import {GraphQLModule} from "@nestjs/graphql";

@Module({
imports: [
GraphQLModule.forRoot({
debug:false,
playground: false
})
]
})

자세한 설정 값은 이 링크에서 확인해보자.

복수의 엔드포인트들

@nestjs/graphql 모듈의 유용한 기능 중 하나는 복수의 엔드포인트를 제공해줄 수 있다는 점이다. 이 기능으로 어던 모듈이 어떤 엔드포인트에 속해있어야 하는지를 결정할 수 있다. 기본적으로 GraphQL은 Resolver를 전체 앱을 대상으로 해서 찾게 된다. 이러한 스캔을 모듈 단위로 제한하려면 include 프로퍼티를 사용해야 한다.

1
2
3
GraphQLModule.forRoot({
include: [CatsModule]
});

Code first

Code First 방법으로는 데코레이터와 타입스크립트의 클래스로 GraphQL 스키마와 상응하는 것을 만들 수 있다고 했는데, 이러한 방법을 사용하려면, autoSchemaFile을 옵션에 달아줘야 한다.

1
2
3
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), "src/schema.gql")
});

autoSchemaFile 프로퍼티 값은 자동적으로 생성될 스키마가 생성될 위치를 넣어준다. 스키마가 on-the-fly(값이 변경될 때마다 변경되는) 메모리 값에 넣어줄 수도 있는데 아래와 같이 autoSchemaFile 옵션 값으로 true를 넣어줘야 한다.

1
2
3
GraphQLModule.forRoot({
autoSchemaFile: true
});

이 링크에서 동작하는 코드 예시를 확인해볼 수 있다.

Schema first

Schema First 방법으로는, 옵션 객체에 typePaths 프로퍼티를 넣어줘야 한다. 이 옵션은 GraphQLModule이 GraphQL SDL 스키마 정의 파일을 어디서 찾아야 하는지를 알려주는 것이다. 이 파일들은 메모리에 묶이게 된다. 이건 스키마들을 몇 개의 파일들로 나누고, resolver 근처에 위치시킬 수 있게 해준다.

1
2
3
GraphQLModule.forRoot({
typePaths: ["./**/*.graphql"]
});

아마도 해당 SDL 타입과 일치하는 타입스크립트 정의들이 필요할 수 있다. 이러한 타입 정의를 만들기 위해 직접 코드를 쓰는 작업은 귀찮고 지루하다. 또한 수작업으로 하게 되면 source of truth 원칙을 어기게 되고 수정시, 두 번의 수정 작업을 거쳐야 한다. 이 부분을 해결하기 위해서 @nestjs/graphql 패키지는 자동적으로 AST(Abstract Syntax Tree)로부터 타입 정의를 생성해준다. 이 기능을 사용하려면 definitions 옵션을 추가해줘야 한다.

1
2
3
4
5
6
GraphQLModule.forRoot({
typePaths: ["./**/*.graphql"],
definitions: {
path: join(process.cwd(), "src/graphql.ts")
}
});

definitions안에 있는 path는 Typescript Output이 어디에 저장될지를 정하는 역할을 해준다. 기본 값으로 모든 생성된 타입스크립트 타입들은 interface로 만들어진다. class로 만들고 싶으면 outputAs 프로퍼티에 값을 class로 두면 된다.

1
2
3
4
5
6
7
GraphQLModule.forRoot({
typePaths: ["./**/*.graphql"],
definitions: {
path: join(process.cwd(), "src/graphql.ts"),
outputAs: "class"
}
});

위 방법은 타입 정의를 앱이 시작할 때마다 동적으로 생성하게 된다. 이 방식 말고 특정 스크립트로 타입이 정의되도록 만들 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
// generate-typings.ts

import { GraphQLDefinitionsFactory } from "@nestjs/graphql";
import { join } from "path";

const definitionsFactory = new GraphQLDefinitionsFactory();
definitionsFactory.generate({
typePaths: ["./src/**/*.graphql"],
path: join(process.cwd(), "src/graphql.ts"),
outputAs: "class"
});

위와 같이 설정하면 아래 명령어로 타입을 생성할 수 있다.

1
ts-node generate-typings

당연하게도 컴파일 한 다음 node로 동작하게 할 수도 있다.

와치모드를 사용할 수도 있다. 간단하게 generate() 메서드에 watch 옵션을 true로 해서 넣어주면 된다.

1
2
3
4
5
6
definitionsFactory.generate({
typePaths: ["./src/**/*.graphql"],
path: join(process.cwd(), "src/graphql.ts"),
outputAs: "class",
watch: true
});

위 방식대로 동작하는 예시를 이 링크에서 확인할 수 있다.

비동기 설정값

모듈 옵션을 비동기적으로 해야 하는 경우, forRootAsync() 메서드를 사용할 수 있다. 대부분의 동적 모듈을에서 Nest는 몇 가지 비동기를 처리하는 기술들을 제공해주고 있다. 그 중 하나는 factory 함수를 사용하는 것이다.

1
2
3
4
5
GraphQLModule.forRootAsync({
useFactory: () => ({
typePaths: ["./**/*.graphql"]
})
});

다른 factory provider처럼 Nest의 factory 함수 역시 비동기를 제공하고, 의존성 주입을 inject옵션을 통해서 사용할 수 있다.

1
2
3
4
5
6
7
GraphQLModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
typePaths: configService.getString("GRAPHQL_TYPE_PATHS")
}),
inject: [ConfigService]
});

factory 함수 하나 대신 클래스를 사용할 수도 있다.

1
2
3
GraphQLModule.forRootAsync({
useClass: GqlConfigService
});

위 예시에서 GqlConfigService는 옵션 객체를 만들기 위해서 GraphQLModule 내부에서 인스턴스화 된다. GqlConfigServiceGqlOptionsFactory 인터페이스를 implements 하고 있어야 한다. GraphQLModulecreateGqlOptions 메서드를 호출하게 될 것이다.

1
2
3
4
5
6
7
8
@Injectable()
class GqlConfigService implements GqlOptionsFactory {
createGqlOptions(): GqlModuleOptions {
return {
typePaths: ["./**/*.graphql"]
};
}
}

후기

NestJS 7.0 버전에서는 @nestjs/graphql 패키지가 많은 변화가 있다고 하긴 했는데, 설정부터 많은 옵션이 있는 것 같다. GraphQL 스키마 정의를 하는 방법도 선택할 수 있고, 타입을 자동으로 만들어주는 기능 등은 좋은 것 같다.

Reference

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×