最新のNestJSでGraphQLサーバを実装する

業務でNestJSに触れる機会があったので、[参考サイト](https://zenn.dev/hakushun/articles/7daac74ae9af25)を見ながらサンプルプロジェクトを作ってみました。
参考サイトから読み替えた部分や、バージョンアップによって修正しなければいけない部分があったので、備忘録としてまとめておきます。

## 対象環境
- Node.js: v16.14.2
- @nestjs/core: v8.4.6
- graphql: v15.8.0
- apollo-server-express: v3.8.1


## npm から yarn へ

読み替えた部分としては `npm` から `yarn` への変更。
たいした変更ではないですが、一応変更後のコマンドを書いておきます。

```bash
yarn add -g @nestjs/cli
```
※ `@nestjs/cli` はグローバルへのインストールが必要

```bash
yarn add @nestjs/graphql graphql@^15 apollo-server-express
```

## ApolloDriver の追加

参考サイトのコードのままサーバを起動すると、以下のようなメッセージが表示されて起動が失敗します。

```
[Nest] 10403  - 06/01/2022, 2:49:31 PM   ERROR [GraphQLModule] Missing "driver" option. In the latest version of "@nestjs/graphql" package (v10) a new required configuration property called "driver" has been introduced. Check out the official documentation for more details on how to migrate (https://docs.nestjs.com/graphql/migration-guide). Example:

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
})
```

エラーメッセージがわかりやすくて素晴らしい。
基本は書いてある通りなのですが、 `@nestjs/graphql` がv10にバージョンアップする際に破壊的な（バージョンアップ後にコードを修正しないと起動失敗するような）変更があったらしい。
[メッセージに記載のページ](https://docs.nestjs.com/graphql/migration-guide) をもとに下記をインストールします。

```bash
yarn add @nestjs/apollo
```

修正したコードがこちら。

```typescript
// src/app.module.ts
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; // <-- 追加
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodoModule } from './todo/todo.module';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({ // <-- 変更
      driver: ApolloDriver, // <-- 追加
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
      sortSchema: true,
    }),
    TodoModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```

このように記載することで、サーバが起動できるようになります。

## Mutationの追加

最後に参考サイトには無かったMutationを追加してみます。
IDにUUIDを使いたかったので、以下をインストールする。

```bash
yarn add uuid
```

まずserviceを修正です。

よくわかってないところなのですが、ModelはDDDでいうドメインモデルという認識でいいんでしょうか。
認識が正しい場合は `Todo` モデル内に初期化処理を書くべきでしょうが、今回はserviceの関数内で実装してしまいます。
いわゆるドメインモデル貧血症状態？（覚えたての言葉を使ってみたくなる病）

```typescript
// src/todo/todo.service.ts
import { v4 } from 'uuid'; // <-- 追加

import { Injectable, NotFoundException } from '@nestjs/common';
import { Todo, TodoStatus } from './models/todo.models';

@Injectable()
export class TodoService {

		:
	   中略
		:

  // 新しいTodoを追加する。
  // ID等、自動的に設定できる項目は引数として受け取らないようにする。
  insert(title: string, description: string): Todo { // <-- 関数追加
    const todo = new Todo();
    todo.id = v4();
    todo.title = title;
    todo.description = description;
    todo.status = TodoStatus.NEW;
    todo.createdAt = new Date();
    todo.updateAt = new Date();

    this.todos.push(todo);

    return todo;
  }
}
```

続いてresolverの修正。

```typescript
// src/todo/todo.resolver.ts
import { Args, ID, Mutation, Query, Resolver } from '@nestjs/graphql'; // <-- Mutation追加
import { Todo } from './models/todo.models';
import { TodoService } from './todo.service';

@Resolver()
export class TodoResolver {
  constructor(private todoService: TodoService) {}

		:
	   中略
		:

  // MutationデコレータでMutationを定義
  // https://docs.nestjs.com/graphql/mutations
  // Queryデコレータと同様に引数にArgsデコレータで定義ができる。
  @Mutation(() => Todo) // <-- 関数追加
  insert(
    @Args('title') title: string,
    @Args('description') description: string,
  ): Todo {
    return this.todoService.insert(title, description);
  }
}

```

以上で、 `shema.gql` にMutationが追加されます。
```graphql
type Mutation {
  insert(description: String!, title: String!): Todo!
}
```

Playgrapundで適当なMutationを実行することで動作を確認できる。

```graphql
mutation {
  insert(
    title: "テスト"
    description: "テスト用のTODOです。"
  ){
    id
  }
}
```
```json
{
  "data": {
    "insert": {
      "id": "f532372f-e56f-4add-a718-071e0558aa75"
    }
  }
}
```

```graphql
query {
  findAll {
    id
    title
    description
    status
    createdAt
    updateAt
  }
}
```
```json
{
  "data": {
    "findAll": [
      {
        "id": "6e099204-1c2f-464f-a7cc-18eb5dd383f0",
        "title": "テスト",
        "description": "テスト用のTODOです。",
        "status": "NEW",
        "createdAt": "2022-06-01T15:26:00.368Z",
        "updateAt": "2022-06-01T15:26:00.368Z"
      }
    ]
  }
}
```

```graphql
query {
  findOneById(
    id: "6e099204-1c2f-464f-a7cc-18eb5dd383f0"
  ) {
    id
    title
    description
    status
    createdAt
    updateAt
  }
}
```
```json
{
  "data": {
    "findOneById": {
      "id": "6e099204-1c2f-464f-a7cc-18eb5dd383f0",
      "title": "テスト",
      "description": "テスト用のTODOです。",
      "status": "NEW",
      "createdAt": "2022-06-01T15:26:00.368Z",
      "updateAt": "2022-06-01T15:26:00.368Z"
    }
  }
}
```

## おわりに

後発のフレームワークだけあって、だいぶ使いやすい印象でした。
eslint, prettier, jestが標準装備で、テストコードを生成してくれるところも好印象です。
しばらく使ってみて、感触を確かめてみたいと思います。