虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

Amplifyにおける複数モデルのGraphQL APIについて

虎の穴ラボのかのたんです。

AWSが公式で提供しているAmplifyという開発ツールは非常に簡単、かつ高速にAPI環境が構築できるということで注目度が上がっており、AWSの公式セミナーや勉強会等でも取り上げられる機会が増えています。

弊社でも有志でAmplifyの使い方についての調査を行っており、特に以前弊社のNSSさんがブログに投稿したAmplifyに関する記事は非常に多くの方に読んでいただきました。

toranoana-lab.hatenablog.com

Amplifyの導入については、こちらのNSSさんの記事が非常に参考になります。

私の方でもAmplifyについて触ってみたので、今回の私の記事は上記のNSSさんの記事から派生するような形で、複数モデルのGraphQL APIの作り方について書きます。

GraphQL APIの作成

本記事の前提条件として、上記のNSSさんの記事等を参考にしてAmplifyのインストールやAmplifyプロジェクトの新規作成、AWS環境との連携などの初期設定を行ってください。

上記の記事に「AmplifyでAPIの実装」という項目がありますが、

$ amplify add api

を行ったあとの選択肢で

Single object with fields (e.g., “Todo” with ID, name, description)

というものを選んでます。今回はこのタイミングで

One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)

f:id:toranoana-lab:20220107142115p:plain

の方を選んでみましょう。するとこういったコードが生成されます。

type Blog @model {
  id: ID!
  name: String!
  posts: [Post] @hasMany
}

type Post @model {
  id: ID!
  title: String!
  blog: Blog @belongsTo
  comments: [Comment] @hasMany
}

type Comment @model {
  id: ID!
  post: Post @belongsTo
  content: String!
}

このコードを参考にすることで、複数モデルにまたがるGraphQLが作成可能です。

amplify mock api で起動することにより、http://localhost:20002/ にてネストされたGraphQLのqueryが利用できることが確認できます。

いくつかテストデータを作って実行してみましょう。

Mutationによるデータ準備

GraphQLではMutationを使ってデータの作成、更新、削除を行います。

テストデータを作るために、CreateのMutationを使って各モデルにデータを入れていきます。

ブログ作成

ブログ01とブログ02という、2つのブログを作成します。

# request
mutation MyMutation {
  createBlog(input: {name: "ブログ01"})
}

# response
{
  "data": {
    "createBlog": {
      "id": "0ee8fb8d-ddca-404c-878b-1465fd782381"
    }
  }
}
# request
mutation MyMutation {
  createBlog(input: {name: "ブログ02"})
}

# response
{
  "data": {
    "createBlog": {
      "id": "9b5eee80-5567-4f8d-89fd-3e4f68761c9a"
    }
  }
}

ここで作成したブログのidをメモしておきます。

投稿作成

今度は投稿01-01と投稿01-02をブログ01に、投稿02-01をブログ02に紐づけてみます。

blogPostsIdが外部キーの役割を果たすので、投稿01-01と投稿01-02にはブログ01の、投稿02-01にはブログ02のidを入れます。

# Request
mutation MyMutation {
  createPost(
    input: {title: "投稿01-01", blogPostsId: "0ee8fb8d-ddca-404c-878b-1465fd782381"}
  ) {
    id
  }
}

# Response
{
  "data": {
    "createPost": {
      "id": "27179845-0f8f-40bb-b202-5a85ea054696"
    }
  }
}
# Request
mutation MyMutation {
  createPost(
    input: {title: "投稿01-02", blogPostsId: "0ee8fb8d-ddca-404c-878b-1465fd782381"}
  ) {
    id
  }
}

# Response
{
  "data": {
    "createPost": {
      "id": "cf02932b-737f-4460-a085-47eb8dcc3b92"
    }
  }
}
# Request
mutation MyMutation {
  createPost(
    input: {title: "投稿02-01", blogPostsId: "9b5eee80-5567-4f8d-89fd-3e4f68761c9a"}
  ) {
    id
  }
}

# Response
{
  "data": {
    "createPost": {
      "id": "3b392929-6b96-4286-bb96-8c71da7d6a49"
    }
  }
}

今度は投稿のidをメモ。

コメントの作成

最後はコメントを作成します。投稿01-01にコメント01-01-01を、投稿02-01にコメント02-01-01を紐付けましょう。

先ほどと同様に、今度はpostCommentsIdにそれぞれの投稿のidを入れます。

# Request
mutation MyMutation {
  createComment(input: {content: "コメント01-01-01", postCommentsId: "27179845-0f8f-40bb-b202-5a85ea054696"}) {
    id
  }
}

# Response
{
  "data": {
    "createComment": {
      "id": "97da2b9d-7dd1-42d8-a7a0-36a21e6a0eba"
    }
  }
}
# Request
mutation MyMutation {
  createComment(input: {content: "コメント02-01-01", postCommentsId: "3b392929-6b96-4286-bb96-8c71da7d6a49"}) {
    id
  }
}


# Response
{
  "data": {
    "createComment": {
      "id": "429717e3-438e-4536-a1e5-a2a1c9fc2409"
    }
  }
}

これでデータの準備は完了です。

Queryによるデータ取得

GraphQLではQueryを使って更新を伴わないデータの参照を行います。

ここからはblogs→posts→commentsの順と、comments→post→blogの順の2パターンでデータを取得してみましょう。

listBlogsによるブログ一覧の取得

listBlogsを使って、ブログ一覧を取得します。

blogs のitemsの中で postsを、 posts のitemsの中で comments を取得しているところがネストされた関連モデルを一気に取得するためのポイントです。

# Request
query MyQuery {
  listBlogs {
    items {
      id
      name
      posts {
        items {
          id
          title
          blogPostsId
          comments {
            items {
              id
              content
              postCommentsId
            }
          }
        }
      }
    }
  }
}

# Response
{
  "data": {
    "listBlogs": {
      "items": [
        {
          "id": "9b5eee80-5567-4f8d-89fd-3e4f68761c9a",
          "name": "ブログ02",
          "posts": {
            "items": [
              {
                "id": "3b392929-6b96-4286-bb96-8c71da7d6a49",
                "title": "投稿02-01",
                "blogPostsId": "9b5eee80-5567-4f8d-89fd-3e4f68761c9a",
                "comments": {
                  "items": [
                    {
                      "id": "429717e3-438e-4536-a1e5-a2a1c9fc2409",
                      "content": "コメント02-01-01",
                      "postCommentsId": "3b392929-6b96-4286-bb96-8c71da7d6a49"
                    }
                  ]
                }
              }
            ]
          }
        },
        {
          "id": "0ee8fb8d-ddca-404c-878b-1465fd782381",
          "name": "ブログ01",
          "posts": {
            "items": [
              {
                "id": "cf02932b-737f-4460-a085-47eb8dcc3b92",
                "title": "投稿01-02",
                "blogPostsId": "0ee8fb8d-ddca-404c-878b-1465fd782381",
                "comments": {
                  "items": []
                }
              },
              {
                "id": "27179845-0f8f-40bb-b202-5a85ea054696",
                "title": "投稿01-01",
                "blogPostsId": "0ee8fb8d-ddca-404c-878b-1465fd782381",
                "comments": {
                  "items": [
                    {
                      "id": "97da2b9d-7dd1-42d8-a7a0-36a21e6a0eba",
                      "content": "コメント01-01-01",
                      "postCommentsId": "27179845-0f8f-40bb-b202-5a85ea054696"
                    }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  }
}

Graphiql上ではこのように見えます。

f:id:toranoana-lab:20220112162115p:plain

f:id:toranoana-lab:20220112162125p:plain

無事にblogs→posts→commentsの順で取得できました。

listCommentsによるコメント一覧の取得

続いてlistCommentsを使って、コメント一覧を取得します。

今度は先ほどとは逆の順で、comments の中で postを、 posts の中で blog を取得することで、親モデルへと辿っているところがポイントです。

# Request
query MyQuery {
  listComments {
    items {
      id
      content
      postCommentsId
      post {
        id
        title
        blogPostsId
        blog {
          id
          name
        }
      }
    }
  }
}

# Response
{
  "data": {
    "listComments": {
      "items": [
        {
          "id": "97da2b9d-7dd1-42d8-a7a0-36a21e6a0eba",
          "content": "コメント01-01-01",
          "postCommentsId": "27179845-0f8f-40bb-b202-5a85ea054696",
          "post": {
            "id": "27179845-0f8f-40bb-b202-5a85ea054696",
            "title": "投稿01-01",
            "blogPostsId": "0ee8fb8d-ddca-404c-878b-1465fd782381",
            "blog": {
              "id": "0ee8fb8d-ddca-404c-878b-1465fd782381",
              "name": "ブログ01"
            }
          }
        },
        {
          "id": "429717e3-438e-4536-a1e5-a2a1c9fc2409",
          "content": "コメント02-01-01",
          "postCommentsId": "3b392929-6b96-4286-bb96-8c71da7d6a49",
          "post": {
            "id": "3b392929-6b96-4286-bb96-8c71da7d6a49",
            "title": "投稿02-01",
            "blogPostsId": "9b5eee80-5567-4f8d-89fd-3e4f68761c9a",
            "blog": {
              "id": "9b5eee80-5567-4f8d-89fd-3e4f68761c9a",
              "name": "ブログ02"
            }
          }
        }
      ]
    }
  }
}

Graphiql上ではこのように見えます。

f:id:toranoana-lab:20220112162221p:plain

f:id:toranoana-lab:20220112162230p:plain

無事にcomments→post→blogという関係も再現できました。

まとめ

今回はAmplifyを使った、複数モデルにまたがるGraphQL APIの作成と使用についてご紹介しました。

他にもAmplifyに関しては

  • 認証・認可
  • 絞り込みとソート
  • enum型の使用
  • 全文検索

など書けるネタが結構あるのですが、2021年12月18日にAmplifyのv2が出たことで記述方法が変わり、コードの追加検証が必要なため今回は断念しました。

公式ドキュメント等で書き換え方法についてサポートしてますので、v2の書き方に慣れていくのがよいかと思います。

https://docs.amplify.aws/cli/migration/transformer-migration/

Amplifyは若いプロジェクトなので新機能追加や仕様変更もあるかと思いますが、キャッチアップしながら使っていきましょう。

P.S.

採用情報
■募集職種
yumenosora.co.jp

カジュアル面談も随時開催中です
■お申し込みはこちら!
news.toranoana.jp

■ToraLab.fmスタートしました!
メンバーによるPodcastを配信中!
是非スキマ時間に聞いて頂けると嬉しいです。
anchor.fm
■Twitterもフォローしてくださいね!
ツイッターでも随時情報発信をしています
twitter.com