如何使用ApolloServer和Sequelize在Node.js中设置GraphQL服务器
简介
GraphQL 是一种规范,因此与语言无关。 使用 Node.js 进行 GraphQL 开发时,有多种可用选项,包括 graphql-js、express-graphql 和 apollo-server。 在本教程中,您将使用 Apollo Server 在 Node.js 中设置一个功能齐全的 GraphQL 服务器。
自 Apollo Server 2 推出以来,使用 Apollo Server 创建 GraphQL 服务器变得更加高效,更不用说它附带的其他功能了。
出于本演示的目的,您将为食谱应用程序构建一个 GraphQL 服务器。
先决条件
要完成本教程,您需要:
- Node.js 的本地开发环境。 关注【X7X】如何安装Node.js并创建本地开发环境【X76X】。
- 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 是一种用于 API 的声明性数据获取规范和查询语言。 它是由 Facebook 创建的。 GraphQL 是 REST 的有效替代品,因为它的创建是为了克服类似 REST 的不足和过度获取的一些缺点。
与 REST 不同,GraphQL 使用一个端点。 这意味着我们向端点发出一个请求,我们将得到一个 JSON 响应。 这个 JSON 响应可以包含我们想要的尽可能少或尽可能多的数据。 与 REST 一样,GraphQL 可以通过 HTTP 运行,尽管 GraphQL 与协议无关。
一个典型的 GraphQL 服务器由 schema 和 resolvers 组成。 模式(或 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,我们还安装了 Node.js 的 sqlite3
包。 为了帮助我们搭建我们的项目,我们将使用我们正在安装的 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" } }
我们将 dialect
设置为 sqlite
并将 storage
设置为指向 SQLite 数据库文件。
接下来,我们需要直接在项目的根目录中创建数据库文件:
touch database.sqlite
现在,您的项目的依赖项已安装以使用 SQLite。
第 2 步 - 创建模型和迁移
完成数据库设置后,我们可以开始为我们的项目创建模型。 我们的食谱应用程序将有两个模型:User
和 Recipe
。 我们将为此使用 Sequelize CLI:
node_modules/.bin/sequelize model:create --name User --attributes name:string,email:string,password:string
这将在 models
目录内创建一个 user.js
文件,并在 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
模型中做同样的事情:
模型/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
函数如下:
模型/user.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
文件并将以下代码添加到其中:
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 模式用于定义 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;
首先,我们 require
来自 apollo-server
的 gql
包。 然后我们用它来定义我们的模式。 理想情况下,我们希望我们的 GraphQL 模式尽可能地反映我们的数据库模式。 所以我们定义了两种类型,User
和Recipe
,对应我们的模型。 在 User
类型上,除了定义我们在 User
模型上的字段外,我们还定义了一个 recipes
字段,用于检索用户的食谱。 与 Recipe
类型相同; 我们定义了一个 user
字段,用于获取配方的用户。
接下来,我们定义了三个查询:获取单个用户、获取所有已创建的食谱和获取单个食谱。 user
和 recipe
查询都可以分别返回用户或配方,如果没有找到与 ID 对应的匹配项,则返回 null
。 allRecipes
查询将始终返回一个配方数组,如果尚未创建任何配方,则该数组可能为空。
注: !
表示必填字段,而[]
表示该字段将返回一个项目数组。
最后,我们定义了用于创建新用户和创建新配方的突变。 两个突变分别返回创建的用户和配方。
第 5 步 - 创建解析器
解析器定义模式中的字段如何执行。 换句话说,如果没有解析器,我们的模式就毫无用处。 在 src
目录下创建一个 resolvers.js
文件,并在其中添加以下代码:
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 }); }, },
createUser
突变接受用户的姓名、电子邮件和密码,并使用提供的详细信息在数据库中创建新记录。 在将密码保存到数据库之前,我们确保使用 bcrypt
包对密码进行哈希处理。 它返回新创建的用户。 createRecipe
突变接受创建配方的用户 ID 以及配方本身的详细信息,将它们保存到数据库中,并返回新创建的配方。
总结一下解析器,让我们定义我们希望如何解析自定义字段(User
上的 recipes
和 Recipe
上的 user
)。 在 Mutation
对象之后的 src/resolvers.js
中添加以下代码:
src/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: "johndoe@example.com", password: "password" ) { id, name, email } }
这将产生以下结果:
Output{ "data": { "createUser": { "id": 1, "name": "John Doe", "email": "johndoe@example.com" } } }
让我们尝试创建一个新配方并将其与创建的用户相关联:
# 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": "johndoe@example.com" } } } }
您可以在此处执行的其他查询包括:user(id: 1)
、recipe(id: 1)
和 allRecipes
。
结论
在本教程中,我们了解了如何使用 Apollo Server 在 Node.js 中创建 GraphQL 服务器。 我们还看到了如何使用 Sequelize 将数据库与 GraphQL 服务器集成。
本教程的 代码可在 GitHub 上找到。