Skip to content

Usage with Relay

pg_graphql implements the GraphQL Global Object Identification Specification (Node interface) and the GraphQL Cursor Connections Specification to be compatible with Relay.

Relay Setup

Pre-requisites

Follow the Relay Installation Guide.

Configuring the Relay Compiler

Modify your relay.config.js 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
module.exports = {
  // standard relay config options
  src: './src',
  language: 'typescript',
  schema: './data/schema.graphql',
  exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**'],
  // pg_graphql specific options
  schemaConfig: {
    nodeInterfaceIdField: 'nodeId',
    nodeInterfaceIdVariableName: 'nodeId',
  },
  customScalars: {
    UUID: 'string',
    Datetime: 'string',
    JSON: 'string',
    BigInt: 'string',
    BigFloat: 'string',
    Opaque: 'any',
  },
}
  • schemaConfig tells the Relay compiler where to find the nodeId field on the node interface
  • customScalars will improve Relay's type emission

Configuring your Relay Environment

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
import {
  Environment,
  FetchFunction,
  Network,
  RecordSource,
  Store,
} from 'relay-runtime'

import supabase, { SUPABASE_ANON_KEY, SUPABASE_URL } from './supabase'

const fetchQuery: FetchFunction = async (operation, variables) => {
  const {
    data: { session },
  } = await supabase.auth.getSession()

  const response = await fetch(`${SUPABASE_URL}/graphql/v1`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      apikey: SUPABASE_ANON_KEY,
      Authorization: `Bearer ${session?.access_token ?? SUPABASE_ANON_KEY}`,
    },
    body: JSON.stringify({
      query: operation.text,
      variables,
    }),
  })

  return await response.json()
}

const network = Network.create(fetchQuery)
const store = new Store(new RecordSource())

const environment = new Environment({
  network,
  store,
  getDataID: (node) => node.nodeId,
  missingFieldHandlers: [
    {
      handle(field, _record, argValues) {
        if (field.name === 'node' && 'nodeId' in argValues) {
          // If field is node(nodeId: $nodeId), look up the record by the value of $nodeId
          return argValues.nodeId
        }

        return undefined
      },
      kind: 'linked',
    },
  ],
})

export default environment
  • getDataID is the most important option to add, as it tells Relay how to store data correctly in the cache.
  • missingFieldHandlers is optional in this example but helps with Rendering Partially Cached Data.

Pagination

Say you are working on a Todo app and want to add pagination. You can use @connection and @prependNode to do this.

Fragment passed to usePaginationFragment()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fragment TodoList_query on Query
@argumentDefinitions(
  cursor: { type: "Cursor" }
  count: { type: "Int", defaultValue: 20 }
)
@refetchable(queryName: "TodoListPaginationQuery") {
  todosCollection(after: $cursor, first: $count)
    @connection(key: "TodoList_query_todosCollection") {
    pageInfo {
      hasNextPage
      endCursor
    }
    edges {
      cursor
      node {
        nodeId
        ...TodoItem_todos
      }
    }
  }
}

Mutation to create a new Todo

1
2
3
4
5
6
7
8
mutation TodoCreateMutation($input: TodosInsertInput!, $connections: [ID!]!) {
  insertIntoTodosCollection(objects: [$input]) {
    affectedCount
    records @prependNode(connections: $connections, edgeTypeName: "TodosEdge") {
      ...TodoItem_todos
    }
  }
}

Code to call the mutation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { ConnectionHandler, graphql, useMutation } from 'react-relay'

// inside a React component
const [todoCreateMutate, isMutationInFlight] =
  useMutation<TodoCreateMutation>(CreateTodoMutation)

// inside your create todo function
const connectionID = ConnectionHandler.getConnectionID(
  'root',
  'TodoList_query_todosCollection'
)

todoCreateMutate({
  variables: {
    input: {
      // ...new todo data
    },
    connections: [connectionID],
  },
})