GraphQL

Configuration & Customization

Extra configuration options can be set on SQL entities using comment directives.


Extra configuration options can be set on SQL entities using comment directives.

Comment Directives#

Comment directives are snippets of configuration associated with SQL entities that alter how those entities behave.

The format of a comment directive is

1
@graphql(<JSON>)

Inflection#

Inflection describes how SQL entities' names are transformed into GraphQL type and field names. By default, inflection is disabled and SQL names are literally interpolated such that

1
create table "BlogPost"(
2
id int primary key,
3
...
4
);

results in GraphQL type names like

1
BlogPost
2
BlogPostEdge
3
BlogPostConnection
4
...

Since snake case is a common casing structure for SQL types, pg_graphql support basic inflection from snake_case to PascalCase for type names, and snake_case to camelCase for field names to match Javascript conventions.

The inflection directive can be applied at the schema level with:

1
comment on schema <schema_name> is e'@graphql({"inflect_names": true})';

for example

1
comment on schema public is e'@graphql({"inflect_names": true})';
2
3
create table blog_post(
4
id int primary key,
5
...
6
);

similarly would generated the GraphQL type names

1
BlogPost
2
BlogPostEdge
3
BlogPostConnection
4
...

For more fine grained adjustments to reflected names, see renaming.

Max Rows#

The default page size for collections is 30 entries. To adjust the number of entries on each page, set a max_rows directive on the relevant schema entity, table or view.

For example, to increase the max rows per page for each table in the public schema:

1
comment on schema public is e'@graphql({"max_rows": 100})';

To limit the max rows per page for the blog_post table and Person view:

1
comment on table blog_post is e'@graphql({"max_rows": 20})';
2
comment on view "Person" is e'@graphql({"primary_key_columns": ["id"], "max_rows": 10})';

The max_rows value falls back to the parent object if it is missing on the current object. For example, if a table doesn't have max_rows set, the value set on the table's schema will be used. If the schema also doesn't have max_rows set, then it falls back to default value 30. The parent object of a view is the schema, not the table on which the view is created.

totalCount#

totalCount is an opt-in field that extends a table's Connection type. It provides a count of the rows that match the query's filters, and ignores pagination arguments.

1
type BlogPostConnection {
2
edges: [BlogPostEdge!]!
3
pageInfo: PageInfo!
4
5
"""The total number of records matching the `filter` criteria"""
6
totalCount: Int! # this field
7
}

to enable totalCount for a table, use the directive

1
comment on table "BlogPost" is e'@graphql({"totalCount": {"enabled": true}})';

for example

1
create table "BlogPost"(
2
id serial primary key,
3
email varchar(255) not null
4
);
5
comment on table "BlogPost" is e'@graphql({"totalCount": {"enabled": true}})';

Aggregate#

The aggregate field is an opt-in field that extends a table's Connection type. It provides various aggregate functions like count, sum, avg, min, and max that operate on the collection of records that match the query's filters.

1
type BlogPostConnection {
2
edges: [BlogPostEdge!]!
3
pageInfo: PageInfo!
4
5
"""Aggregate functions calculated on the collection of `BlogPost`"""
6
aggregate: BlogPostAggregate # this field
7
}

To enable the aggregate field for a table, use the directive:

1
comment on table "BlogPost" is e'@graphql({"aggregate": {"enabled": true}})';

For example:

1
create table "BlogPost"(
2
id serial primary key,
3
title varchar(255) not null,
4
rating int not null
5
);
6
comment on table "BlogPost" is e'@graphql({"aggregate": {"enabled": true}})';

You can combine both totalCount and aggregate directives:

1
comment on table "BlogPost" is e'@graphql({"totalCount": {"enabled": true}, "aggregate": {"enabled": true}})';

Renaming#

Table's Type#

Use the "name" JSON key to override a table's type name.

1
create table account(
2
id serial primary key
3
);
4
5
comment on table public.account is
6
e'@graphql({"name": "AccountHolder"})';

results in:

1
type AccountHolder { # previously: "Account"
2
id: Int!
3
}

Column's Field Name#

Use the "name" JSON key to override a column's field name.

1
create table public."Account"(
2
id serial primary key,
3
email text
4
);
5
6
comment on column "Account".email is
7
e'@graphql({"name": "emailAddress"})';

results in:

1
type Account {
2
nodeId: ID!
3
id: Int!
4
emailAddress: String! # previously "email"
5
}

Computed Field#

Use the "name" JSON key to override a computed field's name.

1
create table "Account"(
2
id serial primary key,
3
"firstName" varchar(255) not null,
4
"lastName" varchar(255) not null
5
);
6
7
-- Extend with function
8
create function public."_fullName"(rec public."Account")
9
returns text
10
immutable
11
strict
12
language sql
13
as $$
14
select format('%s %s', rec."firstName", rec."lastName")
15
$$;
16
17
comment on function public._full_name is
18
e'@graphql({"name": "displayName"})';

results in:

1
type Account {
2
nodeId: ID!
3
id: Int!
4
firstName: String!
5
lastName: String!
6
displayName: String # previously "fullName"
7
}

Relationship's Field#

Use the "local_name" and "foreign_name" JSON keys to override a relationship's inbound and outbound field names.

1
create table "Account"(
2
id serial primary key
3
);
4
5
create table "Post"(
6
id serial primary key,
7
"accountId" integer not null references "Account"(id),
8
title text not null,
9
body text
10
);
11
12
comment on constraint post_account_id_fkey
13
on "Post"
14
is E'@graphql({"foreign_name": "author", "local_name": "posts"})';

results in:

1
type Post {
2
nodeId: ID!
3
id: Int!
4
accountId: Int!
5
title: String!
6
body: String!
7
author: Account # was "account"
8
}
9
10
type Account {
11
id: Int!
12
posts( # was "postCollection"
13
after: Cursor,
14
before: Cursor,
15
filter: PostFilter,
16
first: Int,
17
last: Int,
18
orderBy: [PostOrderBy!]
19
): PostConnection
20
}

Description#

Tables, Columns, and Functions accept a description directive to populate user defined descriptions in the GraphQL schema.

1
create table "Account"(
2
id serial primary key
3
);
4
5
comment on table public.account
6
is e'@graphql({"description": "A User Account"})';
7
8
comment on column public.account.id
9
is e'@graphql({"description": "The primary key identifier"})';
1
"""A User Account"""
2
type Account implements Node {
3
4
"""The primary key identifier"""
5
id: Int!
6
}

Enum Variant#

If a variant of a Postgres enum does not conform to GraphQL naming conventions, introspection returns an error:

For example:

1
create type "Algorithm" as enum ('aead-ietf');

causes the error:

1
{
2
"errors": [
3
{
4
"message": "Names must only contain [_a-zA-Z0-9] but \"aead-ietf\" does not.",
5
}
6
]
7
}

To resolve this problem, rename the invalid SQL enum variant to a GraphQL compatible name:

1
alter type "Algorithm" rename value 'aead-ietf' to 'AEAD_IETF';

or, add a comment directive to remap the enum variant in the GraphQL API

1
comment on type "Algorithm" is '@graphql({"mappings": {"aead-ietf": "AEAD_IETF"}})';

Which both result in the GraphQL enum:

1
enum Algorithm {
2
AEAD_IETF
3
}