# GitHub GraphQL API Examples
GraphQL is a query language for web services APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more.
For the examples here you can play with the queries using also /ˈɡrafək(ə)l/, a graphical interactive in-browser GraphQL IDE. You can use it in any of these ways:
- live GraphQL GitHub Explorer (opens new window) (Is an instance of GraphiQL. See the docs (opens new window))
- Or install the Electron Desktop GraphiQL app (opens new window).
- Try also the live demo at GraphQL site (opens new window): It uses a Star Wars API. Click on the docs on the upper left corner.
Watch the youtube video GitHub's GraphQL API (opens new window) by Kent C. Dodds
More advanced staff is in the video Advanced patterns for GitHub's GraphQL API (opens new window)
# Query Example: Number of repos in an Organization
# Structure of a Query
GraphQL queries return only the data you specify and no more ...
To form a query, you must specify fields within fields (also known as nested subfields) (opens new window) until you return only scalars (opens new window).
Queries are structured like this:
query {
JSON-OBJECT-TO-RETURN
}
2
3
Here is an example. These are the contents of the file org-num-repos.gql
:
query {
organization(login: "ULL-MII-SYTWS-2122") {
repositories {
totalCount
}
}
}
2
3
4
5
6
7
# Executing the Query
Go to the live GraphQL GitHub Explorer (opens new window), authenticate
and copy the request.
We can also execute in gh
this way:
gh api graphql \
--field query=@org-num-repos.gql \
--jq .data.organization.repositories.totalCount
2
3
# Analysis of the query
Let us comment the former query
query {
organization(login: "ULL-MII-SYTWS-2122") {
repositories {
totalCount
}
}
}
2
3
4
5
6
7
step by step:
query
: The GraphQL query keyword
Every GraphQL schema has a root type (opens new window) for both queries and mutations.
- The query root operation type must be provided and must be an Object type (opens new window).
- The mutation root operation type is optional; if it is not provided, the service does not support mutations. If it is provided, it must be an Object type (opens new window).
- Similarly, the subscription root operation type is also optional; if it is not provided, the service does not support subscriptions. If it is provided, it must be an Object type (opens new window).
The query type defines GraphQL operations that retrieve data from the server.
organization(login: "ULL-MII-SYTWS-2122") { ... }
To begin the query, we want to find a organization object.
arguments for queries
The schema validation for organization queries (opens new window) indicates this query requires a login
argument.
An argument is a set of key-value pairs attached to a specific field.
Some fields require an argument. We will see later that Mutations require an input object as an argument.
Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service.
When queries come in, they are validated (opens new window) and executed (opens new window) against that schema.
GraphQL objects
The query organization
is an object of type Organization (opens new window) that like any GraphQL object
- Implements some interfaces (opens new window).
- GraphQL interfaces represent a list of named fields and their arguments.
- GraphQL objects can then implement these interfaces which requires that the object type will define all fields defined by those interfaces
- Has some fields
Among the fields we can see that Organization (opens new window)
- Has a field
repositories
that is an object of type RepositoryConnection (opens new window) that itself - Has a field
totalCount
that is a scalar (opens new window) of typeInt
(integer)
# gh cli: argument conversion
`-f` versus `-F`
Pass one or more -f/--raw-field
values in "key=value"
format to add static string parameters to the request payload.
The -F/--field
flag has type conversion based on the format of the value.
- Placeholder values
"{owner}"
,"{repo}"
, and"{branch}"
get populated with values from the repository of the current directory and - if the value starts with
"@"
, the rest of the value is interpreted as a filename to read the value from. Pass "-
" to read from standard input. - literal values "
true
", "false
", "null
", and integer numbers get converted to appropriate JSON types;
For GraphQL requests, all fields other than query
and operationName
[1] are interpreted as GraphQL variables.
➜ graphql-learning git:(main) ✗ cat my-repos.bash
gh api graphql --paginate -F number_of_repos=3 --field query=@my-repos.gql
2
# Example: -F versus -f
In this example $number_of_repos
is a variable that is set to number 3
inside the command using the option -F number_of_repos=3
➜ graphql-learning git:(main) ✗ cat my-repos.gql
query($number_of_repos:Int!){
viewer {
name
repositories(last: $number_of_repos) {
nodes {
name
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
Here is the output of an execution:
➜ graphql-learning git:(main) ✗ gh api graphql \
--paginate \
-F number_of_repos=3 \
--field query=@my-repos.gql
2
3
4
and the output:
{
"data": {
"viewer": {
"name": "Casiano Rodriguez-Leon",
"repositories": {
"nodes": [
{
"name": "asyncmap-crguezl"
},
{
"name": "gh-clone-org"
},
{
"name": "learning-graphql-with-gh"
}
]
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Exercise: -F versus -f
What is the output if we use -f number_of_repos=3
instead of -F number_of_repos=3
in the former request?
# Example: Getting issues
Follows an example of query using GraphQL (see The Example query in GitHub Docs (opens new window)).
We can set the GraphQL query in a separated file:
➜ bin git:(master) cat gh-api-example.graphql
query {
repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
issues(last:2, states:OPEN) {
edges {
node {
title
url
labels(first:5) {
edges {
node {
name
}
}
}
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
To learn more, see the tutorial Forming calls with GraphQL (opens new window).
# Analysis of the query
Looking at the composition line by line:
query {
Because we want to read data from the server, not modify it, query
is the root operation. (If you don't specify an operation, query
is also the default.)
query {
repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
2
To begin the query, we want to find a repository object (opens new window).
The schema
validation for the query repository (opens new window) indicates this object requires
- an
owner
- and a
name
argument.
As we said before a schema
defines a GraphQL API's type system. It describes the complete set of possible data (objects, fields, relationships, everything) that a client can access
query {
repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
issues(last:2, states:OPEN) {
2
3
A field is a unit of data you can retrieve from an object. As the official GraphQL docs say: The GraphQL query language is basically about selecting fields on objects.
To account for all issues in the repository, we specify the issues
field of the repository object.
Some details about the issues
field:
# Connections in GraphQL
The docs tell us this object has the type IssueConnection (opens new window).
As usual for connections, the Schema indicates this object requires a field like last
or first
to specify the number of results per page as an argument, so we provide 2
.
The docs also tell us this object accepts a states
argument, which is an IssueState
enum that accepts OPEN
or CLOSED
values.
To find only open issues, we give the states key a value of OPEN
.
query {
repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
issues(last:2, states:OPEN) {
edges {
node { ... }
2
3
4
5
Edges represent connections between nodes. When you query a connection, you traverse its edges to get to its nodes.
We know issues is a connection because the Doc says it has the IssueConnection
type.
Connections let us query related objects as part of the same call. With connections, we can use a single GraphQL call where we would have to use multiple calls to a REST API.
To retrieve data about individual issues, we have to access the node via edges
.
query {
repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
issues(last:2, states:OPEN) {
edges {
node {
title
url
labels(first:5) {
edges {
node {
name
}
}
}
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Here we retrieve the node
at the end of the edge
.
The IssueConnection docs (opens new window) indicate the node at the end of the IssueConnection
type is an Issue
object.
Now that we know we're retrieving an Issue
object, we can look at the docs for issue (opens new window) and specify the fields we want to return.
Here we specify the title
, url
, and labels
fields of the Issue
object.
- The
labels
field has the type LabelConnection (opens new window). - As with the
issues
object, becauselabels
is a connection, we must travel itsedges
to a connectednode
: thelabel
object. - At the node, we can specify the
label
object fields we want to return, in this case,name
.
You can see the code at crguezl/learning-graphql-with-gh/tree/main/gh-graphql-connection-example (opens new window)
# Pagination
pageInfo.nextCursor pageInfo.hasNextPage
When in gh
we use the --paginate
option, all pages of results will sequentially be requested until there are no more pages of results. For GraphQL requests, this requires that
- the original query accepts an
$endCursor: String
variable and that - it fetches the
pageInfo{ hasNextPage, endCursor }
set of fields from a collection.
Here is an example that produces an array of objects with the name
and branch
fields of all the repositories in the specified organization:
#!/bin/bash
ORG=$(gh pwd)
if [[ -n $1 ]]; then ORG=$1; fi
gh api graphql --paginate \
--jq '
[
.data
.organization
.repositories
.nodes[] |
{ branch: .defaultBranchRef.name, name: .name }
]
' \
-F org=$ORG \
-F query=@org-getallrepos.gql
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Where the org-getallrepos.gql
file contains:
query($org:String!, $endCursor:String) {
organization(login:$org) {
repositories(first: 100,
after: $endCursor,
isFork:false,
orderBy: {field:NAME, direction:ASC}) {
pageInfo {
hasNextPage
endCursor
}
nodes {
name
defaultBranchRef {
name
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
See crguezl/learning-graphql-with-gh/org-getallrepos-graphql-pagination (opens new window)
# Mutation
The mutation type defines GraphQL operations that change data on the server.
It is analogous to performing HTTP verbs such as POST
, PATCH
, and DELETE
.
To form a mutation, you must specify three things:
- Mutation name. The type of modification you want to perform.
- Input object. The data you want to send to the server, composed of input fields. Pass it as an argument to the mutation name.
- Payload object. The data you want to return from the server, composed of return fields. Pass it as the body of the mutation name.
Mutations are structured like this:
mutation {
MUTATION-NAME(
input: {
MUTATION-NAME-INPUT!
})
{
MUTATION-NAME-PAYLOAD
}
}
}
2
3
4
5
6
7
8
9
10
The input object in this example is MutationNameInput
,
and the payload object is MutationNamePayload
.
For instance, the following example shows a mutation to add an emoji reaction to the issue.
mutation AddReactionToIssue {
addReaction(
input:
{
subjectId:"I_kwDOGLyMF84838wt",
content:ROCKET
}
)
{
reaction {
content
}
subject {
id
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
See the reference docs for addReaction mutation (opens new window), whose description is: Adds a reaction to a subject.
The docs for the mutation list three input fields:
clientMutationId (String)
subjectId (ID!)
content (ReactionContent!)
A required content makes sense:
- we want to add a reaction, so we'll need to specify which emoji to use.
- the
subjectId
is the only way to identify which issue in which repository to react to.
Mutations often require information that you can only find out by performing a query first.
In this example AddReactionToIssue
, we have to find the issue id
:
➜ graphql-learning git:(main) cat findissueid.gql
query FindIssueID {
repository(owner:"crguezl", name:"learning-graphql-with-gh") {
issue(number:2) {
id
}
}
}
2
3
4
5
6
7
which we can get with:
✗ gh api graphql --paginate -F num=1 --field query=@findissueid.gql
{
"data": {
"repository": {
"issue": {
"id": "I_kwDOGLyMF84837de"
}
}
}
}
2
3
4
5
6
7
8
9
10
How do we know which value to use for the content
?
The addReaction (opens new window) docs tell us
the content
field has the type ReactionContent (opens new window),
which is an enum (opens new window) because only certain emoji reactions are supported on GitHub issues.
The rest of the call is composed of the payload object.
This is where we specify the data we want the server to return after we've performed the mutation.
These lines come from the addReaction (opens new window) docs, which three possible return fields:
clientMutationId (String)
reaction (Reaction!)
subject (Reactable!)
In this example, we return the two required fields (reaction
and subject
), both of which have required subfields
(respectively, content
and id
).
Now we can add a reaction to the issue:
➜ graphql-learning git:(main) cat addreactiontoissue.gql
mutation AddReactionToIssue {
addReaction(input:{subjectId:"I_kwDOGLyMF84838wt",content:ROCKET}) {
reaction {
content
}
subject {
id
}
}
}
2
3
4
5
6
7
8
9
10
11
using the command:
gh api graphql --paginate --field query=@addreactiontoissue.gql
# Rename Repository
Here is a second example of mutation that renames a repository:
query getRepoId {
repository(owner: "ULL-ESIT-DMSI-1920", name: "prueba") {
id
}
}
mutation renameRepoName($id: ID!) {
updateRepository(
input: {
name: "prueba-funciona",
repositoryId: $id
}
)
{
repository {
name
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Discussions
# Get Discussions in the repo crguezl/learning-graphql-with-gh
➜ discussions-mutation git:(main) ✗ cat get-discussions.bash
#!/bin/bash
# Get discussions
gh api graphql \
-H 'GraphQL-Features: discussions_api' \
-F owner=':owner' \
-F name=':repo' \
-f query='
query getDiscussions($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
discussions(first: 10) {
edges {
node {
id
number
body
}
}
}
}
}'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Execution:
➜ discussions-mutation git:(main) ./get-discussions.bash
{
"data": {
"repository": {
"discussions": {
"edges": [
{
"node": {
"id": "D_kwDOGLyMF84ARkhY",
"number": 3,
"body": "<!--\r\n ✏️ Optional: Customize the content below to let your community know what you intend to use Discussions for.\r\n-->\r\n## 👋 Welcome!\r\n We’re using Discussions as a place to connect with other members of our community. We hope that you:\r\n * Ask questions you’re wondering about.\r\n * Share ideas.\r\n * Engage with other community members.\r\n * Welcome others and are open-minded. Remember that this is a community we\r\n build together 💪.\r\n\r\n To get started, comment below with an introduction of yourself and tell us about what you do with this community.\r\n\r\n<!--\r\n For the maintainers, here are some tips 💡 for getting started with Discussions. We'll leave these in Markdown comments for now, but feel free to take out the comments for all maintainers to see.\r\n\r\n 📢 **Announce to your community** that Discussions is available! Go ahead and send that tweet, post, or link it from the website to drive traffic here.\r\n\r\n 🔗 If you use issue templates, **link any relevant issue templates** such as questions and community conversations to Discussions. Declutter your issues by driving community content to where they belong in Discussions. If you need help, here's a [link to the documentation](https://docs.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser).\r\n\r\n ➡️ You can **convert issues to discussions** either individually or bulk by labels. Looking at you, issues labeled “question” or “discussion”.\r\n-->\r\n"
}
}
]
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Get Comments in the Discussion
➜ discussions-mutation git:(main) ✗ cat get-comments.bash
#!/bin/bash
# Discussion 3 and comments
```GraphQL
declare -i DISCUSSION_NUMBER=3
if [[ $# != 0 ]]; then
DISCUSSION_NUMBER=$1
fi
gh api graphql \
-H 'GraphQL-Features: discussions_api' \
-F discussionNumber=${DISCUSSION_NUMBER} \
-F owner=':owner' \
-F name=':repo' \
-f query='query getComments($owner: String!, $name: String!, $discussionNumber: Int!) {
repository(owner: $owner, name: $name) {
discussion(number: $discussionNumber) {
id
title
body
comments(first: 10) {
totalCount
edges {
node {
id
body
replies(first: 5) {
edges {
node {
body
id
}
}
}
}
}
}
}
}
}
'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Execution:
➜ discussions-mutation git:(main) ✗ ./get-comments.bash
{
"data": {
"repository": {
"discussion": {
"id": "D_kwDOGLyMF84ARkhY",
"title": "Welcome to learning-graphql-with-gh Discussions!",
"body": "<!--\r\n ✏️ Optional: Customize the content below to let your community know what you intend to use Discussions for.\r\n-->\r\n## 👋 Welcome!\r\n We’re using Discussions as a place to connect with other members of our community. We hope that you:\r\n * Ask questions you’re wondering about.\r\n * Share ideas.\r\n * Engage with other community members.\r\n * Welcome others and are open-minded. Remember that this is a community we\r\n build together 💪.\r\n\r\n To get started, comment below with an introduction of yourself and tell us about what you do with this community.\r\n\r\n<!--\r\n For the maintainers, here are some tips 💡 for getting started with Discussions. We'll leave these in Markdown comments for now, but feel free to take out the comments for all maintainers to see.\r\n\r\n 📢 **Announce to your community** that Discussions is available! Go ahead and send that tweet, post, or link it from the website to drive traffic here.\r\n\r\n 🔗 If you use issue templates, **link any relevant issue templates** such as questions and community conversations to Discussions. Declutter your issues by driving community content to where they belong in Discussions. If you need help, here's a [link to the documentation](https://docs.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser).\r\n\r\n ➡️ You can **convert issues to discussions** either individually or bulk by labels. Looking at you, issues labeled “question” or “discussion”.\r\n-->\r\n",
"comments": {
"totalCount": 1,
"edges": [
{
"node": {
"id": "DC_kwDOGLyMF84AQN01",
"body": "Esta es un comentario en la primera discusión.\r\nVamos a usar la API GraphQL para añadir comentarios",
"replies": {
"edges": [
{
"node": {
"body": "Contador de cuerpo: 1",
"id": "DC_kwDOGLyMF84AQN4l"
}
}
]
}
}
}
]
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Mutation: Add Reply to Discussion Comment
➜ discussions-mutation git:(main) ✗ cat reply-to-discussion-comment.bash
#!/bin/bash
BODY="Contador de cuerpo: 3"
discussionId="D_kwDOGLyMF84ARkhY"
replyToId="DC_kwDOGLyMF84AQN01"
if [[ $# != 0 ]]; then
BODY=$1
fi
gh api graphql \
-H 'GraphQL-Features: discussions_api' \
-f body="Contador de cuerpo: $BODY" \
-F discussionId=$discussionId \
-F replyToId=$replyToId \
-f query='
mutation addComment($body: String!, $discussionId: ID!, $replyToId: ID!){
addDiscussionComment(input:
{
body: $body ,
discussionId: $discussionId,
replyToId: $replyToId
}
)
{
comment{
body
id
}
}
}'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Execution:
➜ discussions-mutation git:(main) ✗ ./reply-to-discussion-comment.bash
{
"data": {
"addDiscussionComment": {
"comment": {
"body": "Contador de cuerpo: Contador de cuerpo: 3",
"id": "DC_kwDOGLyMF84AQN_k"
}
}
}
}
2
3
4
5
6
7
8
9
10
11
# References for Discussions GraphQL API
- gist (opens new window)
- GitHub Docs discussions (opens new window)
- Feedbacks (opens new window)
- Discusion API (opens new window)
# Footnotes
The operation name is a meaningful and explicit name for your operation. It is only required in multi-operation documents, but its use is encouraged because it is very helpful for debugging and server-side logging. ↩︎