GraphQLは、近年API開発において人気を博しています。アプリケーションからデータを公開するのには、RESTful APIが使われるのが一般的ですが、さまざまな制限があるという難点も。そんな問題を解決すべく生まれたのがGraphQLです。

GraphQLは、Facebook(現Meta)が開発したクエリ言語で、2015年からオープンソースプロジェクトになっています。APIデータの記述、アクセスに使える直感的かつ柔軟な構文が特徴です。

この記事では、GraphQLとNode.jsのプロジェクトを構築する方法をご紹介します。また、例としてNode.jsのウェブフレームワーク「Express.jsk」でGraphQLを使用し、ToDoアプリケーションを構築していきます。

GraphQLとは

公式ドキュメントでは、次のように定義されています。「GraphQLは、APIのクエリ言語であり、既存のデータでクエリを実行するためのランタイムです。API内のデータの分かりやすい記述を実現し、必要なものだけをリクエストすることが可能。段階的なAPI拡張も簡単にできる、強力な開発者ツールです」(英語原文の日本語訳)

GraphQLは、データに対し定義した型システムを使ってクエリを実行するサーバー側のランタイムです。また、特定のデータベースやストレージエンジンに縛られることはなく、既存のコードとデータストアに裏打ちされています。GraphQLとRESTful APIの詳しい技術の比較はこちらをご覧ください。

GraphQLサービスを構築するには、まずスキーマタイプを定義し、そのタイプを使ったフィールドを作成します。その後、クライアント側からのデータのリクエスト受け付け時に各フィールドとタイプに対し実行されるリゾルバを用意します。

GraphQLの用語

型は、問い合わせ可能なデータと操作可能なデータを記述するために使用する、GraphQLの中核となる要素です。この章では、データのさまざまな記述・操作方法をご紹介します。

オブジェクト型

GraphQLのオブジェクト型は、強く型付けされたフィールドを含むデータモデルです。モデルとGraphQL型の間には、1対1の紐付けが必要になります。以下は、型の一例です。

type User {
  id: ID! # "!"は必須を意味する
  firstname: String
  lastname: String
  email: String
  username: String
  todos: [Todo] # Todoもまた別のGraphQL型
}

クエリ

GraphQLのクエリは、クライアントがGraphQL API上で実行できるすべてのクエリを定義するものです。既存のクエリをすべて含むRootQueryの定義が必要です。

以下は、クエリ定義と対応するRESTful APIの例です。

type RootQuery {
  user(id: ID): User           # GET /api/users/:idに相当
  users: [User]                # GET /api/usersに相当
  todo(id: ID!): Todo    # GET /api/todos/:id相当
  todos: [Todo]          # GET /api/todosに相当
}

変更

GraphQLのクエリがGETリクエストであれば、変更は、GraphQL APIを操作するPOSTPUTPATCHDELETEリクエストのようなものです。

以下、例としてすべての変更を1つのRootMutationにまとめてみます。

type RootMutation {
  createUser(input: UserInput!): User             # Corresponds to POST /api/users
  updateUser(id: ID!, input: UserInput!): User    # Corresponds to PATCH /api/users
  removeUser(id: ID!): User                       # Corresponds to DELETE /api/users
  createTodo(input: TodoInput!): Todo
  updateTodo(id: ID!, input: TodoInput!): Todo
  removeTodo(id: ID!): Todo
}

ご覧の通り、上記では、UserInputTodoInputのような、変更に-input(入力型)が使用されています。リソースの作成と更新に対して、常に入力型を定義するのがベストプラクティスです。

入力型は、以下のように定義することができます。

input UserInput {
  firstname: String!
  lastname: String
  email: String!
  username: String!
}

リゾルバ

リゾルバは、クエリや変更が要求された際に、何を実行するかをGraphQLに指示する役割を担います。CRUD(作成、読み出し、更新、削除)操作を行うためにデータベースを叩いたり、内部のRESTful APIエンドポイントを叩いたり、クライアントの要求に応えるためにマイクロサービスを呼び出したりなど、重たい作業を行う基本機能です。

resolvers.jsファイルを新規作成し、以下のコードを貼り付けます。

import sequelize from '../models';
export default function resolvers () {
  const models = sequelize.models;
  return {
    // クエリのリゾルバ
    RootQuery: {
      user (root, { id }, context) {
        return models.User.findById(id, context);
      },
      users (root, args, context) {
        return models.User.findAll({}, context);
      }
    },
    User: {
      todos (user) {
        return user.getTodos();
      }
    },
  }
  // 変更のリゾルバ
  RootMutation: {
    createUser (root, { input }, context) {
      return models.User.create(input, context);    
    },
    updateUser (root, { id, input }, context) {
      return models.User.update(input, { ...context, where: { id } });
    },
    removeUser (root, { id }, context) {
      return models.User.destroy(input, { ...context, where: { id } });
    },
    // Todos用リゾルバをここに記述
  }
}

スキーマ

GraphQLのスキーマは、APIの仕様を表すものです。型、クエリ、変更をこのスキーマに含むかたちで公開します。

以下は、型、クエリ、変更を公開するスクリプト例です。

schema {
  query: RootQuery
  mutation: RootMutation
}

上のスキーマは、先ほど作成したRootQueryRootMutationを公開するようになっています。

Node.jsやExpress.jsとの連携方法

GraphQLは、主要プログラミング言語や環境をサポートしており、Node.jsにも対応しています。また、GraphQLの公式サイトでは、JavaScriptを含む様々な言語の実装方法が紹介されており、コード記述も簡単です。

GraphQL Apolloを利用すれば、Node.jsとExpress.jsを組み合わせ、簡単にGraphQLを使い始めることができます。

次の章では、GraphQL Apolloを使用して、Node.jsとExpress.jsのバックエンドフレームワークで、GraphQLアプリを開発する方法をご紹介します。

Express.jsでGraphQLをセットアップする方法

Express.jsを使用したGraphQL APIサーバーの構築は、比較的容易です。まずは、GraphQLサーバーを構築する方法を見ていきましょう。

Expressでのプロジェクトの初期化

まずは、Express.jsプロジェクトをインストールし、セットアップします。プロジェクト用のフォルダを作成し、以下のコマンドを実行してください。

cd <project-name> && npm init -y
npm install express

上のコマンドで、package.jsonファイルが新規作成され、Express.jsのライブラリがプロジェクトにインストールされます。

次に、下のスクリーンショットのように、プロジェクトを構成します。ユーザー、ToDo など、プロジェクトの機能ごとにモジュールが格納されます。

graphql-todo用ファイル
graphql-todoのファイル構造

GraphQLの初期化

まずは、GraphQLとExpress.jsの依存関係をインストールします。以下のコマンドを実行してください。

npm install apollo-server-express graphql @graphql-tools/schema --save

スキーマと型の作成

次に、modulesフォルダ内にindex.jsファイルを作成し、以下のコードを貼り付けます。

const { gql } = require('apollo-server-express');
const users = require('./users');
const todos = require('./todos');
const { GraphQLScalarType } = require('graphql');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const typeDefs = gql`
  scalar Time
  type Query {
    getVersion: String!
  }
  type Mutation {
    version: String!
  }
`;
const timeScalar = new GraphQLScalarType({
  name: 'Time',
  description: 'Time custom scalar type',
 serialize: (value) => value,
});
const resolvers = {
  Time: timeScalar,
  Query: {
    getVersion: () => `v1`,
  },
};
const schema = makeExecutableSchema({
  typeDefs: [typeDefs, users.typeDefs, todos.typeDefs],
  resolvers: [resolvers, users.resolvers, todos.resolvers],
});
module.exports = schema;

コードの解説

ここで、上記コードを詳しく見てみましょう。

ステップ1

必要なライブラリをインポートし、デフォルトのクエリと変更の型を作成します。現時点では、クエリと変更は、GraphQL APIのバージョンを設定するだけに留めますが、後ほど他のスキーマを使い拡張していきます。

GraphQLと拡張機能をインポート
GraphQLと拡張機能をインポート
ステップ2

次に、時間に対するスカラー型と、先ほど作成したクエリと変更用のリゾルバを作ります。さらに、makeExecutableSchema関数を使ってスキーマも追加します。

生成したスキーマには、インポートした他のスキーマがすべて含まれており、作成・インポート時には、さらにスキーマが追加されます。

時間用のスカラー型とリゾルバを作成
時間のスカラー型とリゾルバを作成

上記では、makeExecutableSchema関数に様々なスキーマがインポートされていることがわかります。これは、アプリケーションを複雑に構造化するのに便利です。次に、インポートしたToDoスキーマとUserスキーマを作成します。

ToDoスキーマの作成

ToDoスキーマは、アプリケーションのユーザーが実行できる簡単なCRUD操作を扱います。ToDoのCRUD操作を実装したスキーマは、以下の通りです。

const { gql } = require('apollo-server-express');
const createTodo = require('./mutations/create-todo');
const updateTodo = require('./mutations/update-todo');
const removeTodo = require('./mutations/delete-todo');
const todo = require('./queries/todo');
const todos = require('./queries/todos');
const typeDefs = gql`
  type Todo {
    id: ID!
    title: String
    description: String
    user: User
  }
  input CreateTodoInput {
    title: String!
    description: String
    isCompleted: Boolean
  }
  input UpdateTodoInput {
    title: String
    description: String
    isCompleted: Boolean
  }  extend type Query {
    todo(id: ID): Todo!
    todos: [Todo!]
  }
  extend type Mutation {
    createTodo(input: CreateTodoInput!): Todo
    updateTodo(id: ID!, input: UpdateTodoInput!): Todo
    removeTodo(id: ID!): Todo
  }
`;
// スキーマフィールドのリゾルバ関数を指定
const resolvers = {
  // クエリのリゾルバ
  Query: {
    todo,
    todos,
  },
  // 変更のリゾルバ
  Mutation: {
    createTodo,
    updateTodo,
    removeTodo,
  },
};
module.exports = { typeDefs, resolvers };

コードの解説

では、このコードも詳しく見ていきましょう。

ステップ1

まず、GraphQLのtypeinputextendを使用してToDoスキーマを作成します。extendキーワードは、先ほどつくったルートクエリと変更を継承し、新規クエリと変更を追加するのに使用しています。

ToDo用スキーマを作成
ToDo用スキーマを作成
ステップ2

次に、リゾルバを実装します。これは、特定のクエリや変更が呼び出された際に、正しいデータを取得するのに使用します。

リゾルバを実装
リゾルバを実装する

リゾルバが用意できたら、create-todo.jsの例に見られるように、ビジネスロジックやデータベース操作のメソッドを作成することができます。

./mutations/フォルダにcreate-user.jsファイルを作成して、データベースに新規ToDoを作成するビジネスロジックを追加しましょう。

const models = require('../../../models');
module.exports = async (root, { input }, context) => {
  return models.todos.push({ ...input });
};

上記は、Sequelize ORMを使ってデータベースにToDoを作成する簡単なスクリプトです。Sequelizeの詳細とNode.jsでのセットアップ方法についてはこちらをご覧ください。

アプリケーションに応じて、同じ手順で複数のスキーマを作成したり、GitHubからプロジェクト全体を複製したりすることも可能です。

次に、Express.jsでサーバーをセットアップし、GraphQLとNode.jsを使って作成したToDoアプリケーションを実行してみましょう。

サーバーのセットアップと実行

先ほどインストールしたapollo-server-expressライブラリを使ってサーバーをセットアップし、設定を行います。

apollo-server-expressは、Apollo Server(Express.js)の簡易ラッパーで、Express.jsの開発を考慮し構築されています。

上でご紹介した例を使って、インストールしたapollo-server-expressで動作するようにExpress.jsサーバーを設定します。

ルートディレクトリにserver.jsファイルを作成し、以下のコードを貼り付けます。


const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const schema = require('./modules');
const app = express();
async function startServer() {
  const server = new ApolloServer({ schema });
  await server.start();
  server.applyMiddleware({ app });
}
startServer();
app.listen({ port: 3000 }, () =>
  console.log(`Server ready at http://localhost:3000`)
);

これで、ToDoとUser用のCRUD GraphQLサーバーを作成できました。開発用サーバーを起動し、http://localhost:3000/graphqlからアクセスしてください。適切に操作が行えていれば、以下のような画面が表示されるはずです。

検証画面
検証画面

まとめ

GraphQLは、Facebookが開発した最新技術。RESTfulのアーキテクチャパターンで、大規模なAPIを作成するのに必要になる複雑な作業を簡略化することができます。

今回の記事では、GraphQLについて詳しくご説明し、Express.jsを使ってGraphQL APIを作成する方法をご紹介しました。

GraphQLを使って何か開発されたご経験はありますか?以下のコメント欄でぜひお聞かせください。

Solomon Eseme

ベストプラクティスと業界標準に従って、高性能で革新的な製品の構築を行うソフトウェアエンジニア、コンテンツクリエイター。Masteringbackend.comで、自身の仕事に関連するトピックについて執筆も行う。詳しい仕事情報は、X、LinkedIn、About Meで公開中。