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',
  },
  customScalarTypes: {
    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
  • customScalarTypes will improve Relay's type emission

Note

For Relay versions older than v16.2.0, it should be named customScalars instead.

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],
  },
})