SQL tables are reflected into GraphQL types with columns and foreign keys represented as fields on those types.
Naming
By default, PostgreSQL table and column names are not adjusted when reflecting GraphQL type and field names. For example, an account_holder table has GraphQL type name account_holder and can be queried via the account_holderCollection field of the Query type.
In cases, like the previous example, where the SQL name is snake_case, you'll likely want to enable inflection to re-case names to match GraphQL/Javascript conventions e.g. AccountHolder and accountHolderCollection.
Table, column, and relationship type and field names may also be manually overridden as needed.
Type Conversion
Connection Types
Connection types hande pagination best practices according to the relay spec. pg_graphql uses keyset pagination to enable consistent retrival times on every page.
Example
The following is a complete example showing how a sample SQL schema translates into a GraphQL schema.
-- Turn on automatic inflection of type namescommentonschemapublicis'@graphql({"inflect_names": true})';createtableaccount(idserialprimarykey,emailvarchar(255)notnull,created_attimestampnotnull,updated_attimestampnotnull);-- enable a `totalCount` field on the `account` query typecommentontableaccountise'@graphql({"totalCount": {"enabled": true}})';createtableblog(idserialprimarykey,owner_idintegernotnullreferencesaccount(id),namevarchar(255)notnull,descriptionvarchar(255),created_attimestampnotnull,updated_attimestampnotnull);createtypeblog_post_statusasenum('PENDING','RELEASED');createtableblog_post(iduuidnotnulldefaultuuid_generate_v4()primarykey,blog_idintegernotnullreferencesblog(id),titlevarchar(255)notnull,bodyvarchar(10000),statusblog_post_statusnotnull,created_attimestampnotnull,updated_attimestampnotnull);
typeAccount{id: Int!email: String!createdAt: DateTime!updatedAt: DateTime!blogCollection("""Query the first `n` records in the collection"""first: Int"""Query the last `n` records in the collection"""last: Int"""Query values in the collection before the provided cursor"""before: Cursor"""Query values in the collection after the provided cursor"""after: Cursor"""Filters to apply to the results set when querying from the collection"""filter: BlogFilter"""Sort order to apply to the collection"""orderBy: [BlogOrderBy!]): BlogConnection}
typeAccountConnection{edges: [AccountEdge!]!pageInfo: PageInfo!"""The total number of records matching the `filter` criteria"""totalCount: Int!}
typeAccountDeleteResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [Account!]!}
typeAccountEdge{cursor: String!node: Account}
inputAccountFilter{id: IntFilteremail: StringFiltercreatedAt: DateTimeFilterupdatedAt: DateTimeFilter}
inputAccountInsertInput{email: StringcreatedAt: DateTimeupdatedAt: DateTime}
typeAccountInsertResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [Account!]!}
inputAccountOrderBy{id: OrderByDirectionemail: OrderByDirectioncreatedAt: OrderByDirectionupdatedAt: OrderByDirection}
inputAccountUpdateInput{email: StringcreatedAt: DateTimeupdatedAt: DateTime}
typeAccountUpdateResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [Account!]!}
scalarBigInt
"""Boolean expression comparing fields on type "BigInt""""
inputBigIntFilter{eq: BigIntgt: BigIntgte: BigIntlt: BigIntlte: BigIntneq: BigInt}
typeBlog{id: Int!ownerId: Int!name: String!description: StringcreatedAt: DateTime!updatedAt: DateTime!blogPostCollection("""Query the first `n` records in the collection"""first: Int"""Query the last `n` records in the collection"""last: Int"""Query values in the collection before the provided cursor"""before: Cursor"""Query values in the collection after the provided cursor"""after: Cursor"""Filters to apply to the results set when querying from the collection"""filter: BlogPostFilter"""Sort order to apply to the collection"""orderBy: [BlogPostOrderBy!]): BlogPostConnectionowner: Account}
typeBlogConnection{edges: [BlogEdge!]!pageInfo: PageInfo!}
typeBlogDeleteResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [Blog!]!}
typeBlogEdge{cursor: String!node: Blog}
inputBlogFilter{id: IntFilterownerId: IntFiltername: StringFilterdescription: StringFiltercreatedAt: DateTimeFilterupdatedAt: DateTimeFilter}
inputBlogInsertInput{ownerId: Intname: Stringdescription: StringcreatedAt: DateTimeupdatedAt: DateTime}
typeBlogInsertResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [Blog!]!}
inputBlogOrderBy{id: OrderByDirectionownerId: OrderByDirectionname: OrderByDirectiondescription: OrderByDirectioncreatedAt: OrderByDirectionupdatedAt: OrderByDirection}
typeBlogPost{id: UUID!blogId: Int!title: String!body: Stringstatus: String!createdAt: DateTime!updatedAt: DateTime!blog: Blog}
typeBlogPostConnection{edges: [BlogPostEdge!]!pageInfo: PageInfo!}
typeBlogPostDeleteResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [BlogPost!]!}
typeBlogPostEdge{cursor: String!node: BlogPost}
inputBlogPostFilter{id: UUIDFilterblogId: IntFiltertitle: StringFilterbody: StringFilterstatus: StringFiltercreatedAt: DateTimeFilterupdatedAt: DateTimeFilter}
inputBlogPostInsertInput{id: UUIDblogId: Inttitle: Stringbody: Stringstatus: StringcreatedAt: DateTimeupdatedAt: DateTime}
typeBlogPostInsertResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [BlogPost!]!}
inputBlogPostOrderBy{id: OrderByDirectionblogId: OrderByDirectiontitle: OrderByDirectionbody: OrderByDirectionstatus: OrderByDirectioncreatedAt: OrderByDirectionupdatedAt: OrderByDirection}
enumBlogPostStatus{PENDING
RELEASED
}
inputBlogPostUpdateInput{id: UUIDblogId: Inttitle: Stringbody: Stringstatus: StringcreatedAt: DateTimeupdatedAt: DateTime}
typeBlogPostUpdateResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [BlogPost!]!}
inputBlogUpdateInput{ownerId: Intname: Stringdescription: StringcreatedAt: DateTimeupdatedAt: DateTime}
typeBlogUpdateResponse{"""Count of the records impacted by the mutation"""affectedCount: Int!"""Array of records impacted by the mutation"""records: [Blog!]!}"""Boolean expression comparing fields on type "Boolean""""
inputBooleanFilter{eq: Booleangt: Booleangte: Booleanlt: Booleanlte: Booleanneq: Boolean}
scalarCursor
scalarDateTime
"""Boolean expression comparing fields on type "DateTime""""
inputDateTimeFilter{eq: DateTimegt: DateTimegte: DateTimelt: DateTimelte: DateTimeneq: DateTime}"""Boolean expression comparing fields on type "Float""""
inputFloatFilter{eq: Floatgt: Floatgte: Floatlt: Floatlte: Floatneq: Float}"""Boolean expression comparing fields on type "Int""""
inputIntFilter{eq: Intgt: Intgte: Intlt: Intlte: Intneq: Int}
scalarJSON
"""Boolean expression comparing fields on type "JSON""""
inputJSONFilter{eq: JSONneq: JSON}"""The root type for creating and mutating data"""
typeMutation{"""Deletes zero or more records from the collection"""deleteFromAccountCollection("""Restricts the mutation's impact to records matching the critera"""filter: AccountFilter""" The maximum number of records in the collection permitted to be affected """atMost: Int!=1): AccountDeleteResponse!"""Deletes zero or more records from the collection"""deleteFromBlogCollection("""Restricts the mutation's impact to records matching the critera"""filter: BlogFilter""" The maximum number of records in the collection permitted to be affected """atMost: Int!=1): BlogDeleteResponse!"""Deletes zero or more records from the collection"""deleteFromBlogPostCollection("""Restricts the mutation's impact to records matching the critera"""filter: BlogPostFilter""" The maximum number of records in the collection permitted to be affected """atMost: Int!=1): BlogPostDeleteResponse!"""Adds one or more `AccountInsertResponse` records to the collection"""insertIntoAccountCollection(objects: [AccountInsertInput!]!): AccountInsertResponse"""Adds one or more `BlogInsertResponse` records to the collection"""insertIntoBlogCollection(objects: [BlogInsertInput!]!): BlogInsertResponse"""Adds one or more `BlogPostInsertResponse` records to the collection"""insertIntoBlogPostCollection(objects: [BlogPostInsertInput!]!): BlogPostInsertResponse"""Updates zero or more records in the collection"""updateAccountCollection(""" Fields that are set will be updated for all records matching the `filter` """set: AccountUpdateInput!"""Restricts the mutation's impact to records matching the critera"""filter: AccountFilter""" The maximum number of records in the collection permitted to be affected """atMost: Int!=1): AccountUpdateResponse!"""Updates zero or more records in the collection"""updateBlogCollection(""" Fields that are set will be updated for all records matching the `filter` """set: BlogUpdateInput!"""Restricts the mutation's impact to records matching the critera"""filter: BlogFilter""" The maximum number of records in the collection permitted to be affected """atMost: Int!=1): BlogUpdateResponse!"""Updates zero or more records in the collection"""updateBlogPostCollection(""" Fields that are set will be updated for all records matching the `filter` """set: BlogPostUpdateInput!"""Restricts the mutation's impact to records matching the critera"""filter: BlogPostFilter""" The maximum number of records in the collection permitted to be affected """atMost: Int!=1): BlogPostUpdateResponse!}"""Defines a per-field sorting order"""
enumOrderByDirection{AscNullsFirst
AscNullsLast
DescNullsFirst
DescNullsLast
}
typePageInfo{endCursor: StringhasNextPage: Boolean!hasPreviousPage: Boolean!startCursor: String}"""The root type for querying data"""
typeQuery{"""A pagable collection of type `Account`"""accountCollection("""Query the first `n` records in the collection"""first: Int"""Query the last `n` records in the collection"""last: Int"""Query values in the collection before the provided cursor"""before: Cursor"""Query values in the collection after the provided cursor"""after: Cursor"""Filters to apply to the results set when querying from the collection"""filter: AccountFilter"""Sort order to apply to the collection"""orderBy: [AccountOrderBy!]): AccountConnection"""A pagable collection of type `Blog`"""blogCollection("""Query the first `n` records in the collection"""first: Int"""Query the last `n` records in the collection"""last: Int"""Query values in the collection before the provided cursor"""before: Cursor"""Query values in the collection after the provided cursor"""after: Cursor"""Filters to apply to the results set when querying from the collection"""filter: BlogFilter"""Sort order to apply to the collection"""orderBy: [BlogOrderBy!]): BlogConnection"""A pagable collection of type `BlogPost`"""blogPostCollection("""Query the first `n` records in the collection"""first: Int"""Query the last `n` records in the collection"""last: Int"""Query values in the collection before the provided cursor"""before: Cursor"""Query values in the collection after the provided cursor"""after: Cursor"""Filters to apply to the results set when querying from the collection"""filter: BlogPostFilter"""Sort order to apply to the collection"""orderBy: [BlogPostOrderBy!]): BlogPostConnection}"""Boolean expression comparing fields on type "String""""
inputStringFilter{eq: Stringgt: Stringgte: Stringlt: Stringlte: Stringneq: String}
scalarUUID
"""Boolean expression comparing fields on type "UUID""""
inputUUIDFilter{eq: UUIDneq: UUID}