NestJS에서 GraphQL 설정
GraphQL은 API를 위한 쿼리 언어로, 클라이언트가 필요한 데이터를 정확히 요청할 수 있게 해주는 강력한 도구입니다.
NestJS와 GraphQL을 함께 사용하면 타입 안전성과 효율적인 데이터 fetching을 결합한 강력한 API를 구축할 수 있습니다.
GraphQL vs RESTful API
1. GraphQL
- 단일 엔드포인트
- 클라이언트가 필요한 데이터만 요청 가능
- 오버페칭/언더페칭 문제 해결
- 강력한 타입 시스템
2. RESTful API
- 다중 엔드포인트
- 고정된 데이터 구조
- 버저닝이 필요할 수 있음
- 간단한 구현과 캐싱
NestJS에서 GraphQL을 사용하는 이점
- TypeScript와의 완벽한 통합
- 코드 생성 및 타입 추론
- 모듈화된 구조와 의존성 주입 활용
- 다양한 GraphQL 기능 지원 (구독, 지시어 등)
NestJS에 GraphQL 설정하기
- 의존성 설치
npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express
- 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 시스템 통합
- 자동 타입 생성
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'schema.gql',
definitions: {
path: join(process.cwd(), 'src/graphql.ts'),
},
}),
- 생성된 타입 사용
import { User } from './graphql';
@Resolver('User')
export class UserResolver {
@Query(returns => [User])
async getUsers(): Promise<User[]> {
// 구현
}
}
보안 고려사항
- 인증 및 권한 부여
@Resolver(of => User)
export class UserResolver {
@Query(returns => User)
@UseGuards(GqlAuthGuard)
async me(@CurrentUser() user: User) {
return user;
}
}
- 입력 유효성 검사
@InputType()
class CreateUserInput {
@Field()
@IsEmail()
email: string;
@Field()
@MinLength(8)
password: string;
}
- 쿼리 복잡도 제한
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'schema.gql',
validationRules: [
depthLimitRule(5),
complextiyLimitRule(1000),
],
}),
성능 최적화 전략
- 데이터 로더 사용
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));
});
}
- 쿼리 캐싱
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'schema.gql',
cache: 'bounded',
}),
- 응답 압축
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의 성능과 안정성을 유지하고 개선해 나가는 것이 중요합니다.