Prepare for the PgBouncer and IPv4 deprecations on 26th January 2024

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:


_20
module.exports = {
_20
// standard relay config options
_20
src: './src',
_20
language: 'typescript',
_20
schema: './data/schema.graphql',
_20
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**'],
_20
// pg_graphql specific options
_20
schemaConfig: {
_20
nodeInterfaceIdField: 'nodeId',
_20
nodeInterfaceIdVariableName: 'nodeId',
_20
},
_20
customScalars: {
_20
UUID: 'string',
_20
Datetime: 'string',
_20
JSON: 'string',
_20
BigInt: 'string',
_20
BigFloat: 'string',
_20
Opaque: 'any',
_20
},
_20
}

  • 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.


_54
import {
_54
Environment,
_54
FetchFunction,
_54
Network,
_54
RecordSource,
_54
Store,
_54
} from 'relay-runtime'
_54
_54
import supabase, { SUPABASE_ANON_KEY, SUPABASE_URL } from './supabase'
_54
_54
const fetchQuery: FetchFunction = async (operation, variables) => {
_54
const {
_54
data: { session },
_54
} = await supabase.auth.getSession()
_54
_54
const response = await fetch(`${SUPABASE_URL}/graphql/v1`, {
_54
method: 'POST',
_54
headers: {
_54
'Content-Type': 'application/json',
_54
apikey: SUPABASE_ANON_KEY,
_54
Authorization: `Bearer ${session?.access_token ?? SUPABASE_ANON_KEY}`,
_54
},
_54
body: JSON.stringify({
_54
query: operation.text,
_54
variables,
_54
}),
_54
})
_54
_54
return await response.json()
_54
}
_54
_54
const network = Network.create(fetchQuery)
_54
const store = new Store(new RecordSource())
_54
_54
const environment = new Environment({
_54
network,
_54
store,
_54
getDataID: (node) => node.nodeId,
_54
missingFieldHandlers: [
_54
{
_54
handle(field, _record, argValues) {
_54
if (field.name === 'node' && 'nodeId' in argValues) {
_54
// If field is node(nodeId: $nodeId), look up the record by the value of $nodeId
_54
return argValues.nodeId
_54
}
_54
_54
return undefined
_54
},
_54
kind: 'linked',
_54
},
_54
],
_54
})
_54
_54
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()


_21
fragment TodoList_query on Query
_21
@argumentDefinitions(
_21
cursor: { type: "Cursor" }
_21
count: { type: "Int", defaultValue: 20 }
_21
)
_21
@refetchable(queryName: "TodoListPaginationQuery") {
_21
todosCollection(after: $cursor, first: $count)
_21
@connection(key: "TodoList_query_todosCollection") {
_21
pageInfo {
_21
hasNextPage
_21
endCursor
_21
}
_21
edges {
_21
cursor
_21
node {
_21
nodeId
_21
...TodoItem_todos
_21
}
_21
}
_21
}
_21
}

Mutation to create a new Todo


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

Code to call the mutation


_20
import { ConnectionHandler, graphql, useMutation } from 'react-relay'
_20
_20
// inside a React component
_20
const [todoCreateMutate, isMutationInFlight] =
_20
useMutation<TodoCreateMutation>(CreateTodoMutation)
_20
_20
// inside your create todo function
_20
const connectionID = ConnectionHandler.getConnectionID(
_20
'root',
_20
'TodoList_query_todosCollection'
_20
)
_20
_20
todoCreateMutate({
_20
variables: {
_20
input: {
_20
// ...new todo data
_20
},
_20
connections: [connectionID],
_20
},
_20
})