ApolloServerとSequelizeを使用してNode.jsでGraphQLサーバーをセットアップする方法
はじめに
GraphQLは仕様であるため、言語に依存しません。 Node.jsを使用したGraphQL開発では、graphql-js 、 express-graphql 、apollo-serverなどのさまざまなオプションを利用できます。 このチュートリアルでは、ApolloServerを使用してNode.jsでフル機能のGraphQLサーバーをセットアップします。
Apollo Server 2のリリース以来、Apollo Serverを使用したGraphQLサーバーの作成は、それに付属する他の機能は言うまでもなく、より効率的になりました。
このデモンストレーションの目的で、レシピアプリ用のGraphQLサーバーを構築します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsのローカル開発環境。 Node.jsをインストールしてローカル開発環境を作成する方法に従ってください。
- GraphQLの基本的な知識。
このチュートリアルは、Nodev14.4.0で検証されました。 npm
v6.14.5、 apollo-server
v2.15.0、 graphql
v15.1.0、 sequelize
v5.21.13、および sqlite3
v4.2.0。
GraphQLとは何ですか?
GraphQLは、APIの宣言型データフェッチ仕様およびクエリ言語です。 Facebookによって作成されました。 GraphQLは、アンダーフェッチやオーバーフェッチなど、RESTのいくつかの欠点を克服するために作成されたため、RESTの効果的な代替手段です。
RESTとは異なり、GraphQLは1つのエンドポイントを使用します。 これは、エンドポイントに対して1つのリクエストを行い、1つの応答をJSONとして受け取ることを意味します。 このJSON応答には、必要な数のデータを含めることができます。 GraphQLはプロトコルに依存しませんが、RESTと同様に、GraphQLはHTTP経由で操作できます。
典型的なGraphQLサーバーは、スキーマとリゾルバーで構成されています。 スキーマ(またはGraphQLスキーマ)には、GraphQLAPIを構成する型定義が含まれています。 タイプ定義にはフィールドが含まれ、各フィールドには返されると予想されるものが含まれています。 各フィールドは、リゾルバーと呼ばれるGraphQLサーバー上の関数にマップされます。 リゾルバーには、実装ロジックとフィールドの戻りデータが含まれています。 つまり、スキーマには型定義が含まれ、リゾルバーには実際の実装が含まれます。
ステップ1—データベースのセットアップ
まず、データベースを設定します。 データベースにはSQLiteを使用します。 また、Node.jsのORMである Sequelize を使用して、データベースと対話します。
まず、新しいプロジェクトを作成しましょう。
- mkdir graphql-recipe-server
新しいプロジェクトディレクトリに移動します。
- cd graphql-recipe-server
新しいプロジェクトを初期化します。
- npm init -y
次に、Sequelizeをインストールしましょう。
- npm install sequelize sequelize-cli sqlite3
Sequelizeのインストールに加えて、 sqlite3
Node.jsのパッケージ。 プロジェクトの足場を作るために、同様にインストールしているSequelizeCLIを使用します。
CLIを使用してプロジェクトの足場を作りましょう。
- node_modules/.bin/sequelize init
これにより、次のフォルダが作成されます。
config
:データベースへの接続方法をSequelizeに指示する構成ファイルが含まれています。models
:プロジェクトのすべてのモデルが含まれ、index.js
すべてのモデルを統合するファイル。migrations
:すべての移行ファイルが含まれています。seeders
:すべてのシードファイルが含まれます。
このチュートリアルでは、シーダーは使用しません。 開ける config/config.json
次のコンテンツに置き換えます。
{
"development": {
"dialect": "sqlite",
"storage": "./database.sqlite"
}
}
設定します dialect
に sqlite
を設定します storage
SQLiteデータベースファイルを指すようにします。
次に、プロジェクトのルートディレクトリ内にデータベースファイルを直接作成する必要があります。
- touch database.sqlite
これで、SQLiteを使用するためのプロジェクトの依存関係がインストールされました。
ステップ2—モデルと移行の作成
データベースのセットアップが邪魔にならないので、プロジェクトのモデルの作成を開始できます。 レシピアプリには2つのモデルがあります。 User
と Recipe
. これには、SequelizeCLIを使用します。
- node_modules/.bin/sequelize model:create --name User --attributes name:string,email:string,password:string
これは作成されます user.js
内部のファイル models
ディレクトリとそれに対応する移行ファイル migrations
ディレクトリ。
上のフィールドは必要ないので User
モデルをnull許容にするためには、それを明示的に定義する必要があります。 開ける migrations/XXXXXXXXXXXXXX-create-user.js
次のようにフィールド定義を更新します。
name: {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
type: Sequelize.STRING
},
password: {
allowNull: false,
type: Sequelize.STRING
}
次に、同じことを行います User
モデル:
name: {
allowNull: false,
type: DataTypes.STRING
},
email: {
allowNull: false,
type: DataTypes.STRING
},
password: {
allowNull: false,
type: DataTypes.STRING
}
次に、を作成しましょう Recipe
モデル:
- node_modules/.bin/sequelize model:create --name Recipe --attributes title:string,ingredients:string,direction:string
と同じように User
モデル、私たちは同じことをします Recipe
モデル。 開ける migrations/XXXXXXXXXXXXXX-create-recipe.js
次のようにフィールド定義を更新します。
userId: {
allowNull: false,
type: Sequelize.INTEGER.UNSIGNED
},
title: {
allowNull: false,
type: Sequelize.STRING
},
ingredients: {
allowNull: false,
type: Sequelize.STRING
},
direction: {
allowNull: false,
type: Sequelize.STRING
},
追加のフィールドがあることに気付くでしょう: userId
、レシピを作成したユーザーのIDを保持します。 これについてはまもなく詳しく説明します。
を更新します Recipe
モデルも:
title: {
allowNull: false,
type: DataTypes.STRING
},
ingredients: {
allowNull: false,
type: DataTypes.STRING
},
direction: {
allowNull: false,
type: DataTypes.STRING
}
ユーザーモデルとレシピモデルの間の1対多の関係を定義しましょう。
開ける models/user.js
を更新します User.associate
以下のように機能します:
User.associate = function(models) {
// associations can be defined here
User.hasMany(models.Recipe)
};
また、関係の逆を定義する必要があります Recipe
モデル:
Recipe.associate = function(models) {
// associations can be defined here
Recipe.belongsTo(models.User, { foreignKey: 'userId' });
};
デフォルトでは、Sequelizeは、対応するモデル名のキャメルケース名とその主キーを外部キーとして使用します。 したがって、私たちの場合、外部キーは次のようになります。 UserId
. 列に別の名前を付けたため、明示的に定義する必要があります foreignKey
協会に。
これで、移行を実行できます。
- node_modules/.bin/sequelize db:migrate
これで、モデルと移行のセットアップが完了しました。
ステップ3—GraphQLサーバーを作成する
前述のように、GraphQLサーバーの構築にはApolloサーバーを使用します。 それで、それをインストールしましょう:
- npm install apollo-server graphql bcryptjs
Apolloサーバーには graphql
依存関係として、したがってそれもインストールする必要があります。 また、インストールします bcryptjs
、後でユーザーパスワードをハッシュするために使用します。
それらをインストールして、作成します src
ディレクトリを作成し、その中に、 index.js
ファイルを作成し、それに次のコードを追加します。
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
const models = require('../models');
const server = new ApolloServer({
typeDefs,
resolvers,
context: { models },
});
server
.listen()
.then(({ url }) => console.log('Server is running on localhost:4000'));
ここでは、Apollo Serverの新しいインスタンスを作成し、それにスキーマとリゾルバーを渡します(どちらもまもなく作成します)。 また、モデルをコンテキストとしてApolloサーバーに渡します。 これにより、リゾルバーからモデルにアクセスできるようになります。
最後に、サーバーを起動します。
ステップ4—GraphQLスキーマを定義する
GraphQLスキーマは、GraphQLAPIが持つ機能を定義するために使用されます。 GraphQLスキーマはタイプで構成されています。 タイプは、ドメイン固有のエンティティの構造を定義するためのものにすることができます。 ドメイン固有のエンティティの型を定義することに加えて、GraphQL操作の型を定義することもできます。これは、GraphQLAPIが持つ機能に変換されます。 これらの操作は、クエリ、ミューテーション、およびサブスクリプションです。 クエリは、GraphQLサーバーで読み取り操作(データのフェッチ)を実行するために使用されます。 一方、ミューテーションは、GraphQLサーバーで書き込み操作(データの挿入、更新、または削除)を実行するために使用されます。 サブスクリプションは、GraphQLサーバーにリアルタイム機能を追加するために使用されるため、これら2つとは完全に異なります。
このチュートリアルでは、クエリとミューテーションのみに焦点を当てます。
GraphQLスキーマとは何かを理解したので、アプリのスキーマを作成しましょう。 以内 src
ディレクトリ、作成 schema.js
ファイルを作成し、次のコードを追加します。
const { gql } = require('apollo-server');
const typeDefs = gql`
type User {
id: Int!
name: String!
email: String!
recipes: [Recipe!]!
}
type Recipe {
id: Int!
title: String!
ingredients: String!
direction: String!
user: User!
}
type Query {
user(id: Int!): User
allRecipes: [Recipe!]!
recipe(id: Int!): Recipe
}
type Mutation {
createUser(name: String!, email: String!, password: String!): User!
createRecipe(
userId: Int!
title: String!
ingredients: String!
direction: String!
): Recipe!
}
`;
module.exports = typeDefs;
まず、 require
the gql
からのパッケージ apollo-server
. 次に、それを使用してスキーマを定義します。 理想的には、GraphQLスキーマがデータベーススキーマを可能な限りミラーリングすることを望んでいます。 したがって、2つのタイプを定義します。 User
と Recipe
、これは私たちのモデルに対応しています。 に User
タイプ、フィールドの定義に加えて、 User
モデル、私達はまた定義します recipes
フィールド。ユーザーのレシピを取得するために使用されます。 と同じ Recipe
タイプ; を定義します user
フィールド。レシピのユーザーを取得するために使用されます。
次に、3つのクエリを定義します。単一のユーザーをフェッチするため、作成されたすべてのレシピをフェッチするため、および単一のレシピをフェッチするためです。 両方 user
と recipe
クエリは、ユーザーまたはレシピをそれぞれ返すか、返すことができます null
IDに対応する一致が見つからなかった場合。 The allRecipes
queryは常にレシピの配列を返しますが、まだレシピが作成されていない場合は空になる可能性があります。
注: !
フィールドが必要であることを示しますが、 []
フィールドがアイテムの配列を返すことを示します。
最後に、新しいユーザーを作成するためのミューテーションと、新しいレシピを作成するためのミューテーションを定義します。 両方のミューテーションは、作成されたユーザーとレシピをそれぞれ返します。
ステップ5—リゾルバーを作成する
リゾルバーは、スキーマ内のフィールドの実行方法を定義します。 言い換えれば、私たちのスキーマはリゾルバーなしでは役に立たないのです。 作成する resolvers.js
内部のファイル src
ディレクトリに次のコードを追加します。
const resolvers = {
Query: {
async user(root, { id }, { models }) {
return models.User.findById(id);
},
async allRecipes(root, args, { models }) {
return models.Recipe.findAll();
},
async recipe(root, { id }, { models }) {
return models.Recipe.findById(id);
},
},
};
module.exports = resolvers;
注:最新バージョンの sequelize
非推奨になりました findById
と置き換えました findByPk
. 次のようなエラーが発生した場合 models.Recipe.findById is not a function
また models.User.findById is not a function
、このスニペットを更新する必要がある場合があります。
まず、クエリのリゾルバーを作成します。 ここでは、モデルを使用してデータベースに対して必要なクエリを実行し、結果を返します。
まだ中に src/resolvers.js
、インポートしましょう bcryptjs
ファイルの先頭:
const bcrypt = require('bcryptjs');
次に、直後に次のコードを追加します Query
物体:
Mutation: {
async createUser(root, { name, email, password }, { models }) {
return models.User.create({
name,
email,
password: await bcrypt.hash(password, 10),
});
},
async createRecipe(
root,
{ userId, title, ingredients, direction },
{ models }
) {
return models.Recipe.create({ userId, title, ingredients, direction });
},
},
The createUser
ミューテーションは、ユーザーの名前、電子メール、およびパスワードを受け入れ、提供された詳細を使用してデータベースに新しいレコードを作成します。 必ずパスワードを使用してハッシュします bcrypt
データベースに永続化する前にパッケージ化します。 新しく作成されたユーザーを返します。 The createRecipe
ミューテーションは、レシピを作成しているユーザーのIDとレシピ自体の詳細を受け入れ、それらをデータベースに保持して、新しく作成されたレシピを返します。
リゾルバーで締めくくるために、カスタムフィールドがどのように必要かを定義しましょう(recipes
に User
と user
の上 Recipe
)解決する必要があります。 中に次のコードを追加します src/resolvers.js
直後 Mutation
物体:
User: {
async recipes(user) {
return user.getRecipes();
},
},
Recipe: {
async user(recipe) {
return recipe.getUser();
},
},
これらはメソッドを使用します、 getRecipes()
と getUser()
、定義した関係により、Sequelizeによってモデルで使用できるようになります。
ステップ6—GraphQLサーバーをテストする
GraphQLサーバーをテストする時が来ました。 まず、サーバーを次のコマンドで起動する必要があります。
- node src/index.js
これはで実行されます localhost:4000
、それにアクセスすると、GraphQLPlaygroundが実行されていることがわかります。
新しいユーザーを作成してみましょう。
# create a new user
mutation{
createUser(
name: "John Doe",
email: "[email protected]",
password: "password"
)
{
id,
name,
email
}
}
これにより、次の結果が生成されます。
Output{
"data": {
"createUser": {
"id": 1,
"name": "John Doe",
"email": "[email protected]"
}
}
}
新しいレシピを作成して、作成したユーザーに関連付けてみましょう。
# create a new recipe
mutation {
createRecipe(
userId: 1
title: "Salty and Peppery"
ingredients: "Salt, Pepper"
direction: "Add salt, Add pepper"
) {
id
title
ingredients
direction
user {
id
name
email
}
}
}
これにより、次の結果が生成されます。
Output{
"data": {
"createRecipe": {
"id": 1,
"title": "Salty and Peppery",
"ingredients": "Salt, Pepper",
"direction": "Add salt, Add pepper",
"user": {
"id": 1,
"name": "John Doe",
"email": "[email protected]"
}
}
}
}
ここで実行できるその他のクエリには、次のものがあります。 user(id: 1)
, recipe(id: 1)
、 と allRecipes
.
結論
このチュートリアルでは、ApolloServerを使用してNode.jsでGraphQLサーバーを作成する方法を確認しました。 また、Sequelizeを使用してデータベースをGraphQLサーバーと統合する方法も確認しました。
このチュートリアルのコードは、GitHubで入手できます。