Skip to content
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:

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.

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()

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

mutation TodoCreateMutation($input: TodosInsertInput!, $connections: [ID!]!) {
insertIntoTodosCollection(objects: [$input]) {
affectedCount
records @prependNode(connections: $connections, edgeTypeName: "TodosEdge") {
...TodoItem_todos
}
}
}

Code to call the mutation

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