Skip to content

Usage with Apollo

This guide will show you how to use pg_graphql with Apollo and GraphQL Code Generator for type-safe GraphQL queries in your React application.

Apollo Setup

Pre-requisites

  1. Follow the Apollo Getting Started Guide.
  2. Follow the GraphQL Code Generator Installation Guide.

Configuring GraphQL Code Generator

Modify your codegen.ts file to reflect the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import type { CodegenConfig } from '@graphql-codegen/cli'
import { addTypenameSelectionDocumentTransform } from '@graphql-codegen/client-preset'

const config: CodegenConfig = {
  schema: 'http://localhost:54321/graphql/v1', // Using the local endpoint, update if needed
  documents: 'src/**/*.tsx',
  overwrite: true,
  ignoreNoDocuments: true,
  generates: {
    'src/gql/': {
      preset: 'client',
      documentTransforms: [addTypenameSelectionDocumentTransform],
      plugins: [],
      config: {
        scalars: {
          UUID: 'string',
          Date: 'string',
          Time: 'string',
          Datetime: 'string',
          JSON: 'string',
          BigInt: 'string',
          BigFloat: 'string',
          Opaque: 'any',
        },
      },
    },
  },
  hooks: {
    afterAllFileWrite: ['npm run prettier'], // optional
  },
}

export default config

Configuring Apollo Client

This example uses Supabase for the GraphQL server, but pg_graphql can be used independently.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import {
  ApolloClient,
  InMemoryCache,
  createHttpLink,
  defaultDataIdFromObject
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { relayStylePagination } from '@apollo/client/utilities'
import supabase from './supabase'

const cache = new InMemoryCache({
  dataIdFromObject(responseObject) {
    if ('nodeId' in responseObject) {
      return `${responseObject.nodeId}`
    }

    return defaultDataIdFromObject(responseObject)
  },
  possibleTypes: { Node: ['Todos'] } // optional, but useful to specify supertype-subtype relationships
  typePolicies: {
    Query: {
      fields: {
        todosCollection: relayStylePagination(), // example of paginating a collection
        node: {
          read(_, { args, toReference }) {
            const ref = toReference({
              nodeId: args?.nodeId,
            })

            return ref
          },
        },
      },
    },
  },
})

const httpLink = createHttpLink({
  uri: 'http://localhost:54321/graphql/v1',
})

const authLink = setContext(async (_, { headers }) => {
  const token = (await supabase.auth.getSession()).data.session?.access_token

  return {
    headers: {
      ...headers,
      Authorization: token ? `Bearer ${token}` : '',
    },
  }
})

const apolloClient = new ApolloClient({
  link: authLink.concat(httpLink),
  cache,
})

export default apolloClient
  • typePolicies.Query.fields.node is also optional, but useful for reducing cache misses. Learn more about Redirecting to cached data.

Example Query

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { useQuery } from '@apollo/client'
import { graphql } from './gql'

const allTodosQueryDocument = graphql(/* GraphQL */ `
  query AllTodos($cursor: Cursor) {
    todosCollection(first: 10, after: $cursor) {
      edges {
        node {
          nodeId
          title
        }
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
  }
`)

const TodoList = () => {
  const { data, fetchMore } = useQuery(allTodosQueryDocument)

  return (
    <>
      {data?.thingsCollection?.edges.map(({ node }) => (
        <Todo key={node.nodeId} title={node.title} />
      ))}
      {data?.thingsCollection?.pageInfo.hasNextPage && (
        <Button
          onClick={() => {
            fetchMore({
              variables: {
                cursor: data?.thingsCollection?.pageInfo.endCursor,
              },
            })
          }}
        >
          Load More
        </Button>
      )}
    </>
  )
}

export default TodoList