4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

VolareAdvent Calendar 2020

Day 17

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

Posted at

この記事について

graphqlで動くものを書き換えて試してみたい人向けです。

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

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

フロントエンド

バックエンド

コードと紹介

ファイル名だけで補足している箇所もあるので、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.tsschema.graphql: 5~11行目から選ぶことができます。

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, todogetTodos, getTodoのように書き換えても構いません。
その場合、Reactコンポーネント側で、useTodosQuery -> useGetTodosQueryのように変更する必要があります。

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

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

query todo($id: ID!) {
  todo(id: $id) {
    ...TodoItem
  }
}

以下の様な改変が可能です。

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

mutations

データを変更する場合はmutationを用います。
それ以外はqueriesと同様です。

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が対応しています。

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を体験してみてください!

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?