介绍
GraphQL是一种规格,因此是语言无知的。当涉及到与 Node.js 的 GraphQL 开发时,有各种选项可用,包括 graphql-js
, express-graphql
和 apollo-server
。
自从 Apollo Server 2 推出以来,使用 Apollo Server 创建一个 GraphQL 服务器变得更加高效,更不用说它带来的其他功能。
为此演示目的,您将为食谱应用构建一个 GraphQL 服务器。
前提条件
要完成本教程,您将需要:
- Node.js 的本地开发环境 遵循 如何安装 Node.js 并创建本地开发环境。
- 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 服务器由 schema 和 resolvers 组成. 一个 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)。