icon

NestJS에서 GraphQL 설정


 GraphQL은 API를 위한 쿼리 언어로, 클라이언트가 필요한 데이터를 정확히 요청할 수 있게 해주는 강력한 도구입니다.

 NestJS와 GraphQL을 함께 사용하면 타입 안전성과 효율적인 데이터 fetching을 결합한 강력한 API를 구축할 수 있습니다.

GraphQL vs RESTful API

 1. GraphQL

  • 단일 엔드포인트
  • 클라이언트가 필요한 데이터만 요청 가능
  • 오버페칭/언더페칭 문제 해결
  • 강력한 타입 시스템

 2. RESTful API

  • 다중 엔드포인트
  • 고정된 데이터 구조
  • 버저닝이 필요할 수 있음
  • 간단한 구현과 캐싱

 NestJS에서 GraphQL을 사용하는 이점

  • TypeScript와의 완벽한 통합
  • 코드 생성 및 타입 추론
  • 모듈화된 구조와 의존성 주입 활용
  • 다양한 GraphQL 기능 지원 (구독, 지시어 등)

NestJS에 GraphQL 설정하기

  1. 의존성 설치
npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express
  1. GraphQLModule 설정 (app.module.ts)
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
 
@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: 'schema.gql',
    }),
  ],
})
export class AppModule {}

코드 우선 vs 스키마 우선 접근 방식

 1. 코드 우선 (Code First)

  • TypeScript 클래스로 스키마 정의
  • 자동 스키마 생성
  • 예시
import { Field, ObjectType, ID } from '@nestjs/graphql';
 
@ObjectType()
export class User {
  @Field(type => ID)
  id: string;
 
  @Field()
  name: string;
 
  @Field()
  email: string;
}

 2. 스키마 우선 (Schema First)

  • SDL (Schema Definition Language) 사용
  • 수동 스키마 정의
  • 예시
type User {
  id: ID!
  name: String!
  email: String!
}
import { Resolver, Query } from '@nestjs/graphql';
 
@Resolver('User')
export class UserResolver {
  @Query()
  async getUsers() {
    // 구현
  }
}

GraphQL 플레이그라운드 통합

 GraphQLModule 설정에 playground 옵션 추가

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: 'schema.gql',
  playground: true,
}),

NestJS 모듈 시스템을 활용한 구조화

// users/users.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersResolver, UsersService],
})
export class UsersModule {}
 
// app.module.ts
@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: 'schema.gql',
    }),
    UsersModule,
  ],
})
export class AppModule {}

GraphQL 스키마 생성 및 커스터마이징

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: 'schema.gql',
  sortSchema: true,
  buildSchemaOptions: {
    dateScalarMode: 'timestamp',
    numberScalarMode: 'float',
  },
}),

GraphQL과 TypeScript 시스템 통합

  1. 자동 타입 생성
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: 'schema.gql',
  definitions: {
    path: join(process.cwd(), 'src/graphql.ts'),
  },
}),
  1. 생성된 타입 사용
import { User } from './graphql';
 
@Resolver('User')
export class UserResolver {
  @Query(returns => [User])
  async getUsers(): Promise<User[]> {
    // 구현
  }
}

보안 고려사항

  1. 인증 및 권한 부여
@Resolver(of => User)
export class UserResolver {
  @Query(returns => User)
  @UseGuards(GqlAuthGuard)
  async me(@CurrentUser() user: User) {
    return user;
  }
}
  1. 입력 유효성 검사
@InputType()
class CreateUserInput {
  @Field()
  @IsEmail()
  email: string;
 
  @Field()
  @MinLength(8)
  password: string;
}
  1. 쿼리 복잡도 제한
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: 'schema.gql',
  validationRules: [
    depthLimitRule(5),
    complextiyLimitRule(1000),
  ],
}),

성능 최적화 전략

  1. 데이터 로더 사용
import DataLoader from 'dataloader';
 
@Injectable()
export class UserDataLoader {
  constructor(private readonly userService: UserService) {}
 
  public readonly batchUsers = new DataLoader<number, User>(async (userIds) => {
    const users = await this.userService.findByIds(userIds);
    const userMap = new Map(users.map(user => [user.id, user]));
    return userIds.map(id => userMap.get(id));
  });
}
  1. 쿼리 캐싱
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: 'schema.gql',
  cache: 'bounded',
}),
  1. 응답 압축
import compression from 'compression';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(compression());
  await app.listen(3000);
}

 GraphQL의 쿼리 언어와 타입 시스템은 NestJS의 모듈화된 아키텍처 및 TypeScript와 결합되어 타입 안전성이 높고 효율적인 API를 구축할 수 있습니다.

 GraphQL 플레이그라운드를 통합하면 API를 쉽게 탐색하고 테스트할 수 있어 개발 과정을 크게 간소화할 수 있습니다. NestJS의 모듈 시스템을 활용하여 GraphQL 애플리케이션을 구조화하면 코드의 재사용성과 유지보수성을 높일 수 있습니다.

 GraphQL 스키마 생성 옵션을 커스터마이징하면 특정 요구사항에 맞게 스키마를 최적화할 수 있습니다. 예를 들어, 날짜나 숫자 타입의 처리 방식을 지정하거나 스키마 정렬을 활성화할 수 있습니다.

 GraphQL과 TypeScript의 타입 시스템을 통합하면 end-to-end 타입 안전성을 확보할 수 있습니다. 자동 타입 생성 기능을 활용하면 GraphQL 스키마와 TypeScript 타입을 동기화하여 타입 불일치로 인한 오류를 방지할 수 있습니다.

 보안 측면에서는 인증 및 권한 부여, 입력 유효성 검사, 쿼리 복잡도 제한 등을 구현하여 API를 보호해야 합니다. NestJS의 가드와 파이프를 활용하면 이러한 보안 기능을 효과적으로 구현할 수 있습니다.

 성능 최적화를 위해서는 데이터 로더를 사용하여 N+1 쿼리 문제를 해결하고, 쿼리 캐싱을 통해 반복적인 쿼리의 성능을 향상시킬 수 있습니다. 또한 응답 압축을 적용하여 네트워크 전송을 최적화할 수 있습니다.

 결론적으로 NestJS와 GraphQL의 조합은 현대적이고 효율적인 API 개발을 위한 강력한 도구입니다. 타입 안전성, 유연성, 성능을 모두 고려한 설계와 구현을 통해 높은 품질의 GraphQL API를 구축할 수 있습니다. 지속적인 모니터링과 최적화를 통해 API의 성능과 안정성을 유지하고 개선해 나가는 것이 중요합니다.