Package githubv4 is a client library for accessing GitHub GraphQL API v4 (https://docs.github.com/en/graphql).
If you're looking for a client library for GitHub REST API v3, the recommended package is github (also known as go-github).
- Friendly, simple and powerful API.
- Correctness, high performance and efficiency.
- Support all of GitHub GraphQL API v4 via code generation from schema.
go get github.com/shurcooL/githubv4GitHub GraphQL API v4 requires authentication. The githubv4 package does not directly handle authentication. Instead, when creating a new client, you're expected to pass an http.Client that performs authentication. The easiest and recommended way to do this is to use the golang.org/x/oauth2 package. You'll need an OAuth token from GitHub (for example, a personal API token) with the right scopes. Then:
import"golang.org/x/oauth2"funcmain() {
src:=oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
)
httpClient:=oauth2.NewClient(context.Background(), src)
client:=githubv4.NewClient(httpClient)
// Use client...
}If you are using GitHub Enterprise, use githubv4.NewEnterpriseClient:
client:=githubv4.NewEnterpriseClient(os.Getenv("GITHUB_ENDPOINT"), httpClient)
// Use client...To make a query, you need to define a Go type that corresponds to the GitHub GraphQL schema, and contains the fields you're interested in querying. You can look up the GitHub GraphQL schema at https://docs.github.com/en/graphql/reference/queries.
For example, to make the following GraphQL query:
query {
viewer {
logincreatedAt
}
}You can define this variable:
varquerystruct {
Viewerstruct {
Login githubv4.StringCreatedAt githubv4.DateTime
}
}Then call client.Query, passing a pointer to it:
err:=client.Query(context.Background(), &query, nil)
iferr!=nil {
// Handle error.
}
fmt.Println(" Login:", query.Viewer.Login)
fmt.Println("CreatedAt:", query.Viewer.CreatedAt)
// Output:// Login: gopher// CreatedAt: 2017-05-26 21:17:14 +0000 UTCFor each scalar in the GitHub GraphQL schema listed at https://docs.github.com/en/graphql/reference/scalars, there is a corresponding Go type in package githubv4.
You can use these types when writing queries:
varquerystruct {
Viewerstruct {
Login githubv4.StringCreatedAt githubv4.DateTimeIsBountyHunter githubv4.BooleanBioHTML githubv4.HTMLWebsiteURL githubv4.URI
}
}
// Call client.Query() and use results in query...However, depending on how you're planning to use the results of your query, it's often more convenient to use other Go types.
The encoding/json rules are used for converting individual JSON-encoded fields from a GraphQL response into Go values. See https://godoc.org/encoding/json#Unmarshal for details. The json.Unmarshaler interface is respected.
That means you can simplify the earlier query by using predeclared Go types:
// import "time"varquerystruct {
Viewerstruct {
Loginstring// E.g., "gopher".CreatedAt time.Time// E.g., time.Date(2017, 5, 26, 21, 17, 14, 0, time.UTC).IsBountyHunterbool// E.g., true.BioHTMLstring// E.g., `I am learning <a href="https://graphql.org">GraphQL</a>!`.WebsiteURLstring// E.g., "https://golang.org".
}
}
// Call client.Query() and use results in query...The DateTime scalar is described as "an ISO-8601 encoded UTC date string". If you wanted to fetch in that form without parsing it into a time.Time, you can use the string type. For example, this would work:
// import "html/template"typeMyBooleanboolvarquerystruct {
Viewerstruct {
Loginstring// E.g., "gopher".CreatedAtstring// E.g., "2017-05-26T21:17:14Z".IsBountyHunterMyBoolean// E.g., MyBoolean(true).BioHTML template.HTML// E.g., template.HTML(`I am learning <a href="https://graphql.org">GraphQL</a>!`).WebsiteURL template.URL// E.g., template.URL("https://golang.org").
}
}
// Call client.Query() and use results in query...Often, you'll want to specify arguments on some fields. You can use the graphql struct field tag for this.
For example, to make the following GraphQL query:
{
repository(owner: "octocat", name: "Hello-World") {
description
}
}You can define this variable:
varqstruct {
Repositorystruct {
Descriptionstring
} `graphql:"repository(owner: \"octocat\", name: \"Hello-World\")"`
}Then call client.Query:
err:=client.Query(context.Background(), &q, nil)
iferr!=nil {
// Handle error.
}
fmt.Println(q.Repository.Description)
// Output:// My first repository on GitHub!However, that'll only work if the arguments are constant and known in advance. Otherwise, you will need to make use of variables. Replace the constants in the struct field tag with variable names:
// fetchRepoDescription fetches description of repo with owner and name.funcfetchRepoDescription(ctx context.Context, owner, namestring) (string, error) {
varqstruct {
Repositorystruct {
Descriptionstring
} `graphql:"repository(owner: $owner, name: $name)"`
}When sending variables to GraphQL, you need to use exact types that match GraphQL scalar types, otherwise the GraphQL server will return an error.
So, define a variables map with their values that are converted to GraphQL scalar types:
variables:=map[string]interface{}{
"owner": githubv4.String(owner),
"name": githubv4.String(name),
}Finally, call client.Query providing variables:
err:=client.Query(ctx, &q, variables)
returnq.Repository.Description, err
}Some GraphQL queries contain inline fragments. You can use the graphql struct field tag to express them.
For example, to make the following GraphQL query:
{
repositoryOwner(login: "github") {
login...onOrganization {
description
}
...onUser {
bio
}
}
}You can define this variable:
varqstruct {
RepositoryOwnerstruct {
LoginstringOrganizationstruct {
Descriptionstring
} `graphql:"... on Organization"`Userstruct {
Biostring
} `graphql:"... on User"`
} `graphql:"repositoryOwner(login: \"github\")"`
}Alternatively, you can define the struct types corresponding to inline fragments, and use them as embedded fields in your query:
type (
OrganizationFragmentstruct {
Descriptionstring
}
UserFragmentstruct {
Biostring
}
)
varqstruct {
RepositoryOwnerstruct {
LoginstringOrganizationFragment`graphql:"... on Organization"`UserFragment`graphql:"... on User"`
} `graphql:"repositoryOwner(login: \"github\")"`
}Then call client.Query:
err:=client.Query(context.Background(), &q, nil)
iferr!=nil {
// Handle error.
}
fmt.Println(q.RepositoryOwner.Login)
fmt.Println(q.RepositoryOwner.Description)
fmt.Println(q.RepositoryOwner.Bio)
// Output:// github// How people build software.//Imagine you wanted to get a complete list of comments in an issue, and not just the first 10 or so. To do that, you'll need to perform multiple queries and use pagination information. For example:
typecommentstruct {
BodystringAuthorstruct {
LoginstringAvatarURLstring`graphql:"avatarUrl(size: 72)"`
}
ViewerCanReactbool
}
varqstruct {
Repositorystruct {
Issuestruct {
Commentsstruct {
Nodes []commentPageInfostruct {
EndCursor githubv4.StringHasNextPagebool
}
} `graphql:"comments(first: 100, after: $commentsCursor)"`// 100 per page.
} `graphql:"issue(number: $issueNumber)"`
} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
}
variables:=map[string]interface{}{
"repositoryOwner": githubv4.String(owner),
"repositoryName": githubv4.String(name),
"issueNumber": githubv4.Int(issue),
"commentsCursor": (*githubv4.String)(nil), // Null after argument to get first page.
}
// Get comments from all pages.varallComments []commentfor {
err:=client.Query(ctx, &q, variables)
iferr!=nil {
returnerr
}
allComments=append(allComments, q.Repository.Issue.Comments.Nodes...)
if!q.Repository.Issue.Comments.PageInfo.HasNextPage {
break
}
variables["commentsCursor"] =githubv4.NewString(q.Repository.Issue.Comments.PageInfo.EndCursor)
}There is more than one way to perform pagination. Consider additional fields inside PageInfo object.
Mutations often require information that you can only find out by performing a query first. Let's suppose you've already done that.
For example, to make the following GraphQL mutation:
mutation($input: AddReactionInput!) {
addReaction(input: $input) {
reaction {
content
}
subject {
id
}
}
}
variables {
"input": {
"subjectId": "MDU6SXNzdWUyMTc5NTQ0OTc=",
"content": "HOORAY"
}
}You can define:
varmstruct {
AddReactionstruct {
Reactionstruct {
Content githubv4.ReactionContent
}
Subjectstruct {
ID githubv4.ID
}
} `graphql:"addReaction(input: $input)"`
}
input:= githubv4.AddReactionInput{
SubjectID: targetIssue.ID, // ID of the target issue from a previous query.Content: githubv4.ReactionContentHooray,
}Then call client.Mutate:
err:=client.Mutate(context.Background(), &m, input, nil)
iferr!=nil {
// Handle error.
}
fmt.Printf("Added a %v reaction to subject with ID %#v!\n", m.AddReaction.Reaction.Content, m.AddReaction.Subject.ID)
// Output:// Added a HOORAY reaction to subject with ID "MDU6SXNzdWUyMTc5NTQ0OTc="!| Path | Synopsis |
|---|---|
| example/githubv4dev | githubv4dev is a test program currently being used for developing githubv4 package. |