Chapter 05無料公開

NestJS でGraphQLバックエンド - コードファーストアプローチ

waddy_u
waddy_u
2022.02.18に更新

構成が固まったので、早速 GraphQL バックエンドから構築します。が、その前に、本書のこの先の流れについて説明しておきます。

  1. NestJS で GraphQL サーバーが動くことを確認する
  2. Next.js で GraphQL クライアントが動くことを確認する
  3. DB にデータが入っている想定で、個人ブログの記事一覧画面を作ってみる
  4. Cloud Run と Cloud SQL へデプロイする
  5. 記事一覧をブラッシュアップする
  6. 記事の詳細画面をつくる

GraphQL で一連の開発を体験したあとは、まず優先してデプロイします。先にデプロイしておかないと、完成しきってからデプロイするのは大変です。機能が少ないうちに、CI/CD を組んでローカルの成果物を自動でデプロイできる状況を作っておくと後々楽です。一緒にデプロイもしましょう。

NestJS

GraphQL バックエンドを NestJS で構築します。公式サイトによると、NestJS はプログレッシブ Node.js フレームワークです。オブジェクトのインスタンス管理方法が独特で、Angularの影響を強く受けており、Module と呼ばれるTypeScript の Decoratorsを駆使した仕組みを提供します。やはり文字をみても分かりづらいと思うので早速 GraphQL サーバーをたてていきましょう。NestJS は、内部的に Apollo Server を使っている@nestjs/graphqlというパッケージが使えます。

https://docs.nestjs.com/graphql/quick-start

TypeScript の Decorator という話をしましたが、NestJS は TypeScript を前提としたフレームワークです。ここから作っていくアプリケーションは TypeScript で構築します。GraphQL サーバーを立ち上げるところまでやってみましょう。

インストール

ドキュメントに従いインストールします。

ターミナル
mkdir -p second-step-graphql
cd second-step-graphql

yarn init --yes

# NestJS本体はスターターを利用してインストールします。NestJS CLI を利用
yarn add @nestjs/cli
yarn nest new backend
? Which package manager would you ❤️  to use?
  npmyarn
  pnpm

次に generate された backend ディレクトリへ移動し、GraphQL 関連をインストール。本書ではバージョンを9系に固定します。

ターミナル
cd backend
yarn add @nestjs/graphql@9.1.2 graphql@^15 apollo-server-express

インストールは以上で OK です。

コードファーストとスキーマファースト

NestJS で GraphQL サーバーを開発していくにあたり、ふたつ方針の選択肢があります。なお、どちらを採用した場合でも、先の節で示した「Schema、データ、Resolver を用意すれば GraphQL サーバーは動かせる」という原理に変わりありません。この選択は、Schema をどうやって作成するか という違いです。

  1. コードファースト: GraphQL ライブラリが検知できるよう、TypeScript コード中にヒントとなるスニペットを埋め込む。ライブラリが自動で schema.gql を生成する
  2. スキーマファースト: schema.gqlを絶対王者としてこれを開発者がメンテナンスする。ライブラリが TypeScript の型情報を生成する

本書ではコードファーストアプローチを採用 します。GraphQL の本質を考えると、schema.gqlを中心に据えるほうが理にかなっているかもしれません。しかし実際に開発してみての感想としては、結局主役はデータと Resolver になります。TypeScript の動く実装がなければ GraphQL サーバーも動かせません。大人数が絡むような、大規模な開発ではもしかするとスキーマファーストのほうが合うかもしれませんが、少なくとも本書で扱うレベルの開発作業はコードファーストのほうが楽です。企業様の例としては、メルカリ Shops の BFF レイヤでも NestJS を採用しており、そちらの開発でもコードファーストアプローチを採っているそうです。

https://engineering.mercari.com/blog/entry/20210810-mercari-shops-tech-stack/

また、GraphQL のスキーマ管理は悩ましい課題ですが、NestJS の Code First アプローチはコードに集中することができる良い手法でした。これによってエンジニアはスキーマとコードを同期する必要がなくなり、コードだけを管理すればよくなりました。

GraphQL と TypeScript ではじめる

方針も決まったので、サーバーを立てていきましょう。NestJS はインスタンスの管理にModulesという単位でまとめるアーキテクチャを採用しており、アプリケーションコードも外部ライブラリも、Modules という単位で提供することが基本方針です。こうすることで、

  • NestJS の世界で統一的にインスタンス管理できる
  • NestJS の世界でインターフェースを統一できる

というメリットがあります。GraphQL ライブラリもこの例に漏れず、Modules として提供されており、app.module.tsで以下のように書いて初期化します。

import { Module } from "@nestjs/common";
import { GraphQLModule } from "@nestjs/graphql";

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

アプリケーションで利用するすべての Modules は、app.module.tsで読み込み・初期化します。GraphQLModuleforRoot()には GraphQL のオプションを設定できます。コードファーストアプローチを採用する場合、schema.gqlを出力するよう、パスを指定してみましょう。

app.module.ts
import { Module } from "@nestjs/common";
import { GraphQLModule } from "@nestjs/graphql";
import * as path from 'path';

@Module({
  imports: [
    GraphQLModule.forRoot({
      autoSchemaFile: path.join(process.cwd(), "src/schema.gql"),
      sortSchema: true,
    }),
  ],
})
export class AppModule {}

これでObjectTypeを用意すれば、schema.gqlが作成されます。

ObjectType の作成

そのObjectTypeを定義してみましょう。そのためにまずはModulesを作成します。

ターミナル
yarn nest generate module components/posts

ObjectTypeであるPostModelを作成します。ブログ投稿を想定して書きます。

src/components/posts/interfaces/post.model.ts
import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class PostModel {
  @Field((type) => String)
  id: string;

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

このクラスはレスポンスのシリアライザかつschema.gql自動生成のための情報です。export interface PostModel {だとJSへコンパイルした後に型情報が抜けてしまうため、classとしてます。これはNestJSフレームワークの制約です。@Field((type) => String)の記述がGraphQL Schemaのためのデコレータです。TypeScriptのフィールドに着いているので、どの項目がschema.gqlとどう対応するかわかりやすいですね。

Resolvers(コードファースト)とデータ

SchemaときたのでこんどはResolverとデータです。NestJSではResolverもTypeScriptコードで表現します。

src/components/posts/post.resolvers.ts(新規作成)
import { Args, Query, Resolver } from '@nestjs/graphql';
import { PostModel } from './interfaces/post.model';

@Resolver((of) => PostModel)
export class PostsResolver {
  constructor() {}

  @Query(() => [PostModel], { name: 'posts', nullable: true })
  async getPosts() {
    return [
      {
        id: '1',
        title: 'NestJS is so good.',
      },
      {
        id: '2',
        title: 'GraphQL is so good.',
      },
    ];
  }
}
  • @Resolver((of) => PostModel): PostModelに相当するスキーマを返すことを宣言しています。つまり、このクラスでPostModelへ書いたすべてのフィールドのデータを取得できることを意味します
  • @Query(() => [PostModel], { name: 'posts', nullable: true }): postsというクエリが呼ばれたらこのメソッドを実行する動きになります

PostsModuleの修正

Resolverを定義した後、PostsModuleへ登録します。yarn nest generate module components/postsしたとき一緒に作られたposts.module.tsを修正します。

src/components/posts/posts.module.ts
import { Module } from '@nestjs/common';
import { PostsResolver } from './post.resolvers';

@Module({
+  providers: [PostsResolver],
})
export class PostsModule {}

このようにNestJSはModuleごとにロジックをカプセル化し、統一的なインターフェースで初期化、インスタンス管理ができるようになっています。


定義したModuleを組み合わせてアプリケーションを構築する - https://docs.nestjs.com/providers

AppModuleの修正、起動

最後に作成したModuleをapp.module.tsへ登録する...のですが、さきほどyarn nest generate module components/postsしたとき自動で設定されています。

src/app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import * as path from 'path';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PostsModule } from './components/posts/posts.module';

@Module({
  imports: [
    GraphQLModule.forRoot({
      autoSchemaFile: path.join(
        process.cwd(),
        'src/generated/graphql/schema.gql',
      ),
      sortSchema: true,
    }),
    PostsModule, // これ
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Schema(自動生成)、Resolver、データ(固定データ)がすべて揃いました。これで動くでしょうか。

ターミナル
yarn start:dev

👆このコマンド実行時(コード側に問題がなければ)schema.gqlファイルが生成されます。http://localhost:3000/へアクセスすると普通のAPIのほうは動いている様子。

あとはGraphQLサーバーがどうかですが、さきほど Apollo Server を立ち上げるときに使った Apollo Studio からアクセスしてみましょう。GraphQLサーバーのアドレスはhttp://localhost:3000/graphqlとしてください。

シンプルな Apollo Server の例で示したのと同じように、Apollo Studio から使えました。フレームワークが異なっても同じ開発体験が得られるというのもGraphQLでサーバーを組む良さといえます。最後に、schema.gqlがどうなっているか確認して終わります。

src/generated/graphql/schema.gql
# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

type PostModel {
  id: String!
  title: String!
}

type Query {
  posts: [PostModel!]
}

自動生成された痕跡があり、「いじらないでね」と書かれています。TypeScriptのデコレータから生成することで、私たちはschema.gqlのことを考えずにすみます。

この節のまとめ

ソースコード

この節で実行したコードは以下のリポジトリで公開しています。

https://github.com/cm-wada-yusuke/gql-nest-prisma-training/tree/main/second-step-graphql/backend