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を操作するPOST
、PUT
、PATCH
、DELETE
リクエストのようなものです。
以下、例としてすべての変更を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
}
ご覧の通り、上記では、UserInput
やTodoInput
のような、変更に-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
}
上のスキーマは、先ほど作成したRootQuery
とRootMutation
を公開するようになっています。
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の初期化
まずは、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のバージョンを設定するだけに留めますが、後ほど他のスキーマを使い拡張していきます。
ステップ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のtype
、input
、extend
を使用してToDoスキーマを作成します。extend
キーワードは、先ほどつくったルートクエリと変更を継承し、新規クエリと変更を追加するのに使用しています。
ステップ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を使って何か開発されたご経験はありますか?以下のコメント欄でぜひお聞かせください。
コメントを残す