GraphQL的完整初学者指南

540 阅读17分钟

2015年GraphQL推出后,我第一次在一个项目上使用它,说实话,我不明白为什么我们要使用它。多年来,我越来越喜欢GraphQL--你可以用AWS AppSync和Hasura等管理服务快速创建API,而且它减少了前端和后端开发之间的摩擦。在这篇文章中,我们将讨论什么是GraphQL,为什么要使用它,以及什么时候它可能不是最好的选择,然后使用GraphQL API创建一个完整的配方应用程序。

请注意,我是AWS Amplify团队的开发者倡导者,如果你对它有任何反馈或问题,请联系我或在我们的discord上询问 - discord.gg/amplify!

如果你是API的新手,我建议你先阅读这篇关于API的博文!如果你对REST的概念感到陌生,我也建议你先阅读这篇文章;这篇文章经常会将GraphQL与REST进行比较。我还会在前端的某些部分使用React-- 我建议在这篇文章之前先浏览一下这方面的教程。

另外,我们在这篇文章中会用到一些词汇。

  • 模式:这是对数据结构的一种表示。
  • 字段:这些是与一块数据相关的属性。

什么是GraphQL?

根据其文档,"GraphQL是你的API的查询语言,以及使用你为你的数据定义的类型系统执行查询的服务器端运行时间。"GraphQL本身是一种规范,这意味着有一份文件概述了GraphQL查询的样子,以及客户端与服务器之间的互动如何进行;然而,它可以与任何编程语言或数据层一起用于你的应用程序。

在实践中,这允许前端开发人员发送查询,要求他们需要的数据 - 包括嵌套数据 - 到后端。这允许后端开发人员创建一个端点,而不是REST API所需的许多端点。你可以将改变数据的突变和检索数据的查询全部发送到一个地方。

为什么使用GraphQL?

GraphQL流行的原因有很多。首先,它简化了前端和后端开发人员之间的沟通,降低了难度 -- 前端开发人员不需要在他们的需求发生变化时要求一个新的端点,而只需要更新他们的GraphQL查询。如果你有多个前端需要相同的后端数据,这就更有帮助。前端开发者可以准确地获得他们所需要的数据 -- 没有少取或多取的字段或项目。

由于前台开发者可以使用一个查询来请求嵌套数据,网络请求也被最小化了 -- 例如,如果你查询一个博客文章,你也可以在这个查询中获得该文章的评论,而不是做第二个请求来获得它们。这也可以减少所需的前端代码量,使代码更容易理解。

GraphQL还执行了一个类型化的数据模式,所以每个项目的字段都必须符合这些类型。这使得数据更加一致和易于管理 -- 而不是在博客文章中循环,找出每个标题是字符串还是布尔值,GraphQL将强制要求每个标题是一个字符串。

什么时候GraphQL不那么好?

与软件工程中的任何事情一样,使用GraphQL也有缺点。首先,我在2015年左右开始使用GraphQL时,就已经开始使用了,而且我很讨厌它。我是一个小团队的全栈工程师,构建后端是更多的工作,前端需要更多的言语。GraphQL查询往往很长,而对于许多REST API,你只需提供一个url即可。此外,与REST相比,许多后端框架和语言对GraphQL API的支持远没有那么成熟。你可能需要做更多的工作,并通过一个不太常用的库来获得你的GraphQL Api。如果你是创建端点和消费端点的人,建立REST API可能更快--特别是如果你使用的编程语言或框架对GraphQL的支持不太成熟。

GraphQL在大型团队中大放异彩,其中前端团队正在开发客户端,而另一个团队正在开发服务器。此外,已经有越来越多的管理型GraphQL服务,如Hasura和AWS AppSync。这些服务允许你使用他们的服务生成一个GraphQL后端,然后在前端消费它--与从头开始编写GraphQL服务器相比,这通常会大大加快后端开发的速度。

最后,许多开发人员在其职业生涯的早期就被教导如何使用和创建REST API,而对GraphQL的机构知识可能较少。让一个完整的团队加速发展可能是你需要考虑的投资。

创建一个GraphQL API

现在是有趣的部分,让我们写一些代码吧我们将使用AWS Amplify来创建一个GraphQL后端 -- 这将加快进程,并允许我们只关注GraphQL而不是后端开发的其他部分。

首先,我将创建一个React应用程序 -- 这里没有太多的React代码,但设置将比用捆绑器创建一个Vanilla JS应用程序更快。

在你的终端,运行。

npx create-react-app graphql-playground
cd graphql-playground

注意:你需要安装Node来完成这个步骤。

接下来,我们将在我们的项目中初始化Amplify。

amplify init

注意:你需要安装Amplify来完成这一步。

然后,你会被提示回答几个问题。你可以输入 "y "来获得默认的React配置,然后选择你的AWS配置文件(如果你没有,请看上面的教程!)。

Project information
| Name: graphqldemo
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: dist
| Build Command: npm run-script build
| Start Command: npm run-script start

? Initialize the project with the above configuration? Yes
Using default provider awscloudformation
? Select the authentication method you want to use: AWS profile

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html

? Please choose the profile you want to use default

现在,我们将创建一个GraphQL API。运行。

amplify add api

你会再次被问到几个问题!首先,选择GraphQL,然后命名你的API,例如graphql demo。然后,你可以按两次回车键,接受API密钥的默认值。然后,你可以为GraphQL API选择否,为GraphQL模式选择否。选择 "一对多关系 "模板,是的,现在编辑模式。

? Please select from one of the below mentioned services: GraphQL
? Provide API name: graphqldemo
? Choose the default authorization type for the API API key
? Enter a description for the API key:
? After how many days from now the API key should expire (1-365): 7
? Do you want to configure advanced settings for the GraphQL API No, I am done.
? Do you have an annotated GraphQL schema? No
? Choose a schema template: One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)
? Do you want to edit the schema now? Yes
? Choose your default editor: Visual Studio Code

你会看到一个预先生成的模式弹出,让我们谈谈博客模型。

type Blog @model {
  id: ID!
  name: String!
  posts: [Post] @connection(keyName: "byBlog", fields: ["id"])
}

TODO:添加标量类型的列表

type - 这个词用来表示你可能从你的API中得到的对象类型--在这个例子中是一个博客!type

Blog - 这是该类型的名称

@model - GraphQl中的 符号定义了一个指令,这意味着一个字段或类型有与之相关的自定义逻辑。Amplify@ 提供了相当多的指令,你可以使用这些指令。 指令使得Blog的数据被储存在我们的数据库中。@model

id,name, 和posts - 这些是字段或数据,每个博客将有

ID 和 - 这些是类型,它们定义了 's将是 , s将是字符串。这些字段是 ,这意味着它们是单一的数据块--一个ID和一个名字,而不是每个博客文章的名字集合。String id id name scalar

! - types后面的感叹号意味着该字段是不可归零的,或者说你总是需要为该字段提供一个值。在这种情况下,每个博客都必须有一个ID和名字

[Post] - 首先, ,使得它是一个数组字段。每个博客都可以有一个与之相关的帖子数组。你也可以对标量类型这样做,所以 将允许一个字符串的数组。在这种情况下,我们指的是 模型,它也在这个文件中声明,所以这两种数据类型是相互关联的。[] [String] Post

@connection - 这是另一个指令,在这种情况下,它允许我们将一个模型与另一个模型联系起来。你需要向它提供一些数据,在本例中 和keyName fields

keyName - 这是应该被查询的索引的名称,以获得相关的帖子。你会注意到在 模型上,一个 指令被定义了一个名字。该键的名称将与这里的 。只要你在Amplify中有一个一对多的字段,你就需要定义一个 ,然后用 来引用它。Post @key keyName @key keyName

fields - 这是一个可以被查询以获得连接对象的字段。

现在让我们把这个换成我们的模式。我们将创建一个类似于食谱的东西。让我们首先创建三个模型:Recipe,Ingredient, 和Instruction

type Recipe @model {
}

type Ingredient @model {
}

type Instruction @model {
}

现在,让我们为每个模型添加字段。每个模型将需要一个id ,这将是一个强制性的ID 字段。然后,我们将在RecipeIngredient 中添加nameIngredient 也将有一个数量,Instruction 将有info

type Recipe @model {
  id: ID!
  name: String!
}

type Ingredient @model {
  id: ID!
  name: String!
  quantity: String!
}

type Instruction @model{
  id: ID!
  info: String!
}

现在,我们需要连接我们的模型。首先,我们将在我们的两个子模型--IngredientInstruction 中添加@key 指令,因为Recipes 将拥有这两个模型!我们希望能够通过各自所属的配方来访问Ingredients和Instructions。每个人都将有一个recipeID ,它将指代每个人所属的配方。然后,我们将根据这个recipeID ,创建一个与Recipe 模型的连接。最后,我们将在每个模型上建立一个@key ,这将使我们能够访问属于食谱的成分组或说明。

type Ingredient @model @key(name: "byRecipe", fields: ["recipeID"]) {
  id: ID!
  name: String!
  quantity: String!
  recipeID: ID!
  recipe: Recipe @connection(fields: ["recipeID"])
}

type Instruction @model @key(name: "byRecipe", fields: ["recipeID"]) {
  id: ID!
  info: String!
  recipeID: ID!
  recipe: Recipe @connection(fields: ["recipeID"])
}

最后,我们将添加从Recipe 模型到每个成分和指令的连接。

type Recipe @model {
  id: ID!
  name: String!
  ingredients: [Ingredient] @connection(keyName: "byRecipe", fields: ["id"])
  instructions: [Instruction] @connection(keyName: "byRecipe", fields: ["id"])
}

现在,我们需要部署我们的数据!运行amplify push 将在云端为我们创建一个GraphQL API。

amplify push -y

查询和突变!

好了,我们已经建立了一个GraphQL。现在,让我们与它互动吧我们将使用mutations 创建数据。我们还将使用queries 检索我们的数据。

在你的命令行中,运行。

amplify console api

然后选择graphql。AWS AppSync的控制台将在浏览器中打开。AppSync是我们用来创建GraphQL API的底层服务,使用它的控制台,我们可以用一个可视化的界面测试出查询。

一旦你进入AppSync的界面,在下拉菜单中选择Mutation ,然后点击加号按钮。

Interface showing the mutation drop down and the plus sign

在下面,你会看到一些可供选择的行动。选择 "createRecipe",然后点击输入下name 旁边的复选框。

为你的食谱键入一个名字。我选择了mac n cheese!

screenshot of the interface showing the options and checkbox

按下橙色的运行按钮,你就会有一个配方✨!如果你愿意,你可以创建几个不同的配方 -- 改变配方的名称,为你想做的每个配方按下橙色按钮。

现在让我们来看看我们创建的食谱。将下拉菜单切换回Query ,而不是Mutation 。然后在它下面选择listRecipes 。选择你想看的属性,比如name ,在items 。还请注意,你可以

Display of the final query

重复你创建Recipe 的相同过程,创建一些成分和说明。在recipeID 中使用你的食谱的ID(提示:你可以使用listRecipes 查询得到这个ID!)你也可以在一个突变中创建一个带有成分和说明的食谱,如果你选择它们的字段并同时填充它们的话

现在,用ingredientsinstructions 重新运行listRecipes 查询,你会看到所有的东西都连接起来了。这就是GraphQL的魅力 -- 你可以在不改变端点的情况下获得你需要的任何数据,你只需改变你所交互的字段即可

GraphQL查询的剖析

我们已经使用这个可视化界面编写了GraphQL查询和突变,但让我们也深入了解其语法,以便你可以从头开始编写和理解它们。

这里有一个例子,我们可以在我们的API上使用查询。

query MyQuery {
  # This is a comment!
  listRecipes {
    items {
      name
      id
      createdAt
      instructions {
        items {
          id
          info
        }
      }
      ingredients {
        items {
          id
          name
          quantity
        }
      }
    }
  }
}

query - 这是我们对数据进行的操作类型。 是为了检索数据, 是为了改变数据, 是为了监听我们数据的变化。在本教程的其余部分,我们将使用所有这三种操作query mutation subscription

MyQuery - 这是查询的名称,最好是描述性的,如ListRecipes

listRecipes - AppSync生成GraphQL解析器,使我们能够获得数据。

items - 这在语法上表示,我们得到了多个食谱的回馈

name,id,createdAt - 我们想拿回关于我们的数据的字段。createdAtupdatedAt 是为我们自动添加的。

instructions 和 - 我们还想获得有关说明和成分的数据回来!那么他们的字段就在他们的查询中,以获得这些数据。ingredients

你可以在查询中添加或删除任何你想添加的字段!

有些查询还需要arguments 。例如,如果你只想得到一个Recipe,你可以提供你想要的那个Recipe的id。对于突变也是如此。

query GetRecipe($id: ID!) {
  getRecipe(id: $id) {
    id
    name
  }
}

现在,让我们在我们的应用程序中查询我们新创建的API!

如何在前端运行这些查询

现在我们已经尝试了突变和查询,我们如何将这些整合到我们的应用程序中呢?首先,让我们在不使用任何库的情况下进行尝试。我们可以使用一个普通的Fetch请求,就像我们在REST API调用时使用的那样。

转到你的App.js 组件。首先,从aws-exports.js 文件中导入对象。你可以进去看看那个文件,但它基本上有你的前端需要的关于你的Amplify生成的后端的所有配置信息。另外,从React导入useEffect

import config from './aws-exports'
import { useEffect } from 'react'

现在,我们将创建一个useEffect钩子,在页面加载时发出一个获取请求(如果你使用的是vanilla JavaScript,你很可能在页面加载事件中不使用useEffect而编写同样的代码)。

在获取请求中,我们需要指定端点,我们可以从aws-exports 对象中获得。然后我们需要通过添加请求方法POST 来定制请求。我们还将提供来自aws-exports 文件的API密钥。然后,请求体将包含我们之前使用的查询!我们需要使用JSON.stringify 方法将我们的对象转换为一个字符串。就像其他的获取请求一样,我们需要将数据转换为JSON,然后你就可以查看它了

function App() {
  useEffect(() => {
    const pullData = async () => {
      let data = await fetch(config.aws_appsync_graphqlEndpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          'X-Api-Key': config.aws_appsync_apiKey
        },
        body: JSON.stringify({
          query: `query MyQuery {
          listRecipes {
            items {
              name
              id
              createdAt
              instructions {
                items {
                  id
                  info
                }
              }
              ingredients {
                items {
                  id
                  name
                  quantity
                }
              }
            }
          }
        }
        `
        })
      })
      data = await data.json()
      console.log(data)
    }
    pullData()
  }, [])

  return <h1>Hello GraphQL!</h1>
}

好了,现在我们可以从我们的API获取数据了,但这有点笨重,而且有很多代码。如果你进入Amplify生成的graphql/ 目录,你会看到其中有订阅、查询和突变的文件,用于所有常见的操作!我们将导入这些并在我们的代码中使用它们。另外,Amplify还提供了辅助函数来抽象出HTTP请求。

在你项目的根目录下,运行。

npm i aws-amplify

这将安装Amplify库,这将有助于使GraphQL查询更简明。

我们将在index.js 文件中配置Amplify,将我们的前端和后端联系在一起。把这个添加到顶部。

// index.js
import { Amplify } from 'aws-amplify'
import config from './aws-exports'

Amplify.configure(config)

现在,返回到App.js 文件。我们将从aws-amplify 库中导入一些东西。

import { API } from 'aws-amplify'

我们还将从Amplify生成的查询中导入listRecipes 查询。你可以在'graphql/queries.js'文件中查看它的代码。

import { listRecipes } from './graphql/queries'

让我们修改一下我们的useEffect 代码。用下面的内容替换你的pullData 函数。

useEffect(() => {
  const pullData = async () => {
    const data = await API.graphql({ query: listRecipes })
    console.log(data)
  }
  pullData()
}, [])

API.graphql() 方法向我们应用程序配置的GraphQL API运行一个API请求。我们将在一个对象中传递查询作为参数。比以前的代码少多了!

现在,我们将运行一个突变,在点击按钮时创建一个新的配方。我们还将提示用户输入菜谱的名称。在App.js 组件中用下面的语句代替你的return ,一个按钮在点击时运行一个事件监听器。

return (
  <div className='App'>
    <button onClick={createNewRecipe}>create recipe</button>
  </div>
)

请确保导入我们需要的突变。

import { createRecipe } from './graphql/mutations'

现在,我们将实现createNewRecipe 函数。把这个添加到你的组件中。我们将首先要求用户命名配方。然后,我们将运行一个GraphQL请求,这次是用createRecipe 变异。这个突变也需要变量:在这种情况下是配方的名称。我们也会在一个对象中提供这个变量。

const createNewRecipe = async () => {
  const name = prompt('what is the recipe name?')
  const newRecipe = await API.graphql({ query: createRecipe, variables: { input: { name } }}))
  console.log(newRecipe)
}

如果你刷新页面,你现在会看到你的配方数组中有你创建的新配方但是,我们怎样才能使查询在有新配方创建时自动重新运行呢?订阅!

订阅

订阅允许你通过GraphQL "订阅 "事件,所以每当你的数据更新时,你可以运行代码。在我们的案例中,我们将使它成为每当有新的配方被创建时,我们就重新获取所有的配方。

首先,导入订阅。

import { onCreateRecipe } from './graphql/subscriptions'

然后,我们将更新我们的useEffect 。保留最初的几行,从API中提取食谱。在这下面创建一个订阅。这看起来与我们所做的其他API请求相似,但在这种情况下,我们将把.subscribe 方法添加到它上面。我们将传递一个带有nexterror 的对象。如果订阅出现错误,将运行Error。Next ,在订阅触发后运行。在我们的例子中,我们要重新运行pullData!

最后,确保通过返回一个清理订阅的函数来退订更新。

useEffect(() => {
  const pullData = async () => {
    const data = await API.graphql(graphqlOperation(listRecipes))
    console.log(data)
  }
  pullData()

  const subscription = API.graphql(
    { query: onCreateRecipe }
  ).subscribe({
    next: (recipeData) => {
      pullData()
    },
    error: (err) => {
      console.log(err)
    }
  })

  return () => subscription.unsubscribe()
}, [])

总结

在这篇文章中,我们了解了GraphQL和它的好处,以及为什么你可能不想使用它我们还创建了一个API,然后在一个前端应用程序中使用它。如果你想关闭你的API,你可以从你的CLI中运行amplify delete ,你的代码将持续存在于本地,但它将不再被部署到云端