跳到主要内容

使用 GraphQL 形成调用

了解如何向 GraphQL API 进行身份验证,然后了解如何创建和运行查询和突变。

使用 GraphQL 进行身份验证

可以使用个人访问令牌、GitHub 应用或 OAuth 应用向 GraphQL API 进行身份验证。

使用个人访问令牌进行身份验证

若要使用个人访问令牌进行身份验证,请按照“管理个人访问令牌”中的步骤操作。你请求的数据将决定你需要哪些范围或权限。

例如,选择“issues:read”权限以读取令牌有权访问的所有存储库中的所有问题。

所有细粒度的个人访问令牌都包含对公共存储库的读取访问权限。若要使用个人访问令牌(经典版)访问公共存储库,请选择“public_repo”范围。

如果你的令牌没有访问资源所需的范围或权限,API 将返回一条错误消息,说明令牌需要的范围或权限。

使用 GitHub 应用进行身份验证

如果你想代表组织或其他用户使用 API,GitHub 建议你使用 GitHub 应用。为了将活动归因于你的应用,你可以让你的应用作为应用安装进行身份验证。为了将应用活动归因于用户,你可以让你的应用代表用户进行身份验证。在这两种情况下,你都将生成一个令牌,可用于向 GraphQL API 进行身份验证。有关详细信息,请参阅“注册 GitHub 应用”和“关于使用 GitHub 应用进行身份验证”。

使用 OAuth 应用进行身份验证

要使用 OAuth 应用的 OAuth 令牌进行身份验证,你必须先使用 Web 应用流程或设备流程授权你的 OAuth 应用。然后,你可以使用收到的访问令牌访问 API。有关详细信息,请参阅“创建 OAuth 应用”和“授权 OAuth 应用”。

GraphQL 端点

REST API 有多个端点;GraphQL API 有一个端点

https://api.github.com/graphql

无论执行什么操作,端点都保持不变。

与 GraphQL 通信

由于 GraphQL 操作包含多行 JSON,GitHub 建议使用Explorer进行 GraphQL 调用。你还可以使用 curl 或任何其他 HTTP 语言库。

在 REST 中,HTTP 动词确定执行的操作。在 GraphQL 中,无论执行查询还是突变,你都将提供 JSON 编码的正文,因此 HTTP 动词为 POST。例外情况是内省查询,这是一个简单的 GET 到端点。有关 GraphQL 与 REST 的更多信息,请参阅“从 REST 迁移到 GraphQL”。

要在 curl 命令中查询 GraphQL,请使用 JSON 有效负载发出 POST 请求。有效负载必须包含一个名为 query 的字符串

curl -H "Authorization: bearer TOKEN" -X POST -d " \
 { \
   \"query\": \"query { viewer { login }}\" \
 } \
" https://api.github.com/graphql

注意"query" 的字符串值必须转义换行符,否则模式将无法正确解析它。对于 POST 正文,请使用外部双引号和转义的内部双引号。

关于查询和突变操作

GitHub 的 GraphQL API 中允许的两种操作类型是查询突变。将 GraphQL 与 REST 进行比较,查询操作类似于 GET 请求,而突变操作类似于 POST/PATCH/DELETE突变名称确定执行哪个修改。

有关速率限制的信息,请参阅“GraphQL API 的速率限制和节点限制”。

查询和突变共享类似的格式,但有一些重要的区别。

关于查询

GraphQL 查询仅返回您指定的数据。要形成查询,您必须指定字段中的字段(也称为嵌套子字段),直到仅返回标量

查询的结构如下

query {
  JSON-OBJECT-TO-RETURN
}

有关实际示例,请参见“示例查询”。

关于突变

要形成突变,您必须指定三件事

  1. 突变名称。您要执行的修改类型。
  2. 输入对象。您要发送到服务器的数据,由输入字段组成。将其作为参数传递给突变名称。
  3. 有效负载对象。您要从服务器返回的数据,由返回字段组成。将其作为突变名称的主体传递。

突变的结构如下

mutation {
  MUTATION-NAME(input: {MUTATION-NAME-INPUT!}) {
    MUTATION-NAME-PAYLOAD
  }
}

此示例中的输入对象是 MutationNameInput,有效负载对象是 MutationNamePayload

突变参考中,列出的输入字段是您作为输入对象传递的内容。列出的返回字段是您作为有效负载对象传递的内容。

有关实际示例,请参见“示例突变”。

使用变量

变量可以使查询更具动态性和更强大,并且可以在传递突变输入对象时降低复杂性。

注意:如果您使用 Explorer,请务必在单独的查询变量窗格中输入变量,并且不要在 JSON 对象之前包含单词 variables

这是一个带有单个变量的示例查询

query($number_of_repos:Int!) {
  viewer {
    name
     repositories(last: $number_of_repos) {
       nodes {
         name
       }
     }
   }
}
variables {
   "number_of_repos": 3
}

使用变量有三个步骤

  1. variables 对象中在操作外部定义变量

    variables {
       "number_of_repos": 3
    }
    

    该对象必须是有效的 JSON。此示例显示了一个简单的 Int 变量类型,但可以定义更复杂的变量类型,例如输入对象。您还可以在此处定义多个变量。

  2. 将变量作为参数传递给操作

    query($number_of_repos:Int!){
    

    参数是键值对,其中键是名称,以 $ 开头(例如,$number_of_repos),值是类型(例如,Int)。添加 ! 以指示是否需要该类型。如果您定义了多个变量,请将其作为多个参数包含在此处。

  3. 在操作中使用变量

    repositories(last: $number_of_repos) {
    

    在此示例中,我们将变量替换为要检索的存储库数量。我们在步骤 2 中指定了一个类型,因为 GraphQL 强制执行强类型。

此过程使查询参数动态化。我们现在可以简单地更改 variables 对象中的值,并保持查询的其余部分不变。

使用变量作为参数可让你动态更新 variables 对象中的值,而无需更改查询。

示例查询

让我们浏览一个更复杂的查询,并将此信息置于上下文中。

以下查询查找 octocat/Hello-World 存储库,查找最近 20 个已关闭的问题,并返回每个问题的标题、URL 和前 5 个标签

query {
  repository(owner:"octocat", name:"Hello-World") {
    issues(last:20, states:CLOSED) {
      edges {
        node {
          title
          url
          labels(first:5) {
            edges {
              node {
                name
              }
            }
          }
        }
      }
    }
  }
}

逐行查看组成

  • query {

    因为我们想要从服务器读取数据,而不是修改数据,所以 query 是根操作。(如果你未指定操作,则 query 也是默认值。)

  • repository(owner:"octocat", name:"Hello-World") {

    要开始查询,我们想要查找 repository 对象。架构验证表明此对象需要一个 owner 和一个 name 参数。

  • issues(last:20, states:CLOSED) {

    要考虑存储库中的所有问题,我们调用 issues 对象。(我们可以repository 上查询单个 issue,但这要求我们知道我们想要返回的问题的编号,并将其作为参数提供。)

    有关 issues 对象的一些详细信息

    • 文档 告诉我们此对象具有类型 IssueConnection
    • 架构验证表明此对象需要一个 lastfirst 数字作为参数,因此我们提供 20
    • 文档 还告诉我们此对象接受 states 参数,它是一个 IssueState 枚举,接受 OPENCLOSED 值。要仅查找已关闭的问题,我们给 states 键一个 CLOSED 值。
  • edges {

    我们知道 issues 是一个连接,因为它具有 IssueConnection 类型。要检索有关各个问题的详细信息,我们必须通过 edges 访问节点。

  • node {

    这里我们检索边缘末端的节点。 IssueConnection 文档 指示 IssueConnection 类型末端的节点是一个 Issue 对象。

  • 既然我们知道要检索一个 Issue 对象,我们可以查看 文档 并指定我们想要返回的字段

    title
    url
    labels(first:5) {
      edges {
        node {
          name
        }
      }
    }
    

    这里我们指定 Issue 对象的 titleurllabels 字段。

    labels 字段的类型为 LabelConnection。与 issues 对象一样,因为 labels 是一个连接,我们必须沿着它的边到一个连接的节点:label 对象。在该节点,我们可以指定我们想要返回的 label 对象字段,在本例中为 name

你可能会注意到,在 Octocat 的公共 Hello-World 存储库上运行此查询不会返回很多标签。尝试在你自己的使用标签的存储库上运行它,你可能会看到差异。

示例变异

变异通常需要你只能通过先执行查询才能找到的信息。此示例显示两个操作

  1. 获取问题 ID 的查询。
  2. 向问题添加表情反应的变异。
query FindIssueID {
  repository(owner:"octocat", name:"Hello-World") {
    issue(number:349) {
      id
    }
  }
}

mutation AddReactionToIssue {
  addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
    reaction {
      content
    }
    subject {
      id
    }
  }
}

虽然你可以在同一个 Explorer 窗口中包含一个查询和一个变异(在本例中为 FindIssueIDAddReactionToIssue),但这些操作将作为对 GraphQL 端点的单独调用执行。不可能在与变异同时执行查询,反之亦然。

我们来浏览一下这个示例。任务听起来很简单:向问题添加表情反应。

那么我们如何知道以查询开始?我们还不知道。

因为我们想要修改服务器上的数据(将表情符号附加到问题),所以我们首先在模式中搜索有用的变异。参考文档显示 addReaction 变异,其描述为:向主题添加反应。完美!

变异的文档列出了三个输入字段

  • clientMutationId (String)
  • subjectId (ID!)
  • content (ReactionContent!)

! 表示 subjectIdcontent 是必需字段。必需的 content 合情合理:我们希望添加反应,因此我们需要指定要使用哪个表情符号。

但为什么 subjectId 是必需的?这是因为 subjectId 是识别在 哪个 存储库中对 哪个 问题做出反应的唯一方法。

这就是我们从查询开始此示例的原因:获取 ID

让我们逐行检查查询

  • query FindIssueID {

    此处我们正在执行查询,并将其命名为 FindIssueID。请注意,命名查询是可选的;我们在此处为其命名,以便可以将其包含在与突变相同的 Explorer 窗口中。

  • repository(owner:"octocat", name:"Hello-World") {

    我们通过查询 repository 对象并传递 ownername 参数来指定存储库。

  • issue(number:349) {

    我们通过查询 issue 对象并传递 number 参数来指定要做出反应的问题。

  • id

    此处我们检索 https://github.com/octocat/Hello-World/issues/349id 以作为 subjectId 传递。

当我们运行查询时,我们将获得 idMDU6SXNzdWUyMzEzOTE1NTE=

注意:在查询中返回的 id 是我们将在突变中作为 subjectID 传递的值。文档或模式内省都不会指示此关系;你需要了解名称背后的概念才能弄清楚这一点。

知道了 ID,我们可以继续进行突变

  • mutation AddReactionToIssue {

    此处我们正在执行突变,并将其命名为 AddReactionToIssue。与查询一样,命名突变是可选的;我们在此处为其命名,以便可以将其包含在与查询相同的 Explorer 窗口中。

  • addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {

    让我们检查此行

    • addReaction 是突变的名称。
    • input 是必需的参数键。对于突变,这始终是 input
    • {subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY} 是必需的参数值。这始终是一个 输入对象(因此使用大括号),由输入字段(在本例中为 subjectIdcontent)组成,用于突变。

    我们如何知道要对内容使用哪个值?addReaction 文档告诉我们content 字段具有类型 ReactionContent,它是一个 枚举,因为 GitHub 问题仅支持某些表情符号反应。以下是允许的反应值(请注意,某些值与其对应的表情符号名称不同)

    内容 表情符号
    +1 👍
    -1 👎
    😄
    困惑 😕
    ❤️
    欢呼 🎉
    火箭 🚀
    眼睛 👀
  • 调用的其余部分由有效负载对象组成。我们在此指定希望服务器在执行突变后返回的数据。这些行来自 addReaction 文档,其中包含三个可能的返回字段

    • clientMutationId (String)
    • reaction (Reaction!)
    • subject (Reactable!)

    在此示例中,我们返回两个必需字段(reactionsubject),这两个字段都有必需的子字段(分别为 contentid)。

当我们运行突变时,这是响应

{
  "data": {
    "addReaction": {
      "reaction": {
        "content": "HOORAY"
      },
      "subject": {
        "id": "MDU6SXNzdWUyMTc5NTQ0OTc="
      }
    }
  }
}

就是这样!通过将鼠标悬停在 🎉 上方以查找您的用户名,查看您对 问题的反应

最后一点说明:当您在输入对象中传递多个字段时,语法可能会变得难以处理。将字段移动到 变量 中可能会有所帮助。以下是使用变量重写原始突变的方法

mutation($myVar:AddReactionInput!) {
  addReaction(input:$myVar) {
    reaction {
      content
    }
    subject {
      id
    }
  }
}
variables {
  "myVar": {
    "subjectId":"MDU6SXNzdWUyMTc5NTQ0OTc=",
    "content":"HOORAY"
  }
}

您可能会注意到,较早示例中的 content 字段值(直接在突变中使用该值)在 HOORAY 周围没有引号,但在变量中使用时却有引号。这是有原因的

  • 当您在突变中直接使用 content 时,架构期望该值类型为 ReactionContent,它是一个 枚举,而不是字符串。如果您在枚举值周围添加引号,架构验证将引发错误,因为引号是为字符串保留的。
  • 当您在变量中使用 content 时,变量部分必须是有效的 JSON,因此需要引号。在执行期间将变量传递到突变时,架构验证会正确解释 ReactionContent 类型。

有关枚举和字符串之间差异的更多信息,请参阅 官方 GraphQL 规范

进一步阅读

在形成 GraphQL 调用时,您可以做的事情还有 很多。以下是一些接下来可以查看的地方