1githubv4
2========
3
4[](https://pkg.go.dev/github.com/shurcooL/githubv4)
5
6Package `githubv4` is a client library for accessing GitHub GraphQL API v4 (https://docs.github.com/en/graphql).
7
8If you're looking for a client library for GitHub REST API v3, the recommended package is [`github`](https://github.com/google/go-github#installation) (also known as `go-github`).
9
10Focus
11-----
12
13- Friendly, simple and powerful API.
14- Correctness, high performance and efficiency.
15- Support all of GitHub GraphQL API v4 via code generation from schema.
16
17Installation
18------------
19
20```sh
21go get github.com/shurcooL/githubv4
22```
23
24Usage
25-----
26
27### Authentication
28
29GitHub GraphQL API v4 [requires authentication](https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#authenticating-with-graphql). 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`](https://golang.org/x/oauth2) package. You'll need an OAuth token from GitHub (for example, a [personal API token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)) with the right scopes. Then:
30
31```Go
32import "golang.org/x/oauth2"
33
34func main() {
35 src := oauth2.StaticTokenSource(
36 &oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
37 )
38 httpClient := oauth2.NewClient(context.Background(), src)
39
40 client := githubv4.NewClient(httpClient)
41 // Use client...
42}
43```
44
45If you are using GitHub Enterprise, use [`githubv4.NewEnterpriseClient`](https://godoc.org/github.com/shurcooL/githubv4#NewEnterpriseClient):
46
47```Go
48client := githubv4.NewEnterpriseClient(os.Getenv("GITHUB_ENDPOINT"), httpClient)
49// Use client...
50```
51
52### Simple Query
53
54To 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.
55
56For example, to make the following GraphQL query:
57
58```GraphQL
59query {
60 viewer {
61 login
62 createdAt
63 }
64}
65```
66
67You can define this variable:
68
69```Go
70var query struct {
71 Viewer struct {
72 Login githubv4.String
73 CreatedAt githubv4.DateTime
74 }
75}
76```
77
78Then call `client.Query`, passing a pointer to it:
79
80```Go
81err := client.Query(context.Background(), &query, nil)
82if err != nil {
83 // Handle error.
84}
85fmt.Println(" Login:", query.Viewer.Login)
86fmt.Println("CreatedAt:", query.Viewer.CreatedAt)
87
88// Output:
89// Login: gopher
90// CreatedAt: 2017-05-26 21:17:14 +0000 UTC
91```
92
93### Scalar Types
94
95For 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`.
96
97You can use these types when writing queries:
98
99```Go
100var query struct {
101 Viewer struct {
102 Login githubv4.String
103 CreatedAt githubv4.DateTime
104 IsBountyHunter githubv4.Boolean
105 BioHTML githubv4.HTML
106 WebsiteURL githubv4.URI
107 }
108}
109// Call client.Query() and use results in query...
110```
111
112However, depending on how you're planning to use the results of your query, it's often more convenient to use other Go types.
113
114The `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`](https://godoc.org/encoding/json#Unmarshaler) interface is respected.
115
116That means you can simplify the earlier query by using predeclared Go types:
117
118```Go
119// import "time"
120
121var query struct {
122 Viewer struct {
123 Login string // E.g., "gopher".
124 CreatedAt time.Time // E.g., time.Date(2017, 5, 26, 21, 17, 14, 0, time.UTC).
125 IsBountyHunter bool // E.g., true.
126 BioHTML string // E.g., `I am learning <a href="https://graphql.org">GraphQL</a>!`.
127 WebsiteURL string // E.g., "https://golang.org".
128 }
129}
130// Call client.Query() and use results in query...
131```
132
133The [`DateTime`](https://docs.github.com/en/graphql/reference/scalars#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:
134
135```Go
136// import "html/template"
137
138type MyBoolean bool
139
140var query struct {
141 Viewer struct {
142 Login string // E.g., "gopher".
143 CreatedAt string // E.g., "2017-05-26T21:17:14Z".
144 IsBountyHunter MyBoolean // E.g., MyBoolean(true).
145 BioHTML template.HTML // E.g., template.HTML(`I am learning <a href="https://graphql.org">GraphQL</a>!`).
146 WebsiteURL template.URL // E.g., template.URL("https://golang.org").
147 }
148}
149// Call client.Query() and use results in query...
150```
151
152### Arguments and Variables
153
154Often, you'll want to specify arguments on some fields. You can use the `graphql` struct field tag for this.
155
156For example, to make the following GraphQL query:
157
158```GraphQL
159{
160 repository(owner: "octocat", name: "Hello-World") {
161 description
162 }
163}
164```
165
166You can define this variable:
167
168```Go
169var q struct {
170 Repository struct {
171 Description string
172 } `graphql:"repository(owner: \"octocat\", name: \"Hello-World\")"`
173}
174```
175
176Then call `client.Query`:
177
178```Go
179err := client.Query(context.Background(), &q, nil)
180if err != nil {
181 // Handle error.
182}
183fmt.Println(q.Repository.Description)
184
185// Output:
186// My first repository on GitHub!
187```
188
189However, 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:
190
191```Go
192// fetchRepoDescription fetches description of repo with owner and name.
193func fetchRepoDescription(ctx context.Context, owner, name string) (string, error) {
194 var q struct {
195 Repository struct {
196 Description string
197 } `graphql:"repository(owner: $owner, name: $name)"`
198 }
199```
200
201When sending variables to GraphQL, you need to use exact types that match GraphQL scalar types, otherwise the GraphQL server will return an error.
202
203So, define a `variables` map with their values that are converted to GraphQL scalar types:
204
205```Go
206 variables := map[string]interface{}{
207 "owner": githubv4.String(owner),
208 "name": githubv4.String(name),
209 }
210```
211
212Finally, call `client.Query` providing `variables`:
213
214```Go
215 err := client.Query(ctx, &q, variables)
216 return q.Repository.Description, err
217}
218```
219
220### Inline Fragments
221
222Some GraphQL queries contain inline fragments. You can use the `graphql` struct field tag to express them.
223
224For example, to make the following GraphQL query:
225
226```GraphQL
227{
228 repositoryOwner(login: "github") {
229 login
230 ... on Organization {
231 description
232 }
233 ... on User {
234 bio
235 }
236 }
237}
238```
239
240You can define this variable:
241
242```Go
243var q struct {
244 RepositoryOwner struct {
245 Login string
246 Organization struct {
247 Description string
248 } `graphql:"... on Organization"`
249 User struct {
250 Bio string
251 } `graphql:"... on User"`
252 } `graphql:"repositoryOwner(login: \"github\")"`
253}
254```
255
256Alternatively, you can define the struct types corresponding to inline fragments, and use them as embedded fields in your query:
257
258```Go
259type (
260 OrganizationFragment struct {
261 Description string
262 }
263 UserFragment struct {
264 Bio string
265 }
266)
267
268var q struct {
269 RepositoryOwner struct {
270 Login string
271 OrganizationFragment `graphql:"... on Organization"`
272 UserFragment `graphql:"... on User"`
273 } `graphql:"repositoryOwner(login: \"github\")"`
274}
275```
276
277Then call `client.Query`:
278
279```Go
280err := client.Query(context.Background(), &q, nil)
281if err != nil {
282 // Handle error.
283}
284fmt.Println(q.RepositoryOwner.Login)
285fmt.Println(q.RepositoryOwner.Description)
286fmt.Println(q.RepositoryOwner.Bio)
287
288// Output:
289// github
290// How people build software.
291//
292```
293
294### Pagination
295
296Imagine 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:
297
298```Go
299type comment struct {
300 Body string
301 Author struct {
302 Login string
303 AvatarURL string `graphql:"avatarUrl(size: 72)"`
304 }
305 ViewerCanReact bool
306}
307var q struct {
308 Repository struct {
309 Issue struct {
310 Comments struct {
311 Nodes []comment
312 PageInfo struct {
313 EndCursor githubv4.String
314 HasNextPage bool
315 }
316 } `graphql:"comments(first: 100, after: $commentsCursor)"` // 100 per page.
317 } `graphql:"issue(number: $issueNumber)"`
318 } `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
319}
320variables := map[string]interface{}{
321 "repositoryOwner": githubv4.String(owner),
322 "repositoryName": githubv4.String(name),
323 "issueNumber": githubv4.Int(issue),
324 "commentsCursor": (*githubv4.String)(nil), // Null after argument to get first page.
325}
326
327// Get comments from all pages.
328var allComments []comment
329for {
330 err := client.Query(ctx, &q, variables)
331 if err != nil {
332 return err
333 }
334 allComments = append(allComments, q.Repository.Issue.Comments.Nodes...)
335 if !q.Repository.Issue.Comments.PageInfo.HasNextPage {
336 break
337 }
338 variables["commentsCursor"] = githubv4.NewString(q.Repository.Issue.Comments.PageInfo.EndCursor)
339}
340```
341
342There is more than one way to perform pagination. Consider additional fields inside [`PageInfo`](https://docs.github.com/en/graphql/reference/objects#pageinfo) object.
343
344### Mutations
345
346Mutations often require information that you can only find out by performing a query first. Let's suppose you've already done that.
347
348For example, to make the following GraphQL mutation:
349
350```GraphQL
351mutation($input: AddReactionInput!) {
352 addReaction(input: $input) {
353 reaction {
354 content
355 }
356 subject {
357 id
358 }
359 }
360}
361variables {
362 "input": {
363 "subjectId": "MDU6SXNzdWUyMTc5NTQ0OTc=",
364 "content": "HOORAY"
365 }
366}
367```
368
369You can define:
370
371```Go
372var m struct {
373 AddReaction struct {
374 Reaction struct {
375 Content githubv4.ReactionContent
376 }
377 Subject struct {
378 ID githubv4.ID
379 }
380 } `graphql:"addReaction(input: $input)"`
381}
382input := githubv4.AddReactionInput{
383 SubjectID: targetIssue.ID, // ID of the target issue from a previous query.
384 Content: githubv4.ReactionContentHooray,
385}
386```
387
388Then call `client.Mutate`:
389
390```Go
391err := client.Mutate(context.Background(), &m, input, nil)
392if err != nil {
393 // Handle error.
394}
395fmt.Printf("Added a %v reaction to subject with ID %#v!\n", m.AddReaction.Reaction.Content, m.AddReaction.Subject.ID)
396
397// Output:
398// Added a HOORAY reaction to subject with ID "MDU6SXNzdWUyMTc5NTQ0OTc="!
399```
400
401Directories
402-----------
403
404| Path | Synopsis |
405|--------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
406| [example/githubv4dev](https://pkg.go.dev/github.com/shurcooL/githubv4/example/githubv4dev) | githubv4dev is a test program currently being used for developing githubv4 package. |
407
408License
409-------
410
411- [MIT License](LICENSE)
View as plain text