nest-graphql-endpoint
TypeScript icon, indicating that this package has built-in type declarations

0.7.1 • Public • Published

Nest an external GraphQL Endpoint (Like GitHub's v4 API)

How it works

https://www.parabol.co/blog/nest-github-api-in-graphql-schema

Install

yarn add nest-graphql-endpoint

What's it do?

Merges a remote graphql endpoint into your schema. For example, let's say your app has a GitHub integration. Each user has used OAuth2 to allow your app to access GitHub on their behalf. You have stored that token on the User object in your database. Now, you want to get their name from your app & their bio from GitHub. You also want the bio of all their friends, too. This package will let you write the following query:

query {
  user(id: 'abc') {
    name
    github {
      errors {
        message
      }
      query {
        # These values come from GitHub
        viewer {
          bio
        }
      }
    }
    friends {
      github {
        errors {
          message
        }
        query {
          viewer {
            bio
          }
        }
      }
    }
    todos {
      ... on _extGitHubIssue {
        # These values come from GitHub, too
        number
        repository {
          nameWithOwner
        }
      }
    }
  }
}

Example with your own endpoint

import {schema} from './mySchema'
import nestGraphQLEndpoint from 'nest-graphql-endpoint'
import {print} from 'graphql'
import schemaIDL from './remoteSchemaIDL'

return nestGraphQLEndpoint({
  parentSchema: schema,
  parentType: 'User',
  fieldName: 'github',
  resolveEndpointContext: (source) => ({accessToken: source.accessToken}),
  executor: (document, variables, context) => {
    // Example executor fn not for production! See nestGitHubEndpoint for a production-ready executor
    return fetch('https://foo.co', {
      headers: {
        Authorization: `Bearer ${context.accessToken}`,
      },
      body: JSON.stringify({query: print(document), variables}),
    })
  },
  prefix: '_extEndpoint',
  batchKey: 'accessToken',
  schemaIDL,
})

As a convenience, this package includes a helper for GitHub's API. If you'd like to add other endpoints, please make a PR.

Example using GitHub's API

import {schema} from './mySchema'
import nestGitHubEndpoint from 'nest-graphql-endpoint/lib/nestGitHubEndpoint'

return nestGitHubEndpoint({
  parentSchema: schema,
  parentType: 'User',
  fieldName: 'github',
  // Assumes a `githubToken` is on the User object
  // GitHub preview is being phased out, but still requires a custom header to access preview features
  resolveEndpointContext: (source) => ({
    accessToken: source.githubToken,
    headers: {Accept: 'application/vnd.github.bane-preview+json'},
  }),
}).schema

Note: Requires Node v15.0.0 to use the native AbortController

Example field resolution

Say each User has a list of todos and some of those come from Jira & others come from GitHub.

type User {
  id: ID!
  todos: [Todo]
}
interface Todo {
  id: ID!
}
type _extGitHubIssue implements Todo
type JiraIssue implements Todo
// Front-end
const UserQuery = graphql`
  query UserQuery($userId: ID!) {
    user(id: $userId) {
      todos {
        __typename
        ... on JiraIssue {
          descriptionHTML
        }
      ... on  _extGitHubIssue {
        bodyHTML
        number
        respository {
          nameWithOwner
        }
      }
    }
  }
`;
const ShowTodo = (props) => {
  const data = useQuery(UserQuery)
  if (!data) return null
  return data.todos.map((todo) => {
    if todo.__typename === 'JiraIssue') {
      return <JiraTodo todo={todo}/>
    }
    return <GitHubTodo todo={todo}/>
  })
}
// Backend
const {schema, githubRequest} = nestGitHubEndpoint({...})

const todoResolver = (source, args, context, info) => {
  if (source.type === 'github') {
    const ghContext = {accessToken: '123'}
    const {nameWithOwner, issueNumber} = source
    const [repoOwner, repoName] = nameWithOwner.split('/')
    const wrapper = `
          {
            repository(owner: "${repoOwner}", name: "${repoName}") {
              issue(number: ${issueNumber}) {
                ...info
              }
            }
          }`
    const {data, errors} = await githubRequest(wrapper, ghContext, context, info)
    return data // returns
  }
}

How it works

  1. It extends your schema with a type that contains {errors, query, mutation}
  2. Given a remote IDL, it prefixes the __typename so there are no conflicts with your own schema
  3. It batches all requests by the batchKey so only 1 request is made per key. In the above example, this is the accessToken.
  4. For each batched request, it removes the __typename prefix and merges the fragments, variableDefinitions, and variables.
  5. In the event of a name conflict, it will alias fields before the request is fetched.
  6. When the endpoint responds, it will de-alias the response, re-apply the __typename prefix, and filter the errors by path
  7. For field resolvers, it removes any types that don't exist on the endpoint & then sends the request.

Limnitations

  • FragmentDefinitions are assumed equal if they have matching names (Relay compiler guarantees this)

License

MIT

Readme

Keywords

Package Sidebar

Install

npm i nest-graphql-endpoint

Weekly Downloads

19

Version

0.7.1

License

MIT

Unpacked Size

153 kB

Total Files

72

Last publish

Collaborators

  • mattkrick