如何使用 Apollo Server 和 Sequelize 在 Node.js 中设置 GraphQL 服务器

介绍

GraphQL是一种规格,因此是语言无知的。当涉及到与 Node.js 的 GraphQL 开发时,有各种选项可用,包括 graphql-js, express-graphqlapollo-server

自从 Apollo Server 2 推出以来,使用 Apollo Server 创建一个 GraphQL 服务器变得更加高效,更不用说它带来的其他功能。

为此演示目的,您将为食谱应用构建一个 GraphQL 服务器。

前提条件

要完成本教程,您将需要:

本教程已通过 Node v14.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 是 Facebook 创建的一种声明性数据采集规格和查询语言. GraphQL 是 REST 的有效替代品,因为它是为了克服一些 REST 类似采集的缺点。

与 REST 不同,GraphQL 使用一个终端点,这意味着我们向终端点发出一个请求,我们将收到一个作为 JSON 的响应,这个 JSON 响应可以包含我们想要的尽可能少或尽可能多的数据。

典型的 GraphQL 服务器由 schemaresolvers 组成. 一个 Schema (或 GraphQL 方案) 包含构成 GraphQL API 的类型定义. 一个类型定义包含字段(s),每个字段都包含其预期返回的内容。

步骤 1 - 创建数据库

我们将开始设置我们的数据库,我们将使用 SQLite 用于我们的数据库,我们还将使用 Sequelize,它是 Node.js 的 ORM,与我们的数据库进行交互。

首先,让我们创建一个新的项目:

1mkdir graphql-recipe-server

导航到新项目目录:

1cd graphql-recipe-server

启动新项目:

1npm init -y

接下来,我们来安装Sequelize:

1npm install sequelize sequelize-cli sqlite3

除了安装Sequelize之外,我们还为Node.js安装了sqlite3包,为了帮助我们实现项目,我们将使用Sequelize CLI,我们也在安装它。

让我们用 CLI 来构建我们的项目:

1node_modules/.bin/sequelize init

这将创建以下文件夹:

●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●

对于本教程的目的,我们不会使用任何种子。打开config/config.json,并用以下内容取代它:

1[label config/config.json]
2{
3  "development": {
4    "dialect": "sqlite",
5    "storage": "./database.sqlite"
6  }
7}

我们将方言设置为sqlite,并将存储设置为指向SQLite数据库文件。

接下来,我们需要直接在项目的根目录中创建数据库文件:

1touch database.sqlite

现在为您的项目安装了依赖性以使用 SQLite。

步骤2:创建模型和迁移

有了数据库设置,我们可以开始为我们的项目创建模型,我们的食谱应用程序将有两个模型:用户和食谱。

1node_modules/.bin/sequelize model:create --name User --attributes name:string,email:string,password:string

这将在模型目录中创建一个user.js文件,在迁移目录中创建一个相应的迁移文件。

由于我们不希望用户模型上的任何字段都可恢复,我们需要明确定义这一点。

 1[label migrations/XXXXXXXXXXXXXX-create-user.js]
 2name: {
 3  allowNull: false,
 4  type: Sequelize.STRING
 5},
 6email: {
 7  allowNull: false,
 8  type: Sequelize.STRING
 9},
10password: {
11  allowNull: false,
12  type: Sequelize.STRING
13}

然后我们将在用户模型中做同样的事情:

 1[label models/user.js]
 2name: {
 3  allowNull: false,
 4  type: DataTypes.STRING
 5},
 6email: {
 7  allowNull: false,
 8  type: DataTypes.STRING
 9},
10password: {
11  allowNull: false,
12  type: DataTypes.STRING
13}

接下来,让我们创建食谱模型:

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

用户模型一样,我们将对食谱模型做同样的事情,打开迁移/XXXXXXXXXXXX-create-recipe.js并如下更新字段定义:

 1[label migrations/XXXXXXXXXXXXXX-create-recipe.js]
 2userId: {
 3  allowNull: false,
 4  type: Sequelize.INTEGER.UNSIGNED
 5},
 6title: {
 7  allowNull: false,
 8  type: Sequelize.STRING
 9},
10ingredients: {
11  allowNull: false,
12  type: Sequelize.STRING
13},
14direction: {
15  allowNull: false,
16  type: Sequelize.STRING
17},

你会注意到我们有一个额外的字段: userId,这将包含创建食谱的用户的ID。

也更新了食谱模型:

 1[label models/recipe.js]
 2title: {
 3  allowNull: false,
 4  type: DataTypes.STRING
 5},
 6ingredients: {
 7  allowNull: false,
 8  type: DataTypes.STRING
 9},
10direction: {
11  allowNull: false,
12  type: DataTypes.STRING
13}

让我们来定义用户和食谱模型之间的一对多关系。

打开models/user.js并如下更新User.associate函数:

1[label models/user.js]
2User.associate = function(models) {
3  // associations can be defined here
4  User.hasMany(models.Recipe)
5};

我们还需要在食谱模型上定义相反的关系:

1[label models/recipe.js]
2Recipe.associate = function(models) {
3  // associations can be defined here
4  Recipe.belongsTo(models.User, { foreignKey: 'userId' });
5};

默认情况下,Sequelize 将从相应的模型名称中使用一个 camelcase 名称和其主要密钥作为外部密钥,所以在我们的情况下,它将期望外部密钥为UserId

现在,我们可以管理移民:

1node_modules/.bin/sequelize db:migrate

现在,您的模型和迁移的设置已经完成。

步骤 3 – 创建 GraphQL 服务器

如前所述,我们将使用阿波罗服务器来构建我们的GraphQL服务器,因此,让我们安装它:

1npm install apollo-server graphql bcryptjs

阿波罗服务器需要graphql作为依赖,因此需要安装它,我们也安装了bcryptjs,我们将在以后使用它来哈希用户密码。

使用已安装的,创建一个src目录,然后在其内部创建一个index.js文件,并添加以下代码:

 1[label src/index.js]
 2const { ApolloServer } = require('apollo-server');
 3const typeDefs = require('./schema');
 4const resolvers = require('./resolvers');
 5const models = require('../models');
 6
 7const server = new ApolloServer({
 8  typeDefs,
 9  resolvers,
10  context: { models },
11});
12
13server
14  .listen()
15  .then(({ url }) => console.log('Server is running on localhost:4000'));

在这里,我们创建了一个新的阿波罗服务器实例,将我们的方案和解析器传递给它(我们将很快创建它们)。

最后,我们开始服务器。

步骤 4 – 定义 GraphQL 方案

GraphQL 架构被用来定义 GraphQL API 的功能性。 GraphQL 架构由类型组成。 一个类型可以用于定义我们域特定的实体的结构。 除了定义我们域特定的实体的类型外,我们还可以定义 GraphQL 操作的类型,这反过来会转化为 GraphQL API 的功能性。 这些操作是查询、突变和订阅。 查询用于在 GraphQL 服务器上执行读取操作(数据捕捉)。

我们将在本教程中专注于查询和突变。

现在我们已经了解了什么是GraphQL方案,让我们为我们的应用程序创建方案,在src目录中创建一个schema.js文件,并添加以下代码:

 1[label src/schema.js]
 2const { gql } = require('apollo-server');
 3
 4const typeDefs = gql`
 5  type User {
 6    id: Int!
 7    name: String!
 8    email: String!
 9    recipes: [Recipe!]!
10  }
11
12  type Recipe {
13    id: Int!
14    title: String!
15    ingredients: String!
16    direction: String!
17    user: User!
18  }
19
20  type Query {
21    user(id: Int!): User
22    allRecipes: [Recipe!]!
23    recipe(id: Int!): Recipe
24  }
25
26  type Mutation {
27    createUser(name: String!, email: String!, password: String!): User!
28    createRecipe(
29      userId: Int!
30      title: String!
31      ingredients: String!
32      direction: String!
33    ): Recipe!
34  }
35`;
36
37module.exports = typeDefs;

首先,我们从apollo-server要求``gql包。然后我们使用它来定义我们的方案。理想情况下,我们希望我们的GraphQL方案尽可能地反映我们的数据库方案。因此,我们定义了两个类型,即用户食谱,这对应于我们的模型。在用户类型上,除了定义我们在用户模型上所拥有的字段之外,我们还定义了食谱字段,用来检索用户的食谱。

接下来,我们定义了三个查询:用于检索单个用户,用于检索所有已创建的食谱,以及分别检索单个食谱. 两个用户食谱查询可以分别返回用户或食谱,或者返回如果没有找到对应的匹配ID。

<$>[注] 注: ! 表示需要一个字段,而 [] 表示该字段将返回一组项目。

最后,我们定义了创建新用户以及创建新食谱的突变,这两种突变分别返回了创建的用户和食谱。

步骤五:创建解决方案

Resolvers 定义了图表中的字段是如何执行的,换句话说,如果没有 Resolvers,我们的图表是无用的,然后在src目录中创建一个resolvers.js文件,并添加以下代码:

 1[label src/resolvers.js]
 2const resolvers = {
 3  Query: {
 4    async user(root, { id }, { models }) {
 5      return models.User.findById(id);
 6    },
 7    async allRecipes(root, args, { models }) {
 8      return models.Recipe.findAll();
 9    },
10    async recipe(root, { id }, { models }) {
11      return models.Recipe.findById(id);
12    },
13  },
14};
15
16module.exports = resolvers;

<$>[注] **注:**现代版本的sequelize已取消了findById并用findByPk取代。

在这里,我们使用模型在数据库中执行必要的查询并返回结果。

src/resolvers.js里面,让我们在文件顶部导入bcryptjs:

1[label src/resolvers.js]
2const bcrypt = require('bcryptjs');

然后在Query对象后立即添加以下代码:

 1[label src/resolvers.js]
 2Mutation: {
 3  async createUser(root, { name, email, password }, { models }) {
 4    return models.User.create({
 5      name,
 6      email,
 7      password: await bcrypt.hash(password, 10),
 8    });
 9  },
10  async createRecipe(
11    root,
12    { userId, title, ingredients, direction },
13    { models }
14  ) {
15    return models.Recipe.create({ userId, title, ingredients, direction });
16  },
17},

CreateUser突变接受用户的姓名、电子邮件和密码,并在数据库中创建一个新的记录,并使用所提供的详细信息。我们确保使用bcrypt套件对该密码进行哈希化,然后将其保留在数据库中,然后返回新创建的用户。

为了将解决方案包裹起来,让我们定义我们希望我们的自定义字段(在用户上的食谱食谱上的用户)如何被解决。

 1[label src/resolvers.js]
 2User: {
 3  async recipes(user) {
 4    return user.getRecipes();
 5  },
 6},
 7Recipe: {
 8  async user(recipe) {
 9    return recipe.getUser();
10  },
11},

这些方法使用getRecipes()getUser(),由于我们定义的关系,由Sequelize在我们的模型上提供。

步骤 6 – 测试我们的 GraphQL 服务器

是时候测试我们的GraphQL服务器了,首先,我们需要启动服务器:

1node src/index.js

这将运行在localhost:4000上,如果我们访问它,我们将看到GraphQL Playground运行。

让我们尝试创建一个新的用户:

 1# create a new user
 2
 3mutation{
 4  createUser(
 5    name: "John Doe",
 6    email: "[email protected]",
 7    password: "password"
 8  )
 9  {
10    id,
11    name,
12    email
13  }
14}

这将产生以下结果:

 1[secondary_label Output]
 2{
 3  "data": {
 4    "createUser": {
 5      "id": 1,
 6      "name": "John Doe",
 7      "email": "[email protected]"
 8    }
 9  }
10}

让我们尝试创建一个新的食谱,并将其与创建的用户相关联:

 1# create a new recipe
 2
 3mutation {
 4  createRecipe(
 5    userId: 1
 6    title: "Salty and Peppery"
 7    ingredients: "Salt, Pepper"
 8    direction: "Add salt, Add pepper"
 9  ) {
10    id
11    title
12    ingredients
13    direction
14    user {
15      id
16      name
17      email
18    }
19  }
20}

这将产生以下结果:

 1[secondary_label Output]
 2{
 3  "data": {
 4    "createRecipe": {
 5      "id": 1,
 6      "title": "Salty and Peppery",
 7      "ingredients": "Salt, Pepper",
 8      "direction": "Add salt, Add pepper",
 9      "user": {
10        "id": 1,
11        "name": "John Doe",
12        "email": "[email protected]"
13      }
14    }
15  }
16}

您可以在这里执行的其他查询包括: user(id: 1), recipe(id: 1)allRecipes

结论

在本教程中,我们研究了如何在 Node.js 中使用 Apollo Server 创建 GraphQL 服务器,我们还看到如何使用 Sequelize 将数据库与 GraphQL 服务器集成。

此教程的代码在GitHub上可用(https://github.com/do-community/graphql-recipe-server)。

Published At
Categories with 技术
Tagged with
comments powered by Disqus