Skip to main content

GraphQL

The GraphQL API exposes each database schema as a GraphQL endpoint whose types, queries and mutations are generated from the tables in it. There is nothing to define or deploy: add a table or a column and it appears in the GraphQL schema on the next request, with the PostgreSQL types mapped to GraphQL scalars.

Use GraphQL when a client needs to pick exactly the fields it wants, fetch related data in one round trip, or benefit from the typed schema in tooling and code generation. For ad-hoc or spatial queries that need the full power of PostgreSQL, the SQL API is the better fit, and for stable server-side queries use methods. Authentication, privileges and rules apply as for every other API.

Endpoint and Authentication​

HTTP​

The HTTP endpoint is schema-specific (see Schemas for more information):

POST https://api.gc2.io/api/graphql/schema/{schema_name}

Authentication is handled via a Bearer token in the Authorization header (see OAuth for more information).

POST https://api.gc2.io/api/graphql/schema/public
Authorization: Bearer <your_token>
Content-Type: application/json

{
"query": "query { ... }"
}

WebSocket​

All GraphQL operations — queries, mutations, and subscriptions — are also available over WebSocket:

wss://event.gc2.io?token=<your_token>

Send operations as JSON messages with type, schema, and query:

{
"type": "subscribe",
"id": "q1",
"schema": "public",
"query": "query { getArtists { artist_id legal_name } }"
}

This works identically for mutations:

{
"type": "subscribe",
"id": "m1",
"schema": "public",
"query": "mutation { insertArtists(objects: [{ legal_name: \"Jane Doe\" }]) { artist_id } }"
}

Introspection​

You can use standard GraphQL introspection to explore the available types and fields. This is useful for tools like GraphiQL or Apollo Studio.

query {
__schema {
types {
name
}
}
}

Queries​

For every table in your database, the API provides a query field named get[TableName] (in camelCase).

Basic Query​

To fetch records from a table named artists:

Request, basic query
POST https://api.gc2.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "query { getArtists { artist_id legal_name } }"
}

Filtering​

The where argument supports complex filtering using logical and comparison operators. Simple equality filters are also supported.

Simple Equality:

{ getArtists(where: { artist_id: 1 }) { legal_name } }

Supported Operators:

  • eq: Equal
  • neq: Not equal
  • gt: Greater than
  • gte: Greater than or equal
  • lt: Less than
  • lte: Less than or equal
  • in: Included in array
  • like: Case-sensitive pattern match
  • ilike: Case-insensitive pattern match

Logical Operators:

  • and: Logical AND
  • or: Logical OR
  • not: Logical NOT
Request, filtered query
POST https://api.gc2.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "query { getArtists(where: { and: [ { legal_name: { ilike: \"%Linus%\" } }, { instrument: { eq: \"guitar\" } } ] }) { legal_name instrument } }"
}

Pagination​

Use limit and offset for pagination.

Request, pagination
POST https://api.gc2.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "query { getArtists(limit: 10, offset: 20) { artist_id legal_name } }"
}

Relationships​

If there are foreign key constraints between tables, the GraphQL API automatically exposes them as nested fields.

Request, relationships
POST https://api.gc2.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "query { getAlbums { title bands { name subgenre } } }"
}

Mutations​

The API provides insert, update, and delete mutations for each table.

Insert​

Use insert[TableName] to add new records. You can provide a single record via data or multiple records via objects.

Request, insert mutation
POST https://api.gc2.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "mutation { insertArtists(objects: [{ legal_name: \"John Doe\", instrument: \"Piano\" }]) { artist_id legal_name } }"
}

Update​

Use update[TableName] to modify existing records. Use the where argument to specify which records to update and data for the new values.

Request, update mutation
POST https://api.gc2.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "mutation { updateArtists(where: { artist_id: { eq: 1 } }, data: { instrument: \"Electric Guitar\" }) { artist_id instrument } }"
}

Delete​

Use delete[TableName] to remove records.

Request, delete mutation
POST https://api.gc2.io/api/graphql/schema/public
Content-Type: application/json
Authorization: Bearer <your_token>

{
"query": "mutation { deleteArtists(where: { artist_id: { eq: 1 } }) { artist_id } }"
}

Subscriptions​

Subscriptions let you receive real-time database change events (INSERT, UPDATE, DELETE) over the WebSocket connection using GraphQL syntax.

Subscribing​

After connecting to the WebSocket (see WebSocket above), send a message to register a subscription:

{
"type": "subscribe",
"id": "sub1",
"schema": "public",
"query": "subscription { ArtistsMessageAdded { artist_id legal_name instrument } }"
}

The server responds with an acknowledgement:

{
"type": "subscribe_ack",
"id": "sub1"
}

Subscription Field Names​

Each table gets three subscription fields following the pattern {TableName}Message{Event}:

  • {TableName}MessageAdded — triggered on INSERT
  • {TableName}MessageUpdated — triggered on UPDATE
  • {TableName}MessageDeleted — triggered on DELETE

The table name is in PascalCase, matching the same convention as queries and mutations.

Filtering​

Use the where argument to only receive events matching specific conditions. The same JSON object syntax used in queries and mutations is supported:

Subscribe with filter
{
"type": "subscribe",
"id": "sub2",
"schema": "public",
"query": "subscription { ArtistsMessageUpdated(where: {instrument: {eq: \"guitar\"}}) { artist_id legal_name instrument } }"
}

Supported operators:

  • eq, neq, gt, gte, lt, lte, in, like, ilike

Logical operators:

  • AND, OR, NOT
Subscribe with complex filter
{
"type": "subscribe",
"id": "sub3",
"schema": "public",
"query": "subscription { ArtistsMessageAdded(where: {OR: [{instrument: {eq: \"guitar\"}}, {instrument: {eq: \"piano\"}}]}) { artist_id legal_name } }"
}

A SQL-style string syntax is also supported:

Subscribe with string filter
{
"type": "subscribe",
"id": "sub4",
"schema": "public",
"query": "subscription { ArtistsMessageAdded(where: \"instrument = 'guitar'\") { artist_id legal_name } }"
}
note

Subscription filtering only works with scalar column types (integer, float, boolean, string, etc.). Complex types such as geometric types, range types, and JSON are not supported in subscription where clauses.

Column Selection​

The fields you select in the subscription body determine which columns are included in the event payload. Only matching columns are returned.

JavaScript Example​

const ws = new WebSocket(
`wss://event.gc2.io?token=${encodeURIComponent(token)}`
);

ws.onopen = () => {
// Query over WebSocket
ws.send(JSON.stringify({
type: "subscribe",
id: "q1",
schema: "public",
query: `query { getArtists { artist_id legal_name } }`
}));

// Subscribe to real-time events
ws.send(JSON.stringify({
type: "subscribe",
id: "sub1",
schema: "public",
query: `subscription {
ArtistsMessageAdded(where: {instrument: {eq: "guitar"}}) {
artist_id
legal_name
instrument
}
}`
}));
};

ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received:", data);
};

Data Types​

The GraphQL API supports various PostgreSQL data types, mapping them to appropriate GraphQL types:

  • Scalars: integer, float, boolean, string, JSON , ID, Date, Time, DateTime.
  • Geometric Types: Point, Line, Lseg, Box, Circle, Path, Polygon.
  • Range Types: Int4range, Int8range, Numrange, Tsrange, Tstzrange, Daterange.
  • Arrays: Any type can be returned as a list (e.g., [String], [Int]).

Any complex types can be used as input arguments for mutations. Type hints are not required because the schemas are dynamically generated from the database relations.

Se also Types.

Consider a generated schema like this:

Schema
type Tours {
id: ID!
artist_name: String!
tour_name: String!
from_to: Daterange!
}

"""
Date is accepted in almost any reasonable format, including ISO 8601, SQL-compatible, traditional POSTGRES, and others.
"""
scalar Date

"""Range type for daterange, with lower and upper bounds of type Date."""
type Daterange {
lower: Date
upper: Date
lowerInclusive: Boolean
upperInclusive: Boolean
}

type Mutation {
insertTours(data: JSON, objects: [JSON]): [Tours]
updateTours(id: ID, where: JSON, data: JSON): [Tours]
deleteTours(id: ID, where: JSON): [Tours]
}

Then you can insert a new tour using the following mutation. The Daterange field is represented as JSON:

Operation
mutation InsertTours($data: JSON) {
insertTours(data: $data) {
id
from_to {
lower
upper
}
}
}
variables
{
"data": {
"artist_name": "Guns n’ Roses",
"tour_name": "Europe: Summer 2026",
"from_to": {
"lower": "2010-04-06",
"upper": "2011-03-07",
"lowerInclusive": true,
"upperInclusive": true
}
}
}

Using the SDK​

The SDK wraps the GraphQL API in the Gql class:

  • Class: new Gql(schema: string)
  • Method: request(request: GqlRequest): Promise<GqlResponse>
  • Endpoint: POST https://api.gc2.io/api/graphql/schema/{schema}

Types (simplified):

  • GqlRequest has query, optional variables and operationName
  • GqlResponse has data and optional errors
GraphQL query
import { Gql, type GqlRequest, type GqlResponse } from "@mapcentia/gc2-sdk"

const query = `
query {
getBands {
band_id
name
subgenre
}
}
`
const req: GqlRequest = { query }
const gql = new Gql('public') // Which schema to use
const res: GqlResponse = await gql.request(req)
console.log(res)
GraphQL mutation
import { Gql, type GqlRequest, type GqlResponse } from "@mapcentia/gc2-sdk"

const query = `
mutation UpdateBands($where: JSON, $data: JSON) {
updateBands(where: $where, data: $data) {
band_id
name
website
}
}
`
const variables = {
"where": {
"name": {
"eq": "The Segfaults"
}
},
"data": {
"website": "https://segfaults.example"
}
}
const req: GqlRequest = { query, variables }
const gql = new Gql('public') // Which schema to use
const res: GqlResponse = await gql.request(req)
console.log(res)