reduxとapolloとnestjsとgraphqlで作ったTodoListを元に、graphqlの使い方を一部紹介

# この記事について
graphqlで動くものを書き換えて試してみたい人向けです。

graphqlを使ったTodoListを用いて、graphqlの使い方を一部紹介します。言語はtypescriptを使っています。
特に、graphqlのフロントエンドとバックエンドの対応箇所の紹介となります。

yarn, nodejs, dockerあたりが動作する環境であれば動くと思います。
実行方法及び、詳しいバージョンや利用ライブラリ等は、各リポジトリのREADME.md及びpackage.jsonを参照してください。
#### フロントエンド
https://github.com/aki85/todo-redux-graphql

#### バックエンド
https://github.com/aki85/todo-nestjs-graphql

# コードと紹介
ファイル名だけで補足している箇所もあるので、VSCodeを使える場合は、`ctrl + P` or `cmd + P`のファイル名検索を駆使して見ていただけると把握しやすいかと思います。

## フロントエンドgraphql: fragments, queries, mutations
バックエンドで自動生成される`schema.graphql`を持ってきて、
`frontend/src/graphql`以下のファイルを良い感じに書いて`yarn run graphql-codegen`すると、`frontend/src/api.ts`が生成され、
Reactコンポーネントから呼び出せる(`frontend/src/components/TodoComponent.tsx: 12行目`付近参照)ようになります。

VSCodeを用いている場合は、`Apollo GraphQL`拡張機能がオススメです。
`schema.graphql`を元に`frontend/src/graphql`以下のファイルを書く時にエラーを出したりしてくれます。
`schema.graphql`を更新しても反映されないことがありますが、その際は拡張機能を一度無効にして有効にすると反映されます。

### fragments
他のgraphqlファイルで複数回呼ばれるような取得パターンがある場合にfragmentを作成することで記述量を削減できます。
graphqlAPI側が用意している戻り値の中からデータを選ぶ必要があり、今回だと`backend/src/models/todo/todo.ts`や`schema.graphql: 5~11行目`から選ぶことができます。

```graphql:frontend/src/graphql/fragments/todo.graphql
fragment TodoItem on Todo {
  id
  title
  isDone
}
```
### queries
データを取得する場合にqueryを用います。
取得対象のオブジェクトが配列でもオブジェクトでも(`[Todo]`でも`Todo`)queryやmutationではオブジェクト形式で記述します。

以下ファイル2行目、8行目のような内側の`todos, todo`はAPI側と同じにする必要がありますが、1行目、7行目のような外側の`todos, todo`は`getTodos, getTodo`のように書き換えても構いません。
その場合、Reactコンポーネント側で、`useTodosQuery -> useGetTodosQuery`のように変更する必要があります。

また、ファイルを分割して記載しても問題ありません。

```graphql:frontend/src/graphql/queries/todo.graphql
query todos {
  todos {
    ...TodoItem
  }
}

query todo($id: ID!) {
  todo(id: $id) {
    ...TodoItem
  }
}
```
以下の様な改変が可能です。

```graphql:frontend/src/graphql/queries/getTodo.graphql(例)
query getTodo($id: ID!) {
  todo(id: $id) {
    id
    title
    isDone
    createdAt
    updatedAt
  }
}
```

```graphql:frontend/src/graphql/queries/getTodos.graphql(例)
query getTodos {
  todos {
    id
    title
    isDone
  }
}
```
### mutations
データを変更する場合はmutationを用います。
それ以外はqueriesと同様です。

```graphql:frontend/src/graphql/mutations/todo.graphql
mutation createTodo($input: TodoInput!) {
  createTodo(input: $input) {
    ...TodoItem
  }
}

mutation updateTodo($id: ID!, $input: TodoInput!) {
  updateTodo(id: $id, input: $input) {
    ...TodoItem
  }
}

mutation deleteTodo($id: ID!) {
  deleteTodo(id: $id)
}

```


## バックエンドgraphql: resolver
バックエンドサーバーが稼働している間は、
`@Query`アノテーションと直後のメソッド、`@Mutation`アノテーションと直後のメソッドを元に`backend/src/schema.graphql`が自動更新されます。

`frontend/src/graphql/queries/todo.graphql: 2行目`の`todos`と、以下ファイルの46行目のメソッド名`todos`が対応しています。


```typescript:backend/src/todo/todo.resolver.ts
import { Resolver, Query, Args, ID, Mutation } from '@nestjs/graphql'
import { BadRequestException } from '@nestjs/common'

import Todo from '../models/todo/todo'

import { TodoService } from './todo.service'
import { DBService } from '../db/db.service'
import TodoInput from '../models/todo/todo.input'

@Resolver()
export class TodoResolver {
  constructor(
    private readonly dbservice: DBService,
    private readonly service: TodoService,
  ) {}

  getClient() {
    return this.dbservice.getClient()
  }
  
  @Query(
    returns => Todo,
    {
      description: 'todoを取得',
    },
  )

  async todo(
    @Args({ name: 'id', type: () => ID }) id: string
  ): Promise<Todo> {
    const client = this.getClient()
    const res = await this.service.get(client, id)
    if (!res) {
      throw new BadRequestException(`Todo Not Found id: ${id}`)
    }
    return res
  }
  
  @Query(
    returns => [Todo],
    {
      description: 'todo一覧を取得',
    },
  )

  async todos(): Promise<Todo[]> {
    const client = this.getClient()
    const res = await this.service.scan(client)
    return res
  }
  
  @Mutation(
    returns => Todo,
    {
      description: 'todoを作成',
    },
  )
  async createTodo(@Args('input') input: TodoInput): Promise<Todo> {
    const client = this.getClient()
    const res = await this.service.create(client, input)
    return res
  }

  @Mutation(
    returns => Todo,
    {
      description: 'todoを更新',
    },
  )
  async updateTodo(
    @Args({ name: 'id', type: () => ID }) id: string,
    @Args('input') input: TodoInput,
  ): Promise<Todo> {
    const client = this.getClient()
    const res = await this.service.get(client, id)
    if (!res) {
      throw new BadRequestException(`Todo Not Found id: ${id}`)
    }
    return await this.service.update(client, {
      ...res,
      ...input,
    })
  }

  @Mutation(
    returns => String,
    {
      description: 'todoを削除',
    },
  )
  async deleteTodo(
    @Args({ name: 'id', type: () => ID }) id: string,
  ): Promise<string> {
    const client = this.getClient()
    const res = await this.service.get(client, id)
    if (!res) {
      throw new BadRequestException(`Todo Not Found id: ${id}`)
    }
    await this.service.delete(client, id)
    return id
  }
}
```

# まとめ
以上が主なgraphql部分です。

個人的にですが、graphqlAPIはコード自体にデータの送受信内容が明記されているため、処理がイメージしやすく開発時の負担が少なくなるように感じました。
特に、APIとの接続や実装の変更等が、処理がイメージしやすい分スムーズに行えるように思います。

メソッド名を書き換えてみたり、オリジナルの機能をつけ足してみたりして、graphqlを体験してみてください！