🧑‍🎓

GraphQL入門:基本概念とクエリの書き方を一通り体験しよう

に公開

こんにちは!dotDのエンジニアの前田です!
現在の業務でGraphQLを使用していまして、基本的な使い方についてまとめました。
ご興味のある方はぜひ読んでみてください!

GraphQLとは

  • GraphQLは、Facebook(現Meta)が開発し、2015年にオープンソースとして公開された APIのクエリ言語 です。

💡APIのクエリ言語とは?
クライアントがAPIを通じて必要なデータを取得・操作する際に使う、「問い合わせ言語」のことです。

GraphQLの主な特徴

  1. 単一のHTTPエンドポイントにクエリをPOSTする
    GraphQLでは、全てのリクエストを1つの固定されたエンドポイントに送信します。
  • エンドポイント例:POST /graphql
  • クエリ:取得したいデータ構造や条件をリクエスト内で定義(※詳細は後述)

REST APIのように「エンドポイントごとにリソースを分ける」のではなく、
1つのエンドポイントでどんな処理も可能になるのが大きな特徴です。

  1. レスポンスステータスコードは常に200
    GraphQLでは、レスポンスステータスコードは成功もエラーも共に200が返されます。
    エラーがあった場合は、レスポンスボディのerrorsフィールドにエラーの詳細内容が格納されます。

GraphQLのスキーマとリゾルバ

GraphQLでは、APIの設計を大きく2つの役割に分けて考えます。

スキーマ(schema)
クライアントがどんなデータを取得・操作できるかを定義する「インターフェース」

リゾルバ(resolver)
実際にデータを取得・操作する処理を実装する部分

スキーマ定義

スキーマでは、GraphQL APIがどんなクエリや引数を受け付け、どんなデータを返すのかを定義します。
これは、GraphQL APIのインターフェース定義にあたります。

# ユーザーオブジェクトの構造
type User {
  id: ID!
  name: String!
  age: Int
  posts: [Post]!  # ユーザが作成した投稿(リスト型)
}

# 投稿オブジェクトの構造
type Post {
  id: ID!
  title: String!
  content: String!
  author: User!  # ユーザ情報
}

# データ取得の方法を定義
type Query {
  # すべてのユーザを取得
  users: [User]!
  
  # IDでユーザを取得
  user(id: ID!): User
}

# データ操作の定義
type Mutation {
  # 新しい投稿を作成
  createPost(title: String!, content: String!, authorId: ID!): Post

  # 投稿を更新
  updatePost(id: ID!, title: String, content: String): Post
  
  # 投稿を削除
  deletePost(id: ID!): Post
}

クライアントはどんなデータが取得できるか・どんな操作ができるかをスキーマから知り、必要なデータだけを指定してクエリを送信します。

[全ユーザーのidとnameだけ取得したい場合のクエリ例]

# Query
query {
  users {
    id
    name
  }
}

[新規投稿したい場合のクエリ例]
記事のタイトル・内容・作成者は変数オブジェクトにまとめて、Mutationのクエリとともに送信します。

# Mutation
mutation ($title: String!, $content: String!, $authorId: ID!) {
  createPost(title: $title, content: $content, authorId: $authorId) {
    id
    title
    content
    author {
      id
      name
    }
  }
}

# 変数オブジェクト
{
   "title": "new title 2",
   "content": "new content 2",
   "authorId": "2"
}

リゾルバ定義

GraphQLのスキーマ(Query型やMutation型など)は、どんなデータを扱うか、どのようなリクエストができるかを定義します。

しかし、実際にどのようにデータを取得・操作するかはスキーマだけでは決まりません。
それを実装するのが リゾルバ(resolver) です。

const resolvers = {
  Query: {
    // データ取得の処理をここに定義
  },
  Mutation: {
    // データ作成・更新・削除の処理をここに定義
  }
}

クエリのリゾルバの定義例

① スキーマ定義(Query型)

type Query {
  # すべてのユーザを取得
  users: [User]!
  
  # IDでユーザを取得
  user(id: ID!): User
}

このようにスキーマでフィールド(usersやuser)を定義したら、
同じ名前の関数をリゾルバに実装する必要があります。

② リゾルバ実装

const resolvers = {
   Query: {
     // 簡略化のために静的なデータを使っているが、実際はDBから取得する想定
     users: () => users,
     // 引数(id)に一致するユーザーを返す
     user: (_, { id }) => users.find((user) => user.id === id)
   }
}

💡補足:リゾルバ関数の引数
リゾルバ関数は以下の4つの引数を取ることができますが、よく使うのはこのobjargsです:

(obj, args, context, info)
  • obj:親オブジェクト(この例では不要なので_で省略)
    [親がいる例: 親がuser、子がpost]
query {
  user(id: "1") {
    name
    posts {
      title
    }
  }
}
const resolvers = {
  User: {
    posts: (parent) => {
      // parent は上の user オブジェクトがそのまま渡される
      return getPostsByUserId(parent.id);
    }
  }
};

このようにUser.postsのリゾルバでは、
parentuser(id: "1") の結果(つまり、対象のユーザーオブジェクト)が渡されます。
そのparent.idを使って、その人の投稿を取得するという流れです。

つまり、親子関係のあるデータを扱う場合、
子フィールドごとにリゾルバを定義して、親のデータを元に子の情報を取得する必要があります。

  • args:クエリで指定された引数(例:id)

ミューテーションのリゾルバ定義の方法も同様です!

GraphQLサーバーの構築方法

  • ローカル環境にGraphQLサーバーを構築してGraphQL APIにリクエストを投げることを試したい方は、以下の手順を実施してください!

前提:以下がインストールされていること
Node.js
yarn

1. Node.jsプロジェクトの作成

mkdir graphql-sample && cd graphql-sample
yarn init -y

2. 必要なパッケージのインストール

  • @apollo/server:GraphQLサーバーを実装するためのパッケージ
  • graphql:GraphQLに関するパッケージ
  • nodemon:ホットリロード用パッケージ
yarn add @apollo/server graphql
yarn add -D nodemon

3. yarn のスクリプトを設定
package.jsonに以下を追加

"scripts": {
  "start": "nodemon ./index.js"
}

4. index.js を作成し、以下のコードを記述

touch ./index.js
const { ApolloServer } = require("@apollo/server");
const { startStandaloneServer } = require("@apollo/server/standalone");

// ダミーデータ(ユーザ情報)
const users = [
  { id: "1", name: "Alice", email: "alice@example.com" },
  { id: "2", name: "Bob", email: "bob@example.com" },
];
// ダミーデータ(投稿情報)
const posts = [
  {
    id: "1",
    title: "GraphQL Basics",
    content: "This is an introduction to GraphQL",
    authorId: "1",
  },
  {
    id: "2",
    title: "Apollo Server Guide",
    content: "Learn how to use Apollo Server",
    authorId: "2",
  },
];

// GraphQLのスキーマ定義
const typeDefs = `
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post]
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
  }

  type Query {
    users: [User]
    posts: [Post]
    user(id: ID!): User
    post(id: ID!): Post
  }

  type Mutation {
    createUser(name: String!, email: String!): User
    createPost(title: String!, content: String!, authorId: ID!): Post
    updateUser(id: ID!, name: String, email: String): User
    updatePost(id: ID!, title: String, content: String): Post
    deleteUser(id: ID!): User
    deletePost(id: ID!): Post
  }
`;

// リゾルバの定義
const resolvers = {
  Query: {
    users: () => users,
    posts: () => posts,
    user: (_, { id }) => users.find((user) => user.id === id),
    post: (_, { id }) => posts.find((post) => post.id === id),
  },
  Mutation: {
    createUser: (_, { name, email }) => {
      const newUser = { id: crypto.randomUUID(), name, email };
      users.push(newUser);
      return newUser;
    },
    createPost: (_, { title, content, authorId }) => {
      const newPost = {
        id: crypto.randomUUID(),
        title,
        content,
        authorId,
      };
      posts.push(newPost);
      return newPost;
    },
    updateUser: (_, { id, name, email }) => {
      const user = users.find((user) => user.id === id);
      if (name) user.name = name;
      if (email) user.email = email;
      return user;
    },
    updatePost: (_, { id, title, content }) => {
      const post = posts.find((post) => post.id === id);
      if (title) post.title = title;
      if (content) post.content = content;
      return post;
    },
    deleteUser: (_, { id }) => {
      const index = users.findIndex((user) => user.id === id);
      const user = users[index];
      users.splice(index, 1);
      return user;
    },
    deletePost: (_, { id }) => {
      const index = posts.findIndex((post) => post.id === id);
      const post = posts[index];
      posts.splice(index, 1);
      return post;
    },
  },
  User: {
    posts: (user) => posts.filter((post) => post.authorId === user.id),
  },
  Post: {
    author: (post) => users.find((user) => user.id === post.authorId),
  },
};

async function startServer() {
  const server = new ApolloServer({
    typeDefs,
    resolvers,
  });

  const { url } = await startStandaloneServer(server, {
    listen: { port: 4000 },
  });
  console.log(`🚀  Server ready at: ${url}`);
}

startServer();

5. サーバーを起動

yarn start

6. http://localhost:4000/ にアクセス

  • 以下の画面が表示され、クエリと変数を設定してリクエストを投げることができます。

さいごに

今回は、GraphQLの基本的な概念から、クエリ・ミューテーションの書き方、そしてローカルでのサーバー構築方法までをまとめました。

この記事が、これからGraphQLに触れる方のとっかかりになれば嬉しいです!

最後まで読んでいただきありがとうございました!
ご質問・ご感想などあればぜひコメントで教えてください 🙌

dotD Tech Blog

Discussion