NestJSでGraphQLサーバーを構築する

ライブラリのインストール

shell
npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql

バリデーションライブラリのインストール

shell
npm i --save class-validator class-transformer

main.ts でバリデーションを設定します。

main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  const appPort = configService.get<number>('app.port');
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(appPort);
  console.log(`App is running on: ${await app.getUrl()}`);
}
bootstrap();

GraphQL の依存関係を定義

app.module.tsGraphQLModule.forRoot を import します。 あえてその他のモジュールの import も書いていますが、 GraphQLModule.forRoot<ApolloDriverConfig> の部分が今回の主題です。

app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import configration from './config/configration';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NotesModule } from './notes/notes.module';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [configration],
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        type: 'mariadb',
        host: configService.get<string>('database.host'),
        port: configService.get<number>('database.port'),
        username: configService.get<string>('database.username'),
        password: configService.get<string>('database.password'),
        database: configService.get<string>('database.database'),
        synchronize: configService.get<boolean>('database.synchronize'),
        autoLoadEntities: true,
        charset: 'utf8mb4',
      }),
      inject: [ConfigService],
    }),
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: 'src/schema.gql',
      sortSchema: true,
    }),
    NotesModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

DTO を作成する

GraphQL のクエリの Input, Output を定義します。

src/notes/dto/notes.dto.ts
import { ArgsType, Field, ID, Int, ObjectType } from '@nestjs/graphql';
import { IsInt, Max, Min } from 'class-validator';

@ArgsType()
export class NotesPayload {
  @IsInt()
  @Min(0)
  @Field(() => Int)
  skip: number;

  @Field(() => Int)
  @Min(1)
  @Max(50)
  take: number;
}

@ObjectType({ description: 'ノート' })
export class NotesOutput {
  @Field(() => ID)
  id: string;

  @Field(() => String)
  title: string;
}

Resolver を作成する

パラメータを受け取って、Output で定義した型で値を返します。

src/notes/notes.resolver.ts
import { Args, Query, Resolver } from '@nestjs/graphql';
import { NotesOutput, NotesPayload } from './dto/notes.dto';

@Resolver()
export class NotesResolver {
  @Query(() => [NotesOutput], { name: 'notes' })
  notes(@Args() notesArgs: NotesPayload): NotesOutput[] {
    console.log('サービスに渡してデータを取得します - notesArgs:', notesArgs);
    return [
      {
        id: 'uuid1',
        title: 'note1',
      },
    ];
  }
}

サブモジュールの providers に Resolver を設定する

src/notes/nodes.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
import { NoteEntity } from './entities/note.entity';
import { Module } from '@nestjs/common';
import { NotesResolver } from './notes.resolver';

@Module({
  imports: [TypeOrmModule.forFeature([NoteEntity])],
  providers: [NotesResolver],
})
export class NotesModule {}

この NotesModule はルートモジュール( app.module.ts )で import します。

クエリを投げてみる

Query
query notes(
  $skip: Int!,
  $take: Int!
) {
  notes(
    skip: $skip,
    take: $take
  ) {
    id
    title
  }
}
Variables
{"skip": 1, "take": 5}

上記のクエリを投げると、結果が返って来ます。

結果
{
  "data": {
    "notes": [
      {
        "id": "uuid1",
        "title": "note1"
      }
    ]
  }
}

http://localhost:3000/graphql 向けに POST リクエストを投げてます。

Thunder Client のスクショです。

参考