GraphQL

With Relay

Using pg_grapqhl 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

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 componentconst [todoCreateMutate, isMutationInFlight] = useMutation<TodoCreateMutation>(CreateTodoMutation)// inside your create todo functionconst connectionID = ConnectionHandler.getConnectionID( 'root', 'TodoList_query_todosCollection')todoCreateMutate({ variables: { input: { // ...new todo data }, connections: [connectionID], },})