Quick start

Quick start

GraphQL은 API에 λŒ€ν•œ 쿼리 언어이며 κΈ°μ‘΄ λ°μ΄ν„°λ‘œ 쿼리λ₯Ό μˆ˜ν–‰ν•˜κΈ°μœ„ν•œ λŸ°νƒ€μž„μž…λ‹ˆλ‹€. 일반적인 REST APIμ—μ„œ λ°œμƒν•˜λŠ” μ΄λŸ¬ν•œ λ§Žμ€ 문제λ₯Ό ν•΄κ²°ν•˜λŠ” μš°μ•„ν•œ μ ‘κ·Ό λ°©μ‹μž…λ‹ˆλ‹€. GraphQLκ³Ό REST μ‚¬μ΄μ—λŠ” ν›Œλ₯­ν•œ 비ꡐ가 μžˆμŠ΅λ‹ˆλ‹€. 이 κΈ°μ‚¬μ—μ„œλŠ” GraphQL이 무엇인지 μ„€λͺ…ν•˜μ§€ μ•Šκ³  μ „μš© @nestjs/graphql λͺ¨λ“ˆλ‘œ μž‘μ—…ν•˜λŠ” 방법을 λ³΄μ—¬μ€λ‹ˆλ‹€. 이 μ±•ν„°μ—μ„œλŠ” 이미 GraphQL κΈ°λ³Έ 사항에 μ΅μˆ™ν•˜λ‹€κ³  κ°€μ •ν•©λ‹ˆλ‹€.

GraphQLModule은 [Apollo] (https://www.apollographql.com/) μ„œλ²„μ˜ λž˜νΌμ— μ§€λ‚˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μš°λ¦¬λŠ” 바퀴λ₯Ό 재발 λͺ…ν•˜μ§€ μ•Šκ³  λŒ€μ‹  μ‚¬μš©ν•  μ€€λΉ„κ°€ 된 λͺ¨λ“ˆμ„ μ œκ³΅ν•˜μ—¬ GraphQLκ³Ό Nestλ₯Ό ν•¨κ»˜ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Installation

λ¨Όμ € ν•„μš”ν•œ νŒ¨ν‚€μ§€λ₯Ό μ„€μΉ˜ν•΄μ•Όν•©λ‹ˆλ‹€.

$ npm i --save @nestjs/graphql apollo-server-express graphql-tools graphql

Overview

NestλŠ” GraphQL μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ„ λΉŒλ“œν•˜λŠ” 두 κ°€μ§€ 방법, 즉 μŠ€ν‚€λ§ˆ μš°μ„ κ³Ό μ½”λ“œ μš°μ„ μ„ μ œκ³΅ν•©λ‹ˆλ‹€.

μŠ€ν‚€λ§ˆ μš°μ„  μ ‘κ·Όλ²•μ—μ„œ μ§„μ‹€μ˜ 근원은 GraphQL SDL (μŠ€ν‚€λ§ˆ μ •μ˜ μ–Έμ–΄)μž…λ‹ˆλ‹€. 기본적으둜 μ„œλ‘œ λ‹€λ₯Έ ν”Œλž«νΌκ°„μ— μŠ€ν‚€λ§ˆ νŒŒμΌμ„ 곡유 ν•  수 μžˆλŠ” 언어에 ꡬ애받지 μ•ŠλŠ” λ°©μ‹μž…λ‹ˆλ‹€. λ˜ν•œ NestλŠ” GraphQL μŠ€ν‚€λ§ˆ (클래슀 λ˜λŠ” μΈν„°νŽ˜μ΄μŠ€ μ‚¬μš©)λ₯Ό 기반으둜 TypeScript μ •μ˜λ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•˜μ—¬ 쀑볡성을 μ€„μž…λ‹ˆλ‹€.

λ°˜λ©΄μ— μ½”λ“œ μš°μ„  λ°©μ‹μ—μ„œλŠ” λ°μ½”λ ˆμ΄ν„°μ™€ TypeScript 클래슀 만 μ‚¬μš©ν•˜μ—¬ ν•΄λ‹Ή GraphQL μŠ€ν‚€λ§ˆλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. TypeScript둜 λ…μ μ μœΌλ‘œ μž‘μ—…ν•˜κ³  μ–Έμ–΄ ꡬ문 κ°„μ˜ μ»¨ν…μŠ€νŠΈ μ „ν™˜μ„ ν”Όν•˜λŠ” 것이 맀우 νŽΈλ¦¬ν•©λ‹ˆλ‹€.

Getting started

νŒ¨ν‚€μ§€κ°€ μ„€μΉ˜λ˜λ©΄ GraphQLModule을 등둝 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@@filename()
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';

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

.forRoot()λ©”μ†Œλ“œλŠ” μ˜΅μ…˜ 객체λ₯Ό 인자둜 λ°›μŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ μ˜΅μ…˜μ€ κΈ°λ³Έ Apollo μΈμŠ€ν„΄μŠ€λ‘œ μ „λ‹¬λ©λ‹ˆλ‹€ (μ‚¬μš© κ°€λŠ₯ν•œ 섀정에 λŒ€ν•œ μžμ„Έν•œ λ‚΄μš©μ€ μ—¬κΈ°. 예λ₯Ό λ“€μ–΄, 놀이터λ₯Ό λΉ„ν™œμ„±ν™”ν•˜κ³  디버그 λͺ¨λ“œλ₯Ό 끄렀면 λ‹€μŒ μ˜΅μ…˜μ„ μ „λ‹¬ν•˜μ‹­μ‹œμ˜€.

@@filename()
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';

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

μ–ΈκΈ‰ ν•œ 바와 같이 이 λͺ¨λ“  섀정은ApolloServer μƒμ„±μžλ‘œ μ „λ‹¬λ©λ‹ˆλ‹€.

Playground

λ†€μ΄ν„°λŠ” κ·Έλž˜ν”½ λŒ€ν™” ν˜• λΈŒλΌμš°μ € λ‚΄ GraphQL IDE이며 기본적으둜 GraphQL μ„œλ²„ μžμ²΄μ™€ λ™μΌν•œ URLμ—μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ‹€ν–‰λ˜λŠ” λ™μ•ˆ μ›Ή λΈŒλΌμš°μ €λ₯Ό μ—΄κ³  http://localhost:3000/graphql둜 μ΄λ™ν•˜μ‹­μ‹œμ˜€ (호슀트 및 ν¬νŠΈλŠ” ꡬ성에 따라 λ‹€λ₯Ό 수 있음).

Multiple endpoints

이 λͺ¨λ“ˆμ˜ 또 λ‹€λ₯Έ μœ μš©ν•œ κΈ°λŠ₯은 μ—¬λŸ¬ μ—”λ“œ 포인트λ₯Ό ν•œ λ²ˆμ— μ œκ³΅ν•˜λŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€. 덕뢄에 μ–΄λ–€ μ—”λ“œ ν¬μΈνŠΈμ— μ–΄λ–€ λͺ¨λ“ˆμ„ 포함할지 κ²°μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 기본적으둜 GraphQL은 전체 μ•±μ—μ„œ 리쑸버λ₯Ό κ²€μƒ‰ν•©λ‹ˆλ‹€. λͺ¨λ“ˆμ˜ μ„œλΈŒμ…‹λ§Œ μ œν•œν•˜κΈ° μœ„ν•΄ include 속성을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

GraphQLModule.forRoot({
  include: [CatsModule],
}),

Schema first

μŠ€ν‚€λ§ˆλ₯Ό λ¨Όμ € μ‚¬μš©ν•˜λ €λ©΄ options 객체 μ•ˆμ—typePaths 배열을 μΆ”κ°€ν•˜λ©΄λ©λ‹ˆλ‹€.

GraphQLModule.forRoot({
  typePaths: ['./**/*.graphql'],
}),

typePaths 속성은 GraphQLModule이 GraphQL νŒŒμΌμ„ μ°Ύμ•„μ•Όν•˜λŠ” μœ„μΉ˜λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€. μ΄λŸ¬ν•œ λͺ¨λ“  νŒŒμΌμ€ κ²°κ΅­ λ©”λͺ¨λ¦¬μ— κ²°ν•©λ˜λ―€λ‘œ μŠ€ν‚€λ§ˆλ₯Ό μ—¬λŸ¬ 파일둜 λΆ„ν• ν•˜μ—¬ 리쑸버 κ·Όμ²˜μ— μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

GraphQL μœ ν˜•κ³Ό ν•΄λ‹Ή TypeScript μ •μ˜λ₯Ό λ³„λ„λ‘œ μž‘μ„±ν•˜λ©΄ λΆˆν•„μš”ν•œ 쀑볡이 μƒμ„±λ©λ‹ˆλ‹€. κ²°κ΅­, μš°λ¦¬λŠ” λ‹¨μΌν•œ μ§„μ‹€μ˜ μ›μ²œμ—†μ΄ κ²°κ΅­ SDL λ‚΄μ—μ„œ 이루어진 각 λ³€κ²½μœΌλ‘œ 인해 μΈν„°νŽ˜μ΄μŠ€λ„ μ‘°μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ”°λΌμ„œ@nestjs/graphql νŒ¨ν‚€μ§€λŠ” 또 λ‹€λ₯Έ ν₯미둜운 κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ”λ°, μ΄λŠ” 좔상 ꡬ문 트리 (AST)λ₯Ό μ‚¬μš©ν•œ TS μ •μ˜μ˜ μžλ™ μƒμ„±μž…λ‹ˆλ‹€. 이λ₯Ό κ°€λŠ₯ν•˜κ²Œν•˜λ €λ©΄ definitions 속성을 μΆ”κ°€ν•˜λ©΄ λ©λ‹ˆλ‹€.

GraphQLModule.forRoot({
  typePaths: ['./**/*.graphql'],
  definitions: {
    path: join(process.cwd(), 'src/graphql.ts'),
  },
}),

src/graphql.tsλŠ” TypeScript 좜λ ₯을 μ €μž₯ν•  μœ„μΉ˜λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€. 기본적으둜 λͺ¨λ“  μœ ν˜•μ΄ μΈν„°νŽ˜μ΄μŠ€λ‘œ λ³€ν™˜λ©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ outputAs 속성을 class둜 λ³€κ²½ν•˜μ—¬ λŒ€μ‹  클래슀둜 μ „ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

GraphQLModule.forRoot({
  typePaths: ['./**/*.graphql'],
  definitions: {
    path: join(process.cwd(), 'src/graphql.ts'),
    outputAs: 'class',
  },
}),

κ·ΈλŸ¬λ‚˜ 각 μ‘μš© ν”„λ‘œκ·Έλž¨ μ‹œμž‘μ‹œ μœ ν˜• μ •μ˜λ₯Ό μƒμ„±ν•˜μ§€ μ•Šμ•„λ„λ©λ‹ˆλ‹€. λŒ€μ‹ , μ „μš© λͺ…령이 싀행될 λ•Œλ§Œ μ™„μ „νžˆ μ œμ–΄ν•˜κ³  μž…λ ₯을 μƒμ„±ν•˜λŠ” 것을 μ„ ν˜Έ ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 경우 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',
});

그런 λ‹€μŒ κ°„λ‹¨νžˆ νŒŒμΌμ„ μ‹€ν–‰ν•˜μ‹­μ‹œμ˜€.

ts-node generate-typings

info 힌트 미리 슀크립트λ₯Ό μ»΄νŒŒμΌν•˜κ³  λŒ€μ‹  node μ‹€ν–‰ νŒŒμΌμ„ μ‚¬μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

κ°μ‹œ λͺ¨λ“œλ‘œ μ „ν™˜ν•˜λ €λ©΄ (.graphql 파일 λ³€κ²½μ‹œ μžλ™μœΌλ‘œ μž…λ ₯을 생성) watch μ˜΅μ…˜μ„ generate()λ©”μ†Œλ“œμ— μ „λ‹¬ν•˜μ‹­μ‹œμ˜€.

definitionsFactory.generate({
  typePaths: ['./src/**/*.graphql'],
  path: join(process.cwd(), 'src/graphql.ts'),
  outputAs: 'class',
  watch: true,
});

μ™„μ „νžˆ μž‘λ™ν•˜λŠ” μƒ˜ν”Œμ€ μ—¬κΈ°μ—μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Code first

μ½”λ“œ μš°μ„  λ°©λ²•μ—μ„œλŠ” λ°μ½”λ ˆμ΄ν„°μ™€ TypeScript 클래슀 만 μ‚¬μš©ν•˜μ—¬ ν•΄λ‹Ή GraphQL μŠ€ν‚€λ§ˆλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

NestλŠ”μ΄ κΈ°λŠ₯을 μ œκ³΅ν•˜κΈ° μœ„ν•΄ ν›„λ“œ μ•„λž˜μ—μ„œ λ†€λΌμš΄ type-graphql 라이브러리λ₯Ό μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ 계속 μ§„ν–‰ν•˜κΈ° 전에 이 νŒ¨ν‚€μ§€λ₯Ό μ„€μΉ˜ν•΄μ•Όν•©λ‹ˆλ‹€.

$ npm i type-graphql

μ„€μΉ˜ 과정이 μ™„λ£Œλ˜λ©΄, autoSchemaFile 속성을 μ˜΅μ…˜ 객체에 μΆ”κ°€ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

GraphQLModule.forRoot({
  autoSchemaFile: 'schema.gql',
}),

autoSchemaFile은 μžλ™μœΌλ‘œ μƒμ„±λœ μŠ€ν‚€λ§ˆκ°€ 생성될 경둜λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€. λ˜ν•œ buildSchemaOptions 속성-type-graphql νŒ¨ν‚€μ§€μ—μ„œ buildSchema()ν•¨μˆ˜λ‘œ μ „λ‹¬λ˜λŠ” μ˜΅μ…˜ 객체λ₯Ό 전달할 수 μžˆμŠ΅λ‹ˆλ‹€.

μ™„μ „νžˆ μž‘λ™ν•˜λŠ” μƒ˜ν”Œμ€ μ—¬κΈ°μ—μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Async configuration

λͺ¨λ“ˆ μ˜΅μ…˜μ„ 미리 μ „λ‹¬ν•˜λŠ” λŒ€μ‹  λΉ„λ™κΈ°μ‹μœΌλ‘œ μ „λ‹¬ν•˜λ €λŠ” κ²½μš°κ°€ μ’…μ’… μžˆμŠ΅λ‹ˆλ‹€. 이 경우 비동기 데이터λ₯Ό μ²˜λ¦¬ν•˜λŠ” λͺ‡ κ°€μ§€ λ‹€μ–‘ν•œ 방법을 μ œκ³΅ν•˜λŠ” forRootAsync()λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ‹­μ‹œμ˜€.

κ°€λŠ₯ν•œ 첫 번째 방법은 νŒ©ν† λ¦¬ κΈ°λŠ₯을 μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

GraphQLModule.forRootAsync({
  useFactory: () => ({
    typePaths: ['./**/*.graphql'],
  }),
}),

λΆ„λͺ…νžˆ, 우리 νŒ©ν† λ¦¬λŠ” λ‹€λ₯Έ λͺ¨λ“  κ²ƒμ²˜λŸΌ ν–‰λ™ν•©λ‹ˆλ‹€ ( async일 μˆ˜λ„ 있고 inject을 톡해 μ˜μ‘΄μ„±μ„ μ£Όμž…ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€).

GraphQLModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    typePaths: configService.getString('GRAPHQL_TYPE_PATHS'),
  }),
  inject: [ConfigService],
}),

λ˜λŠ” νŒ©ν† λ¦¬ λŒ€μ‹  클래슀λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

GraphQLModule.forRootAsync({
  useClass: GqlConfigService,
}),

μœ„μ˜ ꡬ성은 GraphQLModule λ‚΄μ—μ„œ GqlConfigServiceλ₯Ό μΈμŠ€ν„΄μŠ€ν™” ν•˜κ³  이λ₯Ό ν™œμš©ν•˜μ—¬ μ˜΅μ…˜ 객체λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. GqlConfigServiceλŠ” GqlOptionsFactory μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•΄μ•Όν•©λ‹ˆλ‹€.

@Injectable()
class GqlConfigService implements GqlOptionsFactory {
  createGqlOptions(): GqlModuleOptions {
    return {
      typePaths: ['./**/*.graphql'],
    };
  }
}

GraphQLModule 내에 GqlConfigServiceκ°€ μž‘μ„±λ˜λŠ” 것을 막고 λ‹€λ₯Έ λͺ¨λ“ˆμ—μ„œ κ°€μ Έμ˜¨ 제곡자λ₯Ό μ‚¬μš©ν•˜λ €λ©΄useExisting ꡬ문을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

GraphQLModule.forRootAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
}),

ν•˜λ‚˜μ˜ μ€‘μš”ν•œ 차이점으둜 useClass와 λ™μΌν•˜κ²Œ μž‘λ™ν•©λ‹ˆλ‹€. GraphQLModule은 κ°€μ Έμ˜¨ λͺ¨λ“ˆμ„ μ‘°νšŒν•˜μ—¬ 이미 μƒμ„±λœ ConfigServiceλ₯Ό 자체적으둜 μΈμŠ€ν„΄μŠ€ν™” ν•˜λŠ” λŒ€μ‹  μž¬μ‚¬μš©ν•©λ‹ˆλ‹€.

Last updated

Was this helpful?