| # graphql.js | |
| > GitHub GraphQL API client for browsers and Node | |
| [](https://www.npmjs.com/package/@octokit/graphql) | |
| [](https://github.com/octokit/graphql.js/actions?query=workflow%3ATest+branch%3Amain) | |
| <!-- toc --> | |
| - [Usage](#usage) | |
| - [Send a simple query](#send-a-simple-query) | |
| - [Authentication](#authentication) | |
| - [Variables](#variables) | |
| - [Pass query together with headers and variables](#pass-query-together-with-headers-and-variables) | |
| - [Use with GitHub Enterprise](#use-with-github-enterprise) | |
| - [Use custom `@octokit/request` instance](#use-custom-octokitrequest-instance) | |
| - [TypeScript](#typescript) | |
| - [Additional Types](#additional-types) | |
| - [Errors](#errors) | |
| - [Partial responses](#partial-responses) | |
| - [Writing tests](#writing-tests) | |
| - [License](#license) | |
| <!-- tocstop --> | |
| ## Usage | |
| <table> | |
| <tbody valign=top align=left> | |
| <tr><th> | |
| Browsers | |
| </th><td width=100%> | |
| Load `@octokit/graphql` directly from [esm.sh](https://esm.sh) | |
| ```html | |
| <script type="module"> | |
| import { graphql } from "https://esm.sh/@octokit/graphql"; | |
| </script> | |
| ``` | |
| </td></tr> | |
| <tr><th> | |
| Node | |
| </th><td> | |
| Install with <code>npm install @octokit/graphql</code> | |
| ```js | |
| const { graphql } = require("@octokit/graphql"); | |
| // or: import { graphql } from "@octokit/graphql"; | |
| ``` | |
| </td></tr> | |
| </tbody> | |
| </table> | |
| ### Send a simple query | |
| ```js | |
| const { repository } = await graphql( | |
| ` | |
| { | |
| repository(owner: "octokit", name: "graphql.js") { | |
| issues(last: 3) { | |
| edges { | |
| node { | |
| title | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `, | |
| { | |
| headers: { | |
| authorization: `token secret123`, | |
| }, | |
| }, | |
| ); | |
| ``` | |
| ### Authentication | |
| The simplest way to authenticate a request is to set the `Authorization` header, e.g. to a [personal access token](https://github.com/settings/tokens/). | |
| ```js | |
| const graphqlWithAuth = graphql.defaults({ | |
| headers: { | |
| authorization: `token secret123`, | |
| }, | |
| }); | |
| const { repository } = await graphqlWithAuth(` | |
| { | |
| repository(owner: "octokit", name: "graphql.js") { | |
| issues(last: 3) { | |
| edges { | |
| node { | |
| title | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `); | |
| ``` | |
| For more complex authentication strategies such as GitHub Apps or Basic, we recommend the according authentication library exported by [`@octokit/auth`](https://github.com/octokit/auth.js). | |
| ```js | |
| const { createAppAuth } = require("@octokit/auth-app"); | |
| const auth = createAppAuth({ | |
| appId: process.env.APP_ID, | |
| privateKey: process.env.PRIVATE_KEY, | |
| installationId: 123, | |
| }); | |
| const graphqlWithAuth = graphql.defaults({ | |
| request: { | |
| hook: auth.hook, | |
| }, | |
| }); | |
| const { repository } = await graphqlWithAuth( | |
| `{ | |
| repository(owner: "octokit", name: "graphql.js") { | |
| issues(last: 3) { | |
| edges { | |
| node { | |
| title | |
| } | |
| } | |
| } | |
| } | |
| }`, | |
| ); | |
| ``` | |
| ### Variables | |
| ⚠️ Do not use [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) in the query strings as they make your code vulnerable to query injection attacks (see [#2](https://github.com/octokit/graphql.js/issues/2)). Use variables instead: | |
| ```js | |
| const { lastIssues } = await graphql( | |
| ` | |
| query lastIssues($owner: String!, $repo: String!, $num: Int = 3) { | |
| repository(owner: $owner, name: $repo) { | |
| issues(last: $num) { | |
| edges { | |
| node { | |
| title | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `, | |
| { | |
| owner: "octokit", | |
| repo: "graphql.js", | |
| headers: { | |
| authorization: `token secret123`, | |
| }, | |
| }, | |
| ); | |
| ``` | |
| ### Pass query together with headers and variables | |
| ```js | |
| const { graphql } = require("@octokit/graphql"); | |
| const { lastIssues } = await graphql({ | |
| query: `query lastIssues($owner: String!, $repo: String!, $num: Int = 3) { | |
| repository(owner:$owner, name:$repo) { | |
| issues(last:$num) { | |
| edges { | |
| node { | |
| title | |
| } | |
| } | |
| } | |
| } | |
| }`, | |
| owner: "octokit", | |
| repo: "graphql.js", | |
| headers: { | |
| authorization: `token secret123`, | |
| }, | |
| }); | |
| ``` | |
| ### Use with GitHub Enterprise | |
| ```js | |
| let { graphql } = require("@octokit/graphql"); | |
| graphql = graphql.defaults({ | |
| baseUrl: "https://github-enterprise.acme-inc.com/api", | |
| headers: { | |
| authorization: `token secret123`, | |
| }, | |
| }); | |
| const { repository } = await graphql(` | |
| { | |
| repository(owner: "acme-project", name: "acme-repo") { | |
| issues(last: 3) { | |
| edges { | |
| node { | |
| title | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `); | |
| ``` | |
| ### Use custom `@octokit/request` instance | |
| ```js | |
| const { request } = require("@octokit/request"); | |
| const { withCustomRequest } = require("@octokit/graphql"); | |
| let requestCounter = 0; | |
| const myRequest = request.defaults({ | |
| headers: { | |
| authorization: "bearer secret123", | |
| }, | |
| request: { | |
| hook(request, options) { | |
| requestCounter++; | |
| return request(options); | |
| }, | |
| }, | |
| }); | |
| const myGraphql = withCustomRequest(myRequest); | |
| await request("/"); | |
| await myGraphql(` | |
| { | |
| repository(owner: "acme-project", name: "acme-repo") { | |
| issues(last: 3) { | |
| edges { | |
| node { | |
| title | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `); | |
| // requestCounter is now 2 | |
| ``` | |
| ## TypeScript | |
| `@octokit/graphql` is exposing proper types for its usage with TypeScript projects. | |
| ### Additional Types | |
| Additionally, `GraphQlQueryResponseData` has been exposed to users: | |
| ```ts | |
| import type { GraphQlQueryResponseData } from "@octokit/graphql"; | |
| ``` | |
| ## Errors | |
| In case of a GraphQL error, `error.message` is set to a combined message describing all errors returned by the endpoint. | |
| All errors can be accessed at `error.errors`. `error.request` has the request options such as query, variables and headers set for easier debugging. | |
| ```js | |
| let { graphql, GraphqlResponseError } = require("@octokit/graphql"); | |
| graphql = graphql.defaults({ | |
| headers: { | |
| authorization: `token secret123`, | |
| }, | |
| }); | |
| const query = `{ | |
| viewer { | |
| bioHtml | |
| } | |
| }`; | |
| try { | |
| const result = await graphql(query); | |
| } catch (error) { | |
| if (error instanceof GraphqlResponseError) { | |
| // do something with the error, allowing you to detect a graphql response error, | |
| // compared to accidentally catching unrelated errors. | |
| // server responds with an object like the following (as an example) | |
| // class GraphqlResponseError { | |
| // "headers": { | |
| // "status": "403", | |
| // }, | |
| // "data": null, | |
| // "errors": [{ | |
| // "message": "Field 'bioHtml' doesn't exist on type 'User'", | |
| // "locations": [{ | |
| // "line": 3, | |
| // "column": 5 | |
| // }] | |
| // }] | |
| // } | |
| console.log("Request failed:", error.request); // { query, variables: {}, headers: { authorization: 'token secret123' } } | |
| console.log(error.message); // Field 'bioHtml' doesn't exist on type 'User' | |
| } else { | |
| // handle non-GraphQL error | |
| } | |
| } | |
| ``` | |
| ## Partial responses | |
| A GraphQL query may respond with partial data accompanied by errors. In this case we will throw an error but the partial data will still be accessible through `error.data` | |
| ```js | |
| let { graphql } = require("@octokit/graphql"); | |
| graphql = graphql.defaults({ | |
| headers: { | |
| authorization: `token secret123`, | |
| }, | |
| }); | |
| const query = `{ | |
| repository(name: "probot", owner: "probot") { | |
| name | |
| ref(qualifiedName: "master") { | |
| target { | |
| ... on Commit { | |
| history(first: 25, after: "invalid cursor") { | |
| nodes { | |
| message | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }`; | |
| try { | |
| const result = await graphql(query); | |
| } catch (error) { | |
| // server responds with | |
| // { | |
| // "data": { | |
| // "repository": { | |
| // "name": "probot", | |
| // "ref": null | |
| // } | |
| // }, | |
| // "errors": [ | |
| // { | |
| // "type": "INVALID_CURSOR_ARGUMENTS", | |
| // "path": [ | |
| // "repository", | |
| // "ref", | |
| // "target", | |
| // "history" | |
| // ], | |
| // "locations": [ | |
| // { | |
| // "line": 7, | |
| // "column": 11 | |
| // } | |
| // ], | |
| // "message": "`invalid cursor` does not appear to be a valid cursor." | |
| // } | |
| // ] | |
| // } | |
| console.log("Request failed:", error.request); // { query, variables: {}, headers: { authorization: 'token secret123' } } | |
| console.log(error.message); // `invalid cursor` does not appear to be a valid cursor. | |
| console.log(error.data); // { repository: { name: 'probot', ref: null } } | |
| } | |
| ``` | |
| ## Writing tests | |
| You can pass a replacement for [the built-in fetch implementation](https://github.com/bitinn/node-fetch) as `request.fetch` option. For example, using [fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/) works great to write tests | |
| ```js | |
| const assert = require("assert"); | |
| const fetchMock = require("fetch-mock/es5/server"); | |
| const { graphql } = require("@octokit/graphql"); | |
| graphql("{ viewer { login } }", { | |
| headers: { | |
| authorization: "token secret123", | |
| }, | |
| request: { | |
| fetch: fetchMock | |
| .sandbox() | |
| .post("https://api.github.com/graphql", (url, options) => { | |
| assert.strictEqual(options.headers.authorization, "token secret123"); | |
| assert.strictEqual( | |
| options.body, | |
| '{"query":"{ viewer { login } }"}', | |
| "Sends correct query", | |
| ); | |
| return { data: {} }; | |
| }), | |
| }, | |
| }); | |
| ``` | |
| ## License | |
| [MIT](LICENSE) | |