GraphQL과 NestJS는 훌륭한 파트너십을 통해 확장 가능한 웹 애플리케이션을 구축할 수 있는 API의 견고한 기반과 사용하기 쉬운 프레임워크를 제공합니다. 이 조합은 프로덕션에 바로 사용할 수 있는 앱을 구축하는 데 적합하며, 두 가지 모두 오늘날의 기술 생태계에서 매우 적절한 도구입니다.

두 제품을 사용하여 API를 구축하는 방법에 대해 자세히 알아보세요.

GraphQL이란 무엇인가요?

GraphQL은 보다 정확하고 간결한 방식으로 API를 구축하는 데 사용할 수 있는 데이터 쿼리 및 조작 언어입니다. GraphQL은 API에 존재하는 데이터에 대한 완전하고 적절한 설명을 제공하며, 클라이언트가 필요한 데이터를 정확하게 얻을 수 있도록 지원합니다.

GraphQL은 정확한 데이터 쿼리부터 graphiql 편집기와 같은 더 나은 개발자 도구에 이르기까지 REST API에 없는 많은 기능을 제공합니다. 또한 단일 요청을 통해 여러 리소스를 쿼리할 수 있습니다.

NestJS란 무엇인가요?

NestJS는 확장 가능하고 효율적인 서버 측 애플리케이션을 구축하는 데 사용할 수 있는 진보적인 Node.js 프레임워크입니다. NestJS는 GraphQL 지원, GRPC, 웹소켓 등 빠르고 쉬운 개발을 위한 도구와 함께 다양한 플러그인을 제공합니다.

NestJS는 모듈, 컨트롤러, 서비스 및 스키마를 사용하여 최적화된 프로젝트 구조로 에코시스템에서 잘 알려져 있습니다. 내장된 CLI를 사용하면 구조화된 API 아키텍처를 만들 수 있습니다. 의존성 주입 원칙을 사용하여 애플리케이션의 각 부분이 서로 통신하는 방식을 제어할 수 있습니다.

NestJS 및 MongoDB로 GraphQL 구현하기

NestJS 및 GraphQL로 API를 빌드하기 전에 올바른 종속성을 사용할 수 있어야 합니다. Node.js와 NestJS를 설치해야 하며, npm i -g @nestjs/cli를 실행하여 설치할 수 있습니다.

다음 예제는 책에 대한 정보를 저장하는 간단한 앱입니다. 터미널에서 다음 명령을 실행하여 새 NestJS 애플리케이션을 생성합니다:

 nest new <app-name>

생성된 애플리케이션의 디렉토리(앱 이름)로 이동하여 다음 명령을 사용하여 해당 종속성을 설치합니다:

 $ npm install --save @nestjs/config @nestjs/graphql graphql-tools graphql \
 @nestjs/apollo apollo-server-express @nestjs/mongoose @types/graphql

GraphQL API를 빌드하는 데는 크게 두 가지 접근 방식이 있습니다:

⭐ 스키마 우선 접근 방식: 스키마 정의 파일 또는 SDL에 API를 설명하면 NestJS가 이를 기반으로 타입스크립트 정의를 생성하는 방식입니다.

⭐ 코드 우선 접근 방식: 타입스크립트 클래스와 데코레이터를 사용하여 쿼리, 변이 및 기타 GraphQL 기능을 정의하면 NestJS가 이를 기반으로 SDL 파일을 생성하는 방식입니다.

이 글도 확인해 보세요:  내부에서 REST API 호출을 수행하는 방법 VS 코드

다음 예제에서는 코드 우선 접근 방식을 사용하는 방법을 설명합니다.

먼저 앱 모듈에서 GraphQL을 초기화하여 MongoDB 데이터베이스에 연결해야 합니다:

 // app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule as NestGraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import mongodbConfig from './config/mongodb.config';

@Module({
  imports: [
     ConfigModule.forRoot({
       load: [mongodbConfig],
       isGlobal: true
    }),
    NestGraphQLModule.forRootAsync<ApolloDriverConfig>({
      driver: ApolloDriver,
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
        installSubscriptionHandlers: true,
        sortSchema: true,
        playground: true,
        debug: configService.get<boolean>("DEBUG"),
        uploads: false,
      }),
    }),
    MongooseModule.forRootAsync({
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        uri: configService.get('MONGO_URI')
      })
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule {}

이 모듈은 @nestjs/graphql에서 GraphQLModule을 가져오고, MongoDB에 연결하는 데 도움이 되는 @nestjs/mongoose에서 MongooseModule을 가져옵니다. autoSchemaFile 속성은 생성된 스키마 파일의 위치를 지정하고, sortSchema 속성은 필드를 알파벳순으로 정렬하도록 합니다.

MongoDB 구성 파일의 모습은 다음과 같습니다:

 import { registerAs } from '@nestjs/config';

/**
 * Mongo database connection config
 */
export default registerAs('mongodb', () => {
  const {
    MONGO_URI
  } = process.env;

  return {
    uri: `${MONGO_URI}`,
  };
});

GraphQL 스키마 정의하기

GraphQL 및 MongoDB 연결을 설정했으면, 스키마(schema.gql) 파일을 생성하기 위해 GraphQL 쿼리 및 변형을 정의해야 합니다.

쿼리 작성

코드 우선 접근 방식에서는 ObjectType 데코레이터를 사용하여 모델을 만듭니다. 나중에 이 모델을 GraphQL 유형으로 변환합니다.

예:

 // book.model.ts
import { Field, ObjectType } from '@nestjs/graphql';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type BookDocument = Book & Document;

@ObjectType()
@Schema()
export class Book {
    @Field()
    title: string;

    @Field()
    author: string;

    @Field()
    publishedDate: boolean;
}

export const BookSchema = SchemaFactory.createForClass(Book);

GraphQL은 기본적으로 생성된 스키마를 사용할 수 없습니다. 이를 사용하려면 GraphQL 유형을 실행하기 위한 함수가 포함된 리졸버 서비스가 필요합니다. 리졸버 데코레이터를 사용하면 됩니다.

 // books.resolver.ts
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { Book } from './book.model';
import { BookService } from './books.service';

@Resolver(() => Book)
export class BookResolver {
    constructor(private readonly bookService: BookService) { }

    @Query(() => [Book])
    async books(): Promise<Book[]> {
        return this.bookService.findAll();
    }

    @Query(() => Book)
    async book(@Args('id', { type: () => ID }) id: string): Promise<Book> {
        return this.bookService.findOne(id);
    }
}

위에서 가져온 BookService를 다음과 같이 구현할 수 있습니다:

 // books.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Book, BookDocument } from './book.model';

@Injectable()
export class BookService {
    constructor(@InjectModel(Book.name) private bookModel: Model<BookDocument>) { }

    async findAll(): Promise<Book[]> {
        return this.bookModel.find().exec();
    }

    async findOne(id: string): Promise<Book> {
        return this.bookModel.findById(id).exec();
    }
}

또한 books.module.ts의 공급자 목록에 BookResolver를 추가해야 합니다.

 import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import { BookService } from './books.service';
import { BookResolver } from './books.resolver';
import { Book, BookSchema } from './book.model';

@Module({
    providers: [
        BookService,
        BookResolver
    ],
    imports: [MongooseModule.forFeature([
        {
            name: Book.name,
            schema: BookSchema,
        },
    ]),
  ],
})

export class BooksModule {}

돌연변이 작업

쿼리를 사용하여 GraphQL에서 데이터를 검색하는 동안 돌연변이는 데이터베이스에서 데이터를 생성하거나 업데이트합니다. 돌연변이를 만들려면 사용자로부터 데이터를 수락해야 합니다. 이때 클래스를 GraphQL 입력 유형으로 변환하는 InputType 데코레이터가 유용하게 사용됩니다.

 // book.input.ts
import { InputType, Field } from '@nestjs/graphql';

@InputType()
export class BookInput {
    @Field()
    title: string;

    @Field()
    author: string;

    @Field()
    publishedDate: boolean
}

이제 books.resolver.ts를 다음과 같이 업데이트할 수 있습니다:

 import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { Book } from './book.model';
import { BookService } from './books.service';
import { BookInput } from './book.input';

@Resolver(() => Book)
export class BookResolver {
    constructor(private readonly bookService: BookService) { }

    @Mutation(() => Book)
    async createBook(@Args('input') input: BookInput): Promise<Book> {
        return this.bookService.create(input);
    }

    @Mutation(() => Book)
    async updateBook(
        @Args('id', { type: () => ID }) id: string,
        @Args('input') input: BookInput,
    ): Promise<Book> {
        return this.bookService.update(id, input);
    }

    @Mutation(() => Book)
    async deleteBook(@Args('id', { type: () => ID }) id: string): Promise<Book> {
        return this.bookService.delete(id);
    }
}

그리고 books.service.ts는 다음과 같이 업데이트할 수 있습니다:

 import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Book, BookDocument } from './book.model';

@Injectable()
export class BookService {
    constructor(@InjectModel(Book.name) private bookModel: Model<BookDocument>) { }

    async create(book: Book): Promise<Book> {
        const newBook = new this.bookModel(book);
        return newBook.save();
    }

    async update(id: string, book: Book): Promise<Book> {
        return this.bookModel.findByIdAndUpdate(id, book, { new: true }).exec();
    }

    async delete(id: string): Promise<Book> {
        return this.bookModel.findByIdAndDelete(id).exec();
    }
}

@Mutation 데코레이터는 함수를 변이 유형으로 표시하고 @Args 데코레이터는 함수에 전달된 모든 입력을 가져옵니다.

이 글도 확인해 보세요:  슬랙에서 나만의 사용자 지정 슬래시 명령 만들기

마지막으로, 책의 모듈을 앱 모듈로 임포트하여 작동하도록 만들어야 합니다. 또한 아래와 같이 BooksModule을 forRootAsync에 전달해야 합니다.

 import { BooksModule } from './books/books.module';
/**
 * other imports
*/

@Module({
  imports: [
     ConfigModule.forRoot({
       load: [mongodbConfig],
       isGlobal: true
    }),
    NestGraphQLModule.forRootAsync<ApolloDriverConfig>({
      driver: ApolloDriver,
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
        installSubscriptionHandlers: true,
        sortSchema: true,
        playground: true,
        debug: configService.get<boolean>("DEBUG"),
        uploads: false,
      }),
    }),
    MongooseModule.forRootAsync({
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        uri: configService.get('MONGO_URI')
      })
    }),
    BooksModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule {}

터미널에서 npm run start:dev를 실행하여 코드를 테스트할 수 있으며, 애플리케이션이 포트 3000에서 성공적으로 시작되어야 합니다.

브라우저에서 localhost:3000/graphql 를 열면 쿼리 및 변형을 테스트할 수 있는 Graphiql 인터페이스가 표시됩니다. 다음은 쿼리를 보여주는 예제입니다:

다음은 돌연변이의 예입니다.

NestJS 및 GraphQL로 효율적인 API 구축

Mongoose를 사용하여 MongoDB와 함께 NestJS에서 GraphQL API를 구축하려면 GraphQL API의 스키마, Mongoose 모델의 스키마, 데이터베이스와 상호작용할 서비스, GraphQL 작업을 서비스 메서드에 매핑하는 리졸버를 정의하는 것이 포함됩니다.

NestJS에는 경로를 정의하는 데코레이터, 경로를 보호하는 가드, 요청과 응답을 처리하는 미들웨어 등 API를 구축하기 위한 기능이 내장되어 있습니다. 또한 PostgreSQL, MySQL, SQLite와 같은 다른 데이터베이스와 Apollo 및 TypeGraphQL과 같은 다른 GraphQL 라이브러리도 지원합니다.

By 이지원

상상력이 풍부한 웹 디자이너이자 안드로이드 앱 마니아인 이지원님은 예술적 감각과 기술적 노하우가 독특하게 조화를 이루고 있습니다. 모바일 기술의 방대한 잠재력을 끊임없이 탐구하고, 최적화된 사용자 중심 경험을 제공하기 위해 최선을 다하고 있습니다. 창의적인 비전과 뛰어난 디자인 역량을 바탕으로 All Things N의 잠재 독자가 공감할 수 있는 매력적인 콘텐츠를 제작합니다.