はじめに

GraphQLは仕様であるため、言語に依存しません。 Node.jsを使用したGraphQL開発では、graphql-js express-graphql apollo-serverなどのさまざまなオプションを利用できます。 このチュートリアルでは、ApolloServerを使用してNode.jsでフル機能のGraphQLサーバーをセットアップします。

Apollo Server 2のリリース以来、Apollo Serverを使用したGraphQLサーバーの作成は、それに付属する他の機能は言うまでもなく、より効率的になりました。

このデモンストレーションの目的で、レシピアプリ用の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 を使用して、データベースと対話します。

まず、新しいプロジェクトを作成しましょう。

  1. mkdir graphql-recipe-server

新しいプロジェクトディレクトリに移動します。

  1. cd graphql-recipe-server

新しいプロジェクトを初期化します。

  1. npm init -y

次に、Sequelizeをインストールしましょう。

  1. npm install sequelize sequelize-cli sqlite3

Sequelizeのインストールに加えて、 sqlite3 Node.jsのパッケージ。 プロジェクトの足場を作るために、同様にインストールしているSequelizeCLIを使用します。

CLIを使用してプロジェクトの足場を作りましょう。

  1. node_modules/.bin/sequelize init

これにより、次のフォルダが作成されます。

  • config:データベースへの接続方法をSequelizeに指示する構成ファイルが含まれています。
  • models:プロジェクトのすべてのモデルが含まれ、 index.js すべてのモデルを統合するファイル。
  • migrations:すべての移行ファイルが含まれています。
  • seeders:すべてのシードファイルが含まれます。

このチュートリアルでは、シーダーは使用しません。 開ける config/config.json 次のコンテンツに置き換えます。

config / config.json
{
  "development": {
    "dialect": "sqlite",
    "storage": "./database.sqlite"
  }
}

設定します dialectsqlite を設定します storage SQLiteデータベースファイルを指すようにします。

次に、プロジェクトのルートディレクトリ内にデータベースファイルを直接作成する必要があります。

  1. touch database.sqlite

これで、SQLiteを使用するためのプロジェクトの依存関係がインストールされました。

ステップ2—モデルと移行の作成

データベースのセットアップが邪魔にならないので、プロジェクトのモデルの作成を開始できます。 レシピアプリには2つのモデルがあります。 UserRecipe. これには、SequelizeCLIを使用します。

  1. 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 次のようにフィールド定義を更新します。

移行/XXXXXXXXXXXXXX-create-user.js
name: {
  allowNull: false,
  type: Sequelize.STRING
},
email: {
  allowNull: false,
  type: Sequelize.STRING
},
password: {
  allowNull: false,
  type: Sequelize.STRING
}

次に、同じことを行います User モデル:

models / user.js
name: {
  allowNull: false,
  type: DataTypes.STRING
},
email: {
  allowNull: false,
  type: DataTypes.STRING
},
password: {
  allowNull: false,
  type: DataTypes.STRING
}

次に、を作成しましょう Recipe モデル:

  1. node_modules/.bin/sequelize model:create --name Recipe --attributes title:string,ingredients:string,direction:string

と同じように User モデル、私たちは同じことをします Recipe モデル。 開ける migrations/XXXXXXXXXXXXXX-create-recipe.js 次のようにフィールド定義を更新します。

移行/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 モデルも:

models / recipe.js
title: {
  allowNull: false,
  type: DataTypes.STRING
},
ingredients: {
  allowNull: false,
  type: DataTypes.STRING
},
direction: {
  allowNull: false,
  type: DataTypes.STRING
}

ユーザーモデルとレシピモデルの間の1対多の関係を定義しましょう。

開ける models/user.js を更新します User.associate 以下のように機能します:

models / user.js
User.associate = function(models) {
  // associations can be defined here
  User.hasMany(models.Recipe)
};

また、関係の逆を定義する必要があります Recipe モデル:

models / recipe.js
Recipe.associate = function(models) {
  // associations can be defined here
  Recipe.belongsTo(models.User, { foreignKey: 'userId' });
};

デフォルトでは、Sequelizeは、対応するモデル名のキャメルケース名とその主キーを外部キーとして使用します。 したがって、私たちの場合、外部キーは次のようになります。 UserId. 列に別の名前を付けたため、明示的に定義する必要があります foreignKey 協会に。

これで、移行を実行できます。

  1. node_modules/.bin/sequelize db:migrate

これで、モデルと移行のセットアップが完了しました。

ステップ3—GraphQLサーバーを作成する

前述のように、GraphQLサーバーの構築にはApolloサーバーを使用します。 それで、それをインストールしましょう:

  1. npm install apollo-server graphql bcryptjs

Apolloサーバーには graphql 依存関係として、したがってそれもインストールする必要があります。 また、インストールします bcryptjs、後でユーザーパスワードをハッシュするために使用します。

それらをインストールして、作成します src ディレクトリを作成し、その中に、 index.js ファイルを作成し、それに次のコードを追加します。

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 ファイルを作成し、次のコードを追加します。

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つのタイプを定義します。 UserRecipe、これは私たちのモデルに対応しています。 に User タイプ、フィールドの定義に加えて、 User モデル、私達はまた定義します recipes フィールド。ユーザーのレシピを取得するために使用されます。 と同じ Recipe タイプ; を定義します user フィールド。レシピのユーザーを取得するために使用されます。

次に、3つのクエリを定義します。単一のユーザーをフェッチするため、作成されたすべてのレシピをフェッチするため、および単一のレシピをフェッチするためです。 両方 userrecipe クエリは、ユーザーまたはレシピをそれぞれ返すか、返すことができます null IDに対応する一致が見つからなかった場合。 The allRecipes queryは常にレシピの配列を返しますが、まだレシピが作成されていない場合は空になる可能性があります。

注: ! フィールドが必要であることを示しますが、 [] フィールドがアイテムの配列を返すことを示します。

最後に、新しいユーザーを作成するためのミューテーションと、新しいレシピを作成するためのミューテーションを定義します。 両方のミューテーションは、作成されたユーザーとレシピをそれぞれ返します。

ステップ5—リゾルバーを作成する

リゾルバーは、スキーマ内のフィールドの実行方法を定義します。 言い換えれば、私たちのスキーマはリゾルバーなしでは役に立たないのです。 作成する resolvers.js 内部のファイル src ディレクトリに次のコードを追加します。

src / resolvers.js
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 ファイルの先頭:

src / resolvers.js
const bcrypt = require('bcryptjs');

次に、直後に次のコードを追加します Query 物体:

src / resolvers.js
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とレシピ自体の詳細を受け入れ、それらをデータベースに保持して、新しく作成されたレシピを返します。

リゾルバーで締めくくるために、カスタムフィールドがどのように必要かを定義しましょう(recipesUseruser の上 Recipe)解決する必要があります。 中に次のコードを追加します src/resolvers.js 直後 Mutation 物体:

src / resolvers.js
User: {
  async recipes(user) {
    return user.getRecipes();
  },
},
Recipe: {
  async user(recipe) {
    return recipe.getUser();
  },
},

これらはメソッドを使用します、 getRecipes()getUser()、定義した関係により、Sequelizeによってモデルで使用できるようになります。

ステップ6—GraphQLサーバーをテストする

GraphQLサーバーをテストする時が来ました。 まず、サーバーを次のコマンドで起動する必要があります。

  1. 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で入手できます。