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

介绍

GraphQL 是一种规范,因此与语言无关。当谈到GraphQL发展与Node.js的,也有提供各种方案,包括graphql-jsexpress-graphql,和apollo-server在本教程中,您将使用 Apollo Server 在 Node.js 中设置一个功能齐全的 GraphQL 服务器。

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

出于本演示的目的,您将为菜谱应用构建一个 GraphQL 服务器。

先决条件

要完成本教程,您需要:

本教程已通过 Node v14.4.0、npmv6.14.5、apollo-serverv2.15.0、graphqlv15.1.0、sequelizev5.21.13 和sqlite3v4.2.0 验证。

什么是 GraphQL?

GraphQL 是 API 的声明性数据获取规范和查询语言。它是由 Facebook 创建的。GraphQL 是 REST 的有效替代方案,因为它的创建是为了克服类似 REST 的欠取和过取的一些缺点。

与 REST 不同,GraphQL 使用一个端点。这意味着我们向端点发出一个请求,我们将得到一个 JSON 形式的响应。这个 JSON 响应可以包含我们想要的尽可能少或尽可能多的数据。与 REST 一样,GraphQL 可以通过 HTTP 进行操作,尽管 GraphQL 与协议无关。

典型的 GraphQL 服务器由架构解析器组成模式(或 GraphQL 模式)包含构成 GraphQL API 的类型定义。类型定义包含字段,每个字段都包含预期返回的内容。每个字段都映射到 GraphQL 服务器上称为解析器的函数。解析器包含实现逻辑并返回字段的数据。换句话说,模式包含类型定义,而解析器包含实际实现。

步骤 1 — 设置数据库

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

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

  • mkdir graphql-recipe-server

导航到新的项目目录:

  • cd graphql-recipe-server

初始化一个新项目:

  • npm init -y

接下来,让我们安装 Sequelize:

  • npm install sequelize sequelize-cli sqlite3

除了安装 Sequelize,我们还安装了sqlite3Node.js包。为了帮助我们搭建我们的项目,我们将使用 Sequelize CLI,我们也正在安装它。

让我们用 CLI 搭建我们的项目:

  • node_modules/.bin/sequelize init

这将创建以下文件夹:

  • config: 包含一个配置文件,它告诉 Sequelize 如何连接我们的数据库。
  • models: 包含我们项目的所有模型,还包含一个index.js将所有模型集成在一起文件。
  • migrations: 包含所有迁移文件。
  • seeders: 包含所有种子文件。

就本教程而言,我们不会使用任何播种机。打开config/config.json并替换为以下内容:

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

我们将dialecttosqliteto设置storage为指向一个 SQLite 数据库文件。

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

  • touch database.sqlite

现在,您的项目的依赖项已安装以使用 SQLite。

第 2 步 – 创建模型和迁移

完成数据库设置后,我们可以开始为我们的项目创建模型。我们的食谱应用程序将有两个模型:UserRecipe我们将为此使用 Sequelize CLI:

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

这将user.jsmodels目录中创建一个文件并在目录中创建一个相应的迁移文件migrations

由于我们不希望User模型上的任何字段都可以为空,因此我们需要明确定义它。打开并更新字段定义,如下所示: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模型中做同样的事情

模型/用户.js
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

迁移/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模型:

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

让我们定义用户和配方模型之间的一对多关系。

打开models/user.js并更新User.associate功能如下:

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

我们还需要在Recipe模型上定义关系的逆

模型/recipe.js
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 服务器

如前所述,我们将使用 Apollo Server 来构建我们的 GraphQL 服务器。所以,让我们安装它:

  • npm install apollo-server graphql bcryptjs

Apollo Server 需要graphql作为依赖项,因此也需要安装它。此外,我们安装bcryptjs,稍后我们将使用它来散列用户密码。

安装后,创建一个src目录,然后在其中创建一个index.js文件并向其中添加以下代码:

源代码/索引.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 Server。这将使我们能够从我们的解析器访问模型。

最后,我们启动服务器。

第 4 步 – 定义 GraphQL 模式

GraphQL 模式用于定义 GraphQL API 将具有的功能。GraphQL 模式由类型组成。类型可以用于定义我们特定于域的实体的结构。除了为特定领域的实体定义类型之外,我们还可以为 GraphQL 操作定义类型,这反过来又会转化为 GraphQL API 将具有的功能。这些操作是查询、突变和订阅。查询用于在 GraphQL 服务器上执行读取操作(获取数据)。另一方面,变异用于在 GraphQL 服务器上执行写入操作(插入、更新或删除数据)。订阅与这两者完全不同,因为它们用于向 GraphQL 服务器添加实时功能。

在本教程中,我们将只关注查询和突变。

现在我们了解了 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;

首先,我们requiregql包从apollo-server然后我们用它来定义我们的模式。理想情况下,我们希望我们的 GraphQL 模式尽可能地反映我们的数据库模式。所以我们定义了两种类型,UserRecipe,它们对应于我们的模型。User类型上,除了定义我们在User模型上的字段外,我们还定义了一个recipes字段,用于检索用户的食谱。Recipe类型相同我们定义了一个user字段,用于获取菜谱的用户。

接下来,我们定义三个查询:获取单个用户、获取已创建的所有配方以及分别获取单个配方。无论是userrecipe查询可以返回用户或配方分别或返回null,如果没有相应的匹配的结果为ID。allRecipes查询将总是返回食谱的数组,如果没有配方,如尚未创建,这可能是空的。

注意:!需要字段表示,而[]表示字段将返回的项目的阵列。

最后,我们定义了用于创建新用户和创建新配方的突变。两个突变分别返回创建的用户和配方。

第 5 步 – 创建解析器

解析器定义如何执行模式中的字段。换句话说,如果没有解析器,我们的模式就毫无用处。resolvers.jssrc目录中创建一个文件并在其中添加以下代码:

源代码/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在文件顶部导入

源代码/resolvers.js
const bcrypt = require('bcryptjs');

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

源代码/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 });
  },
},

createUser突变接受的姓名,电子邮件和用户的密码,并创建与所提供的详细信息数据库的新纪录。在将密码bcrypt持久化到数据库之前,我们确保使用该对密码进行哈希处理它返回新创建的用户。createRecipe突变接受是创造的配方以及用于配方本身细节的用户的ID,他们坚持到数据库中,并返回新创建的配方。

为了结束解析器,让我们定义我们希望如何解析我们的自定义字段(recipesonUseruseron Recipe)。src/resolvers.jsMutation对象之后立即添加以下代码

源代码/resolvers.js
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运行,如果我们访问它,我们将看到 GraphQL Playground 正在运行。

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

# 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

结论

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

本教程代码可在 GitHub 上找到

觉得文章有用?

点个广告表达一下你的爱意吧 !😁