GraphQL

GraphQL API

Understanding the core concepts of the GraphQL API.


In our API, each SQL table is reflected as a set of GraphQL types. At a high level, tables become types and columns/foreign keys become fields on those types.

By default, PostgreSQL table and column names are not inflected when reflecting GraphQL names. For example, an account_holder table has GraphQL type name account_holder. In cases where SQL entities are named using snake_case, enable inflection to match GraphQL/Javascript conventions e.g. account_holder -> AccountHolder.

Individual table, column, and relationship names may also be manually overridden.

Primary Keys (Required)

Every table must have a primary key for it to be exposed in the GraphQL schema. For example, the following Blog table will be available in the GraphQL schema as blogCollection since it has a primary key named id:


_10
create table "Blog"(
_10
id serial primary key,
_10
name varchar(255) not null,
_10
);

But the following table will not be exposed because it doesn't have a primary key:


_10
create table "Blog"(
_10
id int,
_10
name varchar(255) not null,
_10
);

QueryType

The Query type is the entrypoint for all read access into the graph.

Node

The node interface allows for retrieving records that are uniquely identifiable by a globally unique nodeId: ID! field. For more information about nodeId, see nodeId.

SQL Setup


_10
create table "Blog"(
_10
id serial primary key,
_10
name varchar(255) not null,
_10
description varchar(255),
_10
"createdAt" timestamp not null,
_10
"updatedAt" timestamp not null
_10
);

GraphQL Types


_10
"""The root type for querying data"""
_10
type Query {
_10
_10
"""Retrieve a record by its `ID`"""
_10
node(nodeId: ID!): Node
_10
_10
}

To query the node interface effectively, use inline fragments to specify which fields to return for each type.

Example


_12
{
_12
node(
_12
nodeId: "WyJwdWJsaWMiLCAiYmxvZyIsIDFd"
_12
) {
_12
nodeId
_12
# Inline fragment for `Blog` type
_12
... on Blog {
_12
name
_12
description
_12
}
_12
}
_12
}

Collections

Each table has top level entry in the Query type for selecting records from that table. Collections return a connection type and can be paginated, filtered, and sorted using the available arguments.

SQL Setup


_10
create table "Blog"(
_10
id serial primary key,
_10
name varchar(255) not null,
_10
description varchar(255),
_10
"createdAt" timestamp not null,
_10
"updatedAt" timestamp not null
_10
);

GraphQL Types


_30
"""The root type for querying data"""
_30
type Query {
_30
_30
"""A pagable collection of type `Blog`"""
_30
blogCollection(
_30
_30
"""Query the first `n` records in the collection"""
_30
first: Int
_30
_30
"""Query the last `n` records in the collection"""
_30
last: Int
_30
_30
"""Query values in the collection before the provided cursor"""
_30
before: Cursor
_30
_30
"""Query values in the collection after the provided cursor"""
_30
after: Cursor
_30
_30
"""
_30
Skip n values from the after cursor. Alternative to cursor pagination. Backward pagination not supported.
_30
"""
_30
offset: Int
_30
_30
"""Filters to apply to the results set when querying from the collection"""
_30
filter: BlogFilter
_30
_30
"""Sort order to apply to the collection"""
_30
orderBy: [BlogOrderBy!]
_30
): BlogConnection
_30
}

Connection types are the primary interface to returning records from a collection.

Connections wrap a result set with some additional metadata.


_12
type BlogConnection {
_12
_12
# Count of all records matching the *filter* criteria
_12
totalCount: Int!
_12
_12
# Pagination metadata
_12
pageInfo: PageInfo!
_12
_12
# Result set
_12
edges: [BlogEdge!]!
_12
_12
}

Pagination

Keyset Pagination

Paginating forwards and backwards through collections is handled using the first, last, before, and after parameters, following the relay spec.


_20
type Query {
_20
_20
blogCollection(
_20
_20
"""Query the first `n` records in the collection"""
_20
first: Int
_20
_20
"""Query the last `n` records in the collection"""
_20
last: Int
_20
_20
"""Query values in the collection before the provided cursor"""
_20
before: Cursor
_20
_20
"""Query values in the collection after the provided cursor"""
_20
after: Cursor
_20
_20
...truncated...
_20
_20
): BlogConnection
_20
}

Metadata relating to the current page of a result set is available on the pageInfo field of the connection type returned from a collection.


_14
type PageInfo {
_14
_14
# unique identifier of the first record within the query
_14
startCursor: String
_14
_14
# unique identifier of the last record within the query
_14
endCursor: String
_14
_14
# is another page of content available
_14
hasNextPage: Boolean!
_14
_14
# is another page of content available
_14
hasPreviousPage: Boolean!
_14
}

To paginate forward in the collection, use the first and after arguments. To retrieve the first page, the after argument should be null or absent.

Example


_19
{
_19
blogCollection(
_19
first: 2,
_19
after: null
_19
) {
_19
pageInfo {
_19
startCursor
_19
endCursor
_19
hasPreviousPage
_19
hasNextPage
_19
}
_19
edges {
_19
cursor
_19
node {
_19
id
_19
}
_19
}
_19
}
_19
}

To retrieve the next page, provide the cursor value from data.blogCollection.pageInfo.endCursor to the after argument of another query.


_10
{
_10
blogCollection(
_10
first: 2,
_10
after: "WzJd"
_10
) {
_10
...truncated...
_10
}

once the collection has been fully enumerated, data.blogConnection.pageInfo.hasNextPage returns false.

To paginate backwards through a collection, repeat the process substituting first -> last, after -> before, hasNextPage -> hasPreviousPage

Offset Pagination

In addition to keyset pagination, collections may also be paged using first and offset, which operates like SQL's limit and offset to skip offset number of records in the results.


_10
{
_10
blogCollection(
_10
first: 2,
_10
offset: 2
_10
) {
_10
...truncated...
_10
}

Filtering

To filter the result set, use the filter argument.


_11
type Query {
_11
_11
blogCollection(
_11
_11
"""Filters to apply to the results set when querying from the collection"""
_11
filter: BlogFilter
_11
_11
...truncated...
_11
_11
): BlogConnection
_11
}

Where the <Table>Filter type enumerates filterable fields and their associated <Type>Filter.


_12
input BlogFilter {
_12
nodeId: IDFilter
_12
id: IntFilter
_12
name: StringFilter
_12
description: StringFilter
_12
tags: StringListFilter
_12
createdAt: DatetimeFilter
_12
updatedAt: DatetimeFilter
_12
and: [BlogFilter!]
_12
or: [BlogFilter!]
_12
not: BlogFilter
_12
}

The following list shows the operators that may be available on <Type>Filter types.

OperatorDescription
eqEqual To
neqNot Equal To
gtGreater Than
gteGreater Than Or Equal To
inContained by Value List
ltLess Than
lteLess Than Or Equal To
isNull or Not Null
startsWithStarts with prefix
likePattern Match. '%' as wildcard
ilikePattern Match. '%' as wildcard. Case Insensitive
regexPOSIX Regular Expression Match
iregexPOSIX Regular Expression Match. Case Insensitive
containsContains. Applies to array columns only.
containedByContained in. Applies to array columns only.
overlapsOverlap (have points in common). Applies to array columns only.

Not all operators are available on every <Type>Filter type. For example, UUIDFilter only supports eq and neq because UUIDs are not ordered.

Example: simple


_12
{
_12
blogCollection(
_12
filter: {id: {lt: 3}},
_12
) {
_12
edges {
_12
cursor
_12
node {
_12
id
_12
}
_12
}
_12
}
_12
}

Example: array column

The contains filter is used to return results where all the elements in the input array appear in the array column.

contains

Filter Query"


_15
{
_15
blogCollection(
_15
filter: {tags: {contains: ["tech", "innovation"]}},
_15
) {
_15
edges {
_15
cursor
_15
node {
_15
id
_15
name
_15
tags
_15
createdAt
_15
}
_15
}
_15
}
_15
}

contains

Filter Result"


_26
{
_26
"data": {
_26
"blogCollection": {
_26
"edges": [
_26
{
_26
"node": {
_26
"id": 1,
_26
"name": "A: Blog 1",
_26
"createdAt": "2023-07-24T04:01:09.882781",
_26
"tags": ["tech", "innovation"]
_26
},
_26
"cursor": "WzFd"
_26
},
_26
{
_26
"node": {
_26
"id": 2,
_26
"name": "A: Blog 2",
_26
"createdAt": "2023-07-24T04:01:09.882781",
_26
"tags": ["tech", "innovation", "entrepreneurship"]
_26
},
_26
"cursor": "WzJd"
_26
}
_26
]
_26
}
_26
}
_26
}

The contains filter can also accept a single scalar.

contains

Filter with Scalar Query"


_15
{
_15
blogCollection(
_15
filter: {tags: {contains: "tech"}},
_15
) {
_15
edges {
_15
cursor
_15
node {
_15
id
_15
name
_15
tags
_15
createdAt
_15
}
_15
}
_15
}
_15
}

contains

Filter with Scalar Result"


_26
{
_26
"data": {
_26
"blogCollection": {
_26
"edges": [
_26
{
_26
"node": {
_26
"id": 1,
_26
"name": "A: Blog 1",
_26
"createdAt": "2023-07-24T04:01:09.882781",
_26
"tags": ["tech", "innovation"]
_26
},
_26
"cursor": "WzFd"
_26
},
_26
{
_26
"node": {
_26
"id": 2,
_26
"name": "A: Blog 2",
_26
"createdAt": "2023-07-24T04:01:09.882781",
_26
"tags": ["tech", "innovation", "entrepreneurship"]
_26
},
_26
"cursor": "WzJd"
_26
}
_26
]
_26
}
_26
}
_26
}

The containedBy filter is used to return results where every element of the array column appears in the input array.

containedBy

Filter Query"


_15
{
_15
blogCollection(
_15
filter: {tags: {containedBy: ["entrepreneurship", "innovation", "tech"]}},
_15
) {
_15
edges {
_15
cursor
_15
node {
_15
id
_15
name
_15
tags
_15
createdAt
_15
}
_15
}
_15
}
_15
}

containedBy

Filter Result"


_26
{
_26
"data": {
_26
"blogCollection": {
_26
"edges": [
_26
{
_26
"node": {
_26
"id": 1,
_26
"name": "A: Blog 1",
_26
"createdAt": "2023-07-24T04:01:09.882781",
_26
"tags": ["tech", "innovation"]
_26
},
_26
"cursor": "WzFd"
_26
},
_26
{
_26
"node": {
_26
"id": 3,
_26
"name": "A: Blog 3",
_26
"createdAt": "2023-07-24T04:01:09.882781",
_26
"tags": ["innovation", "entrepreneurship"]
_26
},
_26
"cursor": "WzNd"
_26
}
_26
]
_26
}
_26
}
_26
}

The containedBy filter can also accept a single scalar. In this case, only results where the only element in the array column is the input scalar are returned.

containedBy

Filter with Scalar Query"


_15
{
_15
blogCollection(
_15
filter: {tags: {containedBy: "travel"}},
_15
) {
_15
edges {
_15
cursor
_15
node {
_15
id
_15
name
_15
tags
_15
createdAt
_15
}
_15
}
_15
}
_15
}

containedBy

Filter with Scalar Result"


_17
{
_17
"data": {
_17
"blogCollection": {
_17
"edges": [
_17
{
_17
"node": {
_17
"id": 4,
_17
"name": "A: Blog 4",
_17
"createdAt": "2023-07-24T04:01:09.882781",
_17
"tags": ["travel"]
_17
},
_17
"cursor": "WzPd"
_17
}
_17
]
_17
}
_17
}
_17
}

The overlaps filter is used to return results where the array column and the input array have at least one element in common.

overlaps

Filter Query"


_15
{
_15
blogCollection(
_15
filter: {tags: {overlaps: ["tech", "travel"]}},
_15
) {
_15
edges {
_15
cursor
_15
node {
_15
id
_15
name
_15
tags
_15
createdAt
_15
}
_15
}
_15
}
_15
}

overlaps

Filter Result"


_35
{
_35
"data": {
_35
"blogCollection": {
_35
"edges": [
_35
{
_35
"node": {
_35
"id": 1,
_35
"name": "A: Blog 1",
_35
"createdAt": "2023-07-24T04:01:09.882781",
_35
"tags": ["tech", "innovation"]
_35
},
_35
"cursor": "WzFd"
_35
},
_35
{
_35
"node": {
_35
"id": 2,
_35
"name": "A: Blog 2",
_35
"createdAt": "2023-07-24T04:01:09.882781",
_35
"tags": ["tech", "innovation", "entrepreneurship"]
_35
},
_35
"cursor": "WzJd"
_35
},
_35
{
_35
"node": {
_35
"id": 4,
_35
"name": "A: Blog 4",
_35
"createdAt": "2023-07-24T04:01:09.882781",
_35
"tags": ["travel"]
_35
},
_35
"cursor": "WzPd"
_35
}
_35
]
_35
}
_35
}
_35
}

Example: and/or

Multiple filters can be combined with and, or and not operators. The and and or operators accept a list of <Type>Filter.

and

Filter Query"


_20
{
_20
blogCollection(
_20
filter: {
_20
and: [
_20
{id: {eq: 1}}
_20
{name: {eq: "A: Blog 1"}}
_20
]
_20
}
_20
) {
_20
edges {
_20
cursor
_20
node {
_20
id
_20
name
_20
description
_20
createdAt
_20
}
_20
}
_20
}
_20
}

and

Filter Result"


_17
{
_17
"data": {
_17
"blogCollection": {
_17
"edges": [
_17
{
_17
"node": {
_17
"id": 1,
_17
"name": "A: Blog 1",
_17
"createdAt": "2023-07-24T04:01:09.882781",
_17
"description": "a desc1"
_17
},
_17
"cursor": "WzFd"
_17
}
_17
]
_17
}
_17
}
_17
}

or

Filter Query"


_20
{
_20
blogCollection(
_20
filter: {
_20
or: [
_20
{id: {eq: 1}}
_20
{name: {eq: "A: Blog 2"}}
_20
]
_20
}
_20
) {
_20
edges {
_20
cursor
_20
node {
_20
id
_20
name
_20
description
_20
createdAt
_20
}
_20
}
_20
}
_20
}

or

Filter Result"


_26
{
_26
"data": {
_26
"blogCollection": {
_26
"edges": [
_26
{
_26
"node": {
_26
"id": 1,
_26
"name": "A: Blog 1",
_26
"createdAt": "2023-07-24T04:01:09.882781",
_26
"description": "a desc1"
_26
},
_26
"cursor": "WzFd"
_26
},
_26
{
_26
"node": {
_26
"id": 2,
_26
"name": "A: Blog 2",
_26
"createdAt": "2023-07-24T04:01:09.882781",
_26
"description": "a desc2"
_26
},
_26
"cursor": "WzJd"
_26
}
_26
]
_26
}
_26
}
_26
}

Example: not

not accepts a single <Type>Filter.

not

Filter Query"


_17
{
_17
blogCollection(
_17
filter: {
_17
not: {id: {eq: 1}}
_17
}
_17
) {
_17
edges {
_17
cursor
_17
node {
_17
id
_17
name
_17
description
_17
createdAt
_17
}
_17
}
_17
}
_17
}

not

Filter Result"


_35
{
_35
"data": {
_35
"blogCollection": {
_35
"edges": [
_35
{
_35
"node": {
_35
"id": 2,
_35
"name": "A: Blog 2",
_35
"createdAt": "2023-07-24T04:01:09.882781",
_35
"description": "a desc2"
_35
},
_35
"cursor": "WzJd"
_35
},
_35
{
_35
"node": {
_35
"id": 3,
_35
"name": "A: Blog 3",
_35
"createdAt": "2023-07-24T04:01:09.882781",
_35
"description": "a desc3"
_35
},
_35
"cursor": "WzNd"
_35
},
_35
{
_35
"node": {
_35
"id": 4,
_35
"name": "B: Blog 3",
_35
"createdAt": "2023-07-24T04:01:09.882781",
_35
"description": "b desc1"
_35
},
_35
"cursor": "WzRd"
_35
}
_35
]
_35
}
_35
}
_35
}

Example: nested composition

The and, or and not operators can be arbitrarily nested inside each other.


_21
{
_21
blogCollection(
_21
filter: {
_21
or: [
_21
{ id: { eq: 1 } }
_21
{ id: { eq: 2 } }
_21
{ and: [{ id: { eq: 3 }, not: { name: { eq: "A: Blog 2" } } }] }
_21
]
_21
}
_21
) {
_21
edges {
_21
cursor
_21
node {
_21
id
_21
name
_21
description
_21
createdAt
_21
}
_21
}
_21
}
_21
}

Example: empty

Empty filters are ignored, i.e. they behave as if the operator was not specified at all.


_17
{
_17
blogCollection(
_17
filter: {
_17
and: [], or: [], not: {}
_17
}
_17
) {
_17
edges {
_17
cursor
_17
node {
_17
id
_17
name
_17
description
_17
createdAt
_17
}
_17
}
_17
}
_17
}

Example: implicit and

Multiple column filters at the same level will be implicitly combined with boolean and. In the following example the id: {eq: 1} and name: {eq: "A: Blog 1"} will be anded.


_21
{
_21
blogCollection(
_21
filter: {
_21
# Equivalent to not: { and: [{id: {eq: 1}}, {name: {eq: "A: Blog 1"}}]}
_21
not: {
_21
id: {eq: 1}
_21
name: {eq: "A: Blog 1"}
_21
}
_21
}
_21
) {
_21
edges {
_21
cursor
_21
node {
_21
id
_21
name
_21
description
_21
createdAt
_21
}
_21
}
_21
}
_21
}

This means that an and filter can be often be simplified. In the following example all queries are equivalent and produce the same result.

and

Query"


_21
{
_21
blogCollection(
_21
filter: {
_21
and: [
_21
{id: {gt: 0}}
_21
{id: {lt: 2}}
_21
{name: {eq: "A: Blog 1"}}
_21
]
_21
}
_21
) {
_21
edges {
_21
cursor
_21
node {
_21
id
_21
name
_21
description
_21
createdAt
_21
}
_21
}
_21
}
_21
}

Be aware that the above simplification only works for the and operator. If you try it with an or operator it will behave like an and.


_21
{
_21
blogCollection(
_21
filter: {
_21
# This is really an `and` in `or`'s clothing
_21
or: {
_21
id: {eq: 1}
_21
name: {eq: "A: Blog 2"}
_21
}
_21
}
_21
) {
_21
edges {
_21
cursor
_21
node {
_21
id
_21
name
_21
description
_21
createdAt
_21
}
_21
}
_21
}
_21
}

This is because according to the rules of GraphQL list input coercion, if a value passed to an input of list type is not a list, then it is coerced to a list of a single item. So in the above example or: {id: {eq: 1}, name: {eq: "A: Blog 2}} will be coerced into or: [{id: {eq: 1}, name: {eq: "A: Blog 2}}] which is equivalent to or: [and: [{id: {eq: 1}}, {name: {eq: "A: Blog 2}}}] due to implicit anding.

The and, or and not operators also work with update and delete mutations.

Ordering

The default order of results is defined by the underlying table's primary key column in ascending order. That default can be overridden by passing an array of <Table>OrderBy to the collection's orderBy argument.


_11
type Query {
_11
_11
blogCollection(
_11
_11
"""Sort order to apply to the collection"""
_11
orderBy: [BlogOrderBy!]
_11
_11
...truncated...
_11
_11
): BlogConnection
_11
}

Example


_11
{
_11
blogCollection(
_11
orderBy: [{id: DescNullsLast}]
_11
) {
_11
edges {
_11
node {
_11
id
_11
}
_11
}
_11
}
_11
}

Note, only one key value pair may be provided to each element of the input array. For example, [{name: AscNullsLast}, {id: AscNullFirst}] is valid. Passing multiple key value pairs in a single element of the input array e.g. [{name: AscNullsLast, id: AscNullFirst}], is invalid.

MutationType

The Mutation type is the entrypoint for mutations/edits.

Each table has top level entry in the Mutation type for inserting insertInto<Table>Collection, updating update<Table>Collection and deleting deleteFrom<Table>Collection.

SQL Setup


_10
create table "Blog"(
_10
id serial primary key,
_10
name varchar(255) not null,
_10
description varchar(255),
_10
"createdAt" timestamp not null default now(),
_10
"updatedAt" timestamp
_10
);


_41
"""The root type for creating and mutating data"""
_41
type Mutation {
_41
_41
"""Adds one or more `BlogInsertResponse` records to the collection"""
_41
insertIntoBlogCollection(
_41
_41
"""Records to add to the Blog collection"""
_41
objects: [BlogInsertInput!]!
_41
_41
): BlogInsertResponse
_41
_41
"""Updates zero or more records in the collection"""
_41
updateBlogCollection(
_41
"""
_41
Fields that are set will be updated for all records matching the `filter`
_41
"""
_41
set: BlogUpdateInput!
_41
_41
"""Restricts the mutation's impact to records matching the critera"""
_41
filter: BlogFilter
_41
_41
"""
_41
The maximum number of records in the collection permitted to be affected
_41
"""
_41
atMost: Int! = 1
_41
_41
): BlogUpdateResponse!
_41
_41
"""Deletes zero or more records from the collection"""
_41
deleteFromBlogCollection(
_41
"""Restricts the mutation's impact to records matching the critera"""
_41
filter: BlogFilter
_41
_41
"""
_41
The maximum number of records in the collection permitted to be affected
_41
"""
_41
atMost: Int! = 1
_41
_41
): BlogDeleteResponse!
_41
_41
}

Insert

To add records to a collection, use the insertInto<Table>Collection field on the Mutation type.

SQL Setup


_10
create table "Blog"(
_10
id serial primary key,
_10
name varchar(255) not null,
_10
description varchar(255),
_10
"createdAt" timestamp not null default now(),
_10
"updatedAt" timestamp
_10
);

GraphQL Types


_12
"""The root type for creating and mutating data"""
_12
type Mutation {
_12
_12
"""Adds one or more `BlogInsertResponse` records to the collection"""
_12
insertIntoBlogCollection(
_12
_12
"""Records to add to the Blog collection"""
_12
objects: [BlogInsertInput!]!
_12
_12
): BlogInsertResponse
_12
_12
}

Where elements in the objects array are inserted into the underlying table.

Example


_14
mutation {
_14
insertIntoBlogCollection(
_14
objects: [
_14
{name: "foo"},
_14
{name: "bar"},
_14
]
_14
) {
_14
affectedCount
_14
records {
_14
id
_14
name
_14
}
_14
}
_14
}

Update

To update records in a collection, use the update<Table>Collection field on the Mutation type.

SQL Setup


_10
create table "Blog"(
_10
id serial primary key,
_10
name varchar(255) not null,
_10
description varchar(255),
_10
"createdAt" timestamp not null default now(),
_10
"updatedAt" timestamp
_10
);

GraphQL Types


_21
"""The root type for creating and mutating data"""
_21
type Mutation {
_21
_21
"""Updates zero or more records in the collection"""
_21
updateBlogCollection(
_21
"""
_21
Fields that are set will be updated for all records matching the `filter`
_21
"""
_21
set: BlogUpdateInput!
_21
_21
"""Restricts the mutation's impact to records matching the critera"""
_21
filter: BlogFilter
_21
_21
"""
_21
The maximum number of records in the collection permitted to be affected
_21
"""
_21
atMost: Int! = 1
_21
_21
): BlogUpdateResponse!
_21
_21
}

Where the set argument is a key value pair describing the values to update, filter controls which records should be updated, and atMost restricts the maximum number of records that may be impacted. If the number of records impacted by the mutation exceeds the atMost parameter the operation will return an error.

Example


_12
mutation {
_12
updateBlogCollection(
_12
set: {name: "baz"}
_12
filter: {id: {eq: 1}}
_12
) {
_12
affectedCount
_12
records {
_12
id
_12
name
_12
}
_12
}
_12
}

Delete

To remove records from a collection, use the deleteFrom<Table>Collection field on the Mutation type.

SQL Setup


_10
create table "Blog"(
_10
id serial primary key,
_10
name varchar(255) not null,
_10
description varchar(255),
_10
"createdAt" timestamp not null default now(),
_10
"updatedAt" timestamp
_10
);

GraphQL Types


_16
"""The root type for creating and mutating data"""
_16
type Mutation {
_16
_16
"""Deletes zero or more records from the collection"""
_16
deleteFromBlogCollection(
_16
"""Restricts the mutation's impact to records matching the critera"""
_16
filter: BlogFilter
_16
_16
"""
_16
The maximum number of records in the collection permitted to be affected
_16
"""
_16
atMost: Int! = 1
_16
_16
): BlogDeleteResponse!
_16
_16
}

Where filter controls which records should be deleted and atMost restricts the maximum number of records that may be deleted. If the number of records impacted by the mutation exceeds the atMost parameter the operation will return an error.

Example


_11
mutation {
_11
deleteFromBlogCollection(
_11
filter: {id: {eq: 1}}
_11
) {
_11
affectedCount
_11
records {
_11
id
_11
name
_11
}
_11
}
_11
}

Concepts

nodeId

The base GraphQL type for every table with a primary key is automatically assigned a nodeId: ID! field. That value, can be passed to the node entrypoint of the Query type to retrieve its other fields. nodeId may also be used as a caching key.

SQL Setup


_10
create table "Blog"(
_10
id serial primary key,
_10
name varchar(255) not null
_10
);

GraphQL Types


_10
type Blog {
_10
nodeId: ID! # this field
_10
id: Int!
_10
name: String!
_10
}

Relationships

Relationships between collections in the Graph are derived from foreign keys.

One-to-Many

A foreign key on table A referencing table B defines a one-to-many relationship from table A to table B.

SQL Setup


_11
create table "Blog"(
_11
id serial primary key,
_11
name varchar(255) not null
_11
);
_11
_11
create table "BlogPost"(
_11
id serial primary key,
_11
"blogId" integer not null references "Blog"(id),
_11
title varchar(255) not null,
_11
body varchar(10000)
_11
);

GraphQL Types


_35
type Blog {
_35
_35
# globally unique identifier
_35
nodeId: ID!
_35
_35
id: Int!
_35
name: String!
_35
description: String
_35
_35
blogPostCollection(
_35
"""Query the first `n` records in the collection"""
_35
first: Int
_35
_35
"""Query the last `n` records in the collection"""
_35
last: Int
_35
_35
"""Query values in the collection before the provided cursor"""
_35
before: Cursor
_35
_35
"""Query values in the collection after the provided cursor"""
_35
after: Cursor
_35
_35
"""
_35
Skip n values from the after cursor. Alternative to cursor pagination. Backward pagination not supported.
_35
"""
_35
offset: Int
_35
_35
"""Filters to apply to the results set when querying from the collection"""
_35
filter: BlogPostFilter
_35
_35
"""Sort order to apply to the collection"""
_35
orderBy: [BlogPostOrderBy!]
_35
): BlogPostConnection
_35
_35
}

Where blogPostCollection exposes the full Query interface to BlogPosts.

Example


_17
{
_17
blogCollection {
_17
edges {
_17
node {
_17
name
_17
blogPostCollection {
_17
edges {
_17
node {
_17
id
_17
title
_17
}
_17
}
_17
}
_17
}
_17
}
_17
}
_17
}

Many-to-One

A foreign key on table A referencing table B defines a many-to-one relationship from table B to table A.

SQL Setup


_11
create table "Blog"(
_11
id serial primary key,
_11
name varchar(255) not null
_11
);
_11
_11
create table "BlogPost"(
_11
id serial primary key,
_11
"blogId" integer not null references "Blog"(id),
_11
title varchar(255) not null,
_11
body varchar(10000)
_11
);

GraphQL Types


_10
type BlogPost {
_10
nodeId: ID!
_10
id: Int!
_10
blogId: Int!
_10
title: String!
_10
body: String
_10
_10
blog: Blog
_10
}

Where blog exposes the Blog record associated with the BlogPost.


_12
{
_12
blogPostCollection {
_12
edges {
_12
node {
_12
title
_12
blog {
_12
name
_12
}
_12
}
_12
}
_12
}
_12
}

One-to-One

A one-to-one relationship is defined by a foreign key on table A referencing table B where the columns making up the foreign key on table A are unique.

SQL Setup


_10
create table "EmailAddress"(
_10
id serial primary key,
_10
address text unique not null
_10
);
_10
_10
create table "Employee"(
_10
id serial primary key,
_10
name text not null,
_10
email_address_id int unique references "EmailAddress"(id)
_10
);

GraphQL Types


_10
type Employee {
_10
nodeId: ID!
_10
id: Int!
_10
name: String!
_10
emailAddressId: Int
_10
emailAddress: EmailAddress
_10
}

Example


_19
{
_19
"data": {
_19
"employeeCollection": {
_19
"edges": [
_19
{
_19
"node": {
_19
"name": "Foo Barington",
_19
"emailAddress": {
_19
"address": "[email protected]",
_19
"employee": {
_19
"name": "Foo Barington"
_19
}
_19
}
_19
}
_19
}
_19
]
_19
}
_19
}
_19
}

Custom Scalars

Due to differences among the types supported by PostgreSQL, JSON, and GraphQL, pg_graphql adds several new Scalar types to handle PostgreSQL builtins that require special handling.

JSON

pg_graphql serializes json and jsonb data types as String under the custom scalar name JSON.


_10
scalar JSON

Example

Given the setup


_10
create table "User"(
_10
id bigserial primary key,
_10
config jsonb
_10
);
_10
_10
insert into "User"(config)
_10
values (jsonb_build_object('palette', 'dark-mode'));

The query


_10
{
_10
userCollection {
_10
edges {
_10
node {
_10
config
_10
}
_10
}
_10
}
_10
}

The returns the following data. Note that config is serialized as a string


_13
{
_13
"data": {
_13
"userCollection": {
_13
"edges": [
_13
{
_13
"node": {
_13
"config": "{\"palette\": \"dark-mode\"}"
_13
}
_13
}
_13
]
_13
}
_13
}
_13
}

Use serialized JSON strings when updating or inserting JSON fields via the GraphQL API.

JSON does not currently support filtering.

BigInt

PostgreSQL bigint and bigserial types are 64 bit integers. In contrast, JSON supports 32 bit integers.

Since PostgreSQL bigint values may be outside the min/max range allowed by JSON, they are represented in the GraphQL schema as BigInts and values are serialized as strings.


_12
scalar BigInt
_12
_12
input BigIntFilter {
_12
eq: BigInt
_12
gt: BigInt
_12
gte: BigInt
_12
in: [BigInt!]
_12
lt: BigInt
_12
lte: BigInt
_12
neq: BigInt
_12
is: FilterIs
_12
}

Example

Given the setup


_10
create table "Person"(
_10
id bigserial primary key,
_10
name text
_10
);
_10
_10
insert into "Person"(name)
_10
values ('J. Bazworth');

The query


_10
{
_10
personCollection {
_10
edges {
_10
node {
_10
id
_10
name
_10
}
_10
}
_10
}
_10
}

The returns the following data. Note that id is serialized as a string


_14
{
_14
"data": {
_14
"personCollection": {
_14
"edges": [
_14
{
_14
"node": {
_14
"id": "1",
_14
"name": "Foo Barington",
_14
}
_14
}
_14
]
_14
}
_14
}
_14
}

BigFloat

PostgreSQL's numeric type supports arbitrary precision floating point values. JSON's float is limited to 64-bit precision.

Since a PostgreSQL numeric may require more precision than can be handled by JSON, numeric types are represented in the GraphQL schema as BigFloat and values are serialized as strings.


_12
scalar BigFloat
_12
_12
input BigFloatFilter {
_12
eq: BigFloat
_12
gt: BigFloat
_12
gte: BigFloat
_12
in: [BigFloat!]
_12
lt: BigFloat
_12
lte: BigFloat
_12
neq: BigFloat
_12
is: FilterIs
_12
}

Example

Given the SQL setup


_10
create table "GeneralLedger"(
_10
id serial primary key,
_10
amount numeric(10,2)
_10
);
_10
_10
insert into "GeneralLedger"(amount)
_10
values (22.15);

The query


_10
{
_10
generalLedgerCollection {
_10
edges {
_10
node {
_10
id
_10
amount
_10
}
_10
}
_10
}
_10
}

The returns the following data. Note that amount is serialized as a string


_14
{
_14
"data": {
_14
"generalLedgerCollection": {
_14
"edges": [
_14
{
_14
"node": {
_14
"id": 1,
_14
"amount": "22.15",
_14
}
_14
}
_14
]
_14
}
_14
}
_14
}

Opaque

PostgreSQL's type system is extensible and not all types handle all operations e.g. filtering with like. To account for these, pg_graphql introduces a scalar Opaque type. The Opaque type uses PostgreSQL's to_json method to serialize values. That allows complex or unknown types to be included in the schema by delegating handling to the client.


_10
scalar Opaque
_10
_10
input OpaqueFilter {
_10
eq: Opaque
_10
is: FilterIs
_10
}