rablentain
rablentain

Reputation: 6745

graphql-codegen error response from prismic

When trying to generate types from Prismic with graphql-codegen I get the following error:

graphql/types.tsx
    Failed to load schema from [object Object]:

        invalid json response body at https://my-project-name.prismic.io/graphql reason: Unexpected t
oken < in JSON at position 0

It seems to be returning HTML I guess (hence the <). If i go to the graphql url in Chrome I get the graphiql-editor. If I go to the url in Postman I get the missing query parameter(which is expected) error, so the path seems to work in those environments. Is there a specific config I need to use with Prismic?

schema:
  - https://my-project-name.prismic.io/graphql:
    headers:
      Prismic-Ref: PRISMIC_REF
documents:
  - "graphql/**/*.ts"
generates:
  graphql/types.tsx:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
    config:
      noHOC: true
      noComponents: true
      noNamespaces: true
      withHooks: true

Upvotes: 0

Views: 1694

Answers (3)

Rob Hogan
Rob Hogan

Reputation: 2634

There are a few elements to this:

  • Requests must use GET
  • The Prismic-ref header must be passed with the ID of the master ref
  • An access token must be given (for private APIs), even for introspection

Codegen supports a customFetch option which allows us to customise the outgoing request. I've packaged up the steps above into a customFetch implementation and published it here:

https://www.npmjs.com/package/codegen-prismic-fetch

Upvotes: 1

Arvin Wilderink
Arvin Wilderink

Reputation: 11

This is indeed because the Prismic GraphQL api uses a GET instead of POST request. I actually did not find any tool which made it possible to introspect a GraphQL endpoint with a GET. After a little digging a came up with the following solution:

  1. This code is based on prismic-apollo-link. I modified it slightly since I'm using it in conjunction with Next JS (I needed isomorphic unfetch). If you're not using Next JS you will still need it since we need fetch to work in Node.
import {ApolloClient} from 'apollo-client'
import {InMemoryCache} from 'apollo-cache-inmemory'
import {HttpLink} from 'apollo-link-http'
import {setContext} from 'apollo-link-context'
import Prismic from 'prismic-javascript'
import fetch from 'isomorphic-unfetch'

const baseEndpoint = 'https://<your project>.cdn.prismic.io'
const accessToken = '<your access token>'

export default function createApolloClient(initialState, ctx) {
  const primicClient = Prismic.client(`${baseEndpoint}/api`, {accessToken})

  const prismicLink = setContext((req, options) => {
    return primicClient.getApi().then(api => ({
      headers: {
        'Prismic-ref': api.masterRef.ref,
        ...options.headers,
        ...((api as any).integrationFieldRef
          ? {'Prismic-integration-field-ref': (api as any).integrationFieldRef}
          : {}),
        ...(accessToken ? {Authorization: `Token ${accessToken}`} : {}),
      },
    }))
  })

  const httpLink = new HttpLink({
    uri: `${baseEndpoint}/graphql`,
    useGETForQueries: true,
    fetch,
  })

  return new ApolloClient({
    ssrMode: Boolean(ctx),
    link: prismicLink.concat(httpLink),
    cache: new InMemoryCache().restore(initialState),
  })
}

  1. Make a script to introspect the Prismic GraphQL server:
import createApolloClient from '../apolloClient'
import gql from 'graphql-tag'
import path from 'path'
import fs from 'fs'

const client = createApolloClient({}, null)

const main = async () => {
  try {
    const res = await client.query({
      query: gql`
        query IntrospectionQuery {
          __schema {
            queryType {
              name
            }
            mutationType {
              name
            }
            subscriptionType {
              name
            }
            types {
              ...FullType
            }
            directives {
              name
              description
              locations
              args {
                ...InputValue
              }
            }
          }
        }

        fragment FullType on __Type {
          kind
          name
          description
          fields(includeDeprecated: true) {
            name
            description
            args {
              ...InputValue
            }
            type {
              ...TypeRef
            }
            isDeprecated
            deprecationReason
          }
          inputFields {
            ...InputValue
          }
          interfaces {
            ...TypeRef
          }
          enumValues(includeDeprecated: true) {
            name
            description
            isDeprecated
            deprecationReason
          }
          possibleTypes {
            ...TypeRef
          }
        }

        fragment InputValue on __InputValue {
          name
          description
          type {
            ...TypeRef
          }
          defaultValue
        }

        fragment TypeRef on __Type {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
                ofType {
                  kind
                  name
                  ofType {
                    kind
                    name
                    ofType {
                      kind
                      name
                      ofType {
                        kind
                        name
                      }
                    }
                  }
                }
              }
            }
          }
        }
      `,
    })

    if (res.data) {
      const schema = JSON.stringify(res.data)
      // Specify where the schema should be written to
      fs.writeFileSync(path.resolve(__dirname, '../../schema.json'), schema)
    } else {
      throw new Error('No Data')
    }
    process.exit()
  } catch (e) {
    console.log(e)
    process.exit(1)
  }
}

main()
  1. Include the script your codegen.yml:
schema: "./schema.json"
documents: ./src/**/*.graphql
generates:
  ./src/generated.tsx:
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo
    config:
      withHooks: true
      withComponent: false
hooks:
  afterStart:
    - ts-node  <path to script>/introspectPrismic.ts

Maybe it is not the most elegant solution but it works!

Upvotes: 1

Arnaud Lewis
Arnaud Lewis

Reputation: 31

I'm not very familiar with this tool but I guess that by default, this tool will try to make a call to the graphQL API with the POST Method. For Cache reasons, Prismic only uses GET for now so i'm pretty sure it has something to do with it. Hope it helps you figure it out.

Upvotes: 1

Related Questions