GraphQL API

GraphQL API

GraphQL (opens in a new tab) is an open-source data query language that allows clients to specify the data they need from the API. It also allows querying data across relational data models, meaning that you can do in one API call what might otherwise require several.

The GraphQL endpoint for your Keel app is /api/graphql. When running locally with keel run you can also access a GraphiQL (opens in a new tab) playground at /api/graphiql.

Queries & Mutations

GraphQL has the concept of queries which are for reading data and mutations which are for writing data. Any get, list, and read actions in your Keel schema become queries in your GraphQL API whilst create, update, delete, and write actions become mutations.

All queries or mutations in your GraphQL API accept a single input value called input.

Types

The built-in Keel types are mapped to GraphQL types in the following way:

Keel typeGraphQL type
TextString
NumberInt
BooleanBoolean
TimestampTimestamp (custom type)
DateDate (custom type)

Optional / Required

In a Keel schema fields are required by default, but can be marked optional by adding ? after the type. In a GraphQL schema it's the other way around, with fields being optional by default and ! after the type indicating the field is required.

This means that if a field in your Keel is schema does not have a ? after it's type, then it will have a ! after it in GraphQL.

schema.keel
model Profile {
  fields {
    username Text
    realname Text?
  }
}
Generated GraphQL type
type Profile {
  username: String!
  realname: String
}

This applies to model fields, action inputs, and message fields.

Timestamp & Date

The Timestamp and Date types in a Keel schema correspond to the following GraphQL types.

Built-in Timestamp and Date GraphQL types
type Timestamp {
  # a value based on the provided format
  formatted(format: String!): String!
  
  # a value like "in 2 days" or "5 months ago"
  fromNow: String!
 
  # ISO-8601 format string e.g. "2023-05-27T15:02:14.185Z"
  iso8601: String!
 
  # Unix time
  seconds: Int!
}
 
type Date {
  # a value based on the provided format
  formatted(format: String!): String!
 
  # ISO-8601 format string e.g. "2023-05-27"
  iso8601: String!
}

The formatted field of these types accept a format string that contain the following placeholders.

PlaceholderDescriptionExample (for 2023-01-02 14:09:05)
YYYYfull year"2023"
YYshort year"23"
MMMMfull month"January"
MMMabbreviated month"Jan"
MMzero-padded month number"01"
Mmonth number"1"
DDDDzero-padded day of year"002"
DDzero-padded day of month"02"
Dday of month"2"
ddddfull day of week"Thursday"
dddabbreviated day of week"Thu"
HHzero-padded hour (24-hour)"14"
hhzero-padded hour (12-hour)"02"
hhour (12-hour)"2"
Aupper-case AM/PM"PM"
alower-case am/pm"pm"
mmzero-padded minute"09"
mminute"9"
sszero-padded seconds"05"
sseconds"5"
ZZZtime-zone with name"UTC"

Models

Models defined in your Keel schema become types in your GraphQL API.

schema.keel
model Profile {
  fields {
    username Text
  }
}
Generated GraphQL schema
type Profile {
  id: ID!
  createdAt: Timestamp!
  updatedAt: Timestamp!
  username: String!
}

You'll see that the GraphQL Person type contains all the built-in model fields as well as the ones you defined in your schema.

Enums

Enums defined in your Keel schema become enums in your GraphQL API. GraphQL represents enums in exactly the same say as Keel.

enum Category {
  Sports
  Finance
  Politics
}

Creating a new record

You can define an action for creating a new record in your Keel schema using the create action type. These actions will become mutations in your GraphQL schema.

schema.keel
model Profile {
  fields {
    username Text @unique
    bio Text
  }
  actions {
    create createProfile() with (username, bio)
  }
}

Reading a single record

You can define an action for reading a single record in your Keel schema using the get action type. These actions will become queries in your GraphQL schema.

schema.keel
model Profile {
  fields {
    username Text
  }
  actions {
    get getProfile(id)
  }
}

Updating a record

You can define an action for updating a single record in your Keel schema using the update action type. These actions will become mutations in your GraphQL schema.

schema.keel
model Profile {
  fields {
    username Text @unique
    bio Text
  }
  actions {
    update updateBio(id) with (bio)
  }
}

Listing records

You can define an action for listing many records in your Keel schema using the list action type. These actions will become queries in your GraphQL schema. You'll see in the example below that the input types for list actions are a little more complex than the other action types, and this is because they allow for filtering records in a number of different ways. See the Query Inputs section below for more info on this.

schema.keel
model Book {
  fields {
    title Text
    genre Text
    releaseDate Date
  }
  actions {
    list listBooks(title?, genre?, releaseDate?)
  }
}

Query Inputs

We can use a list action to perform queries like:

  • Find all books whose title contains the word "Love"
  • Find all books whose genre is "Sci-Fi" or "Crime" and were released in the last year

This is possible because in a list action each input gets wrapped in a query type. For example an input that points to a Text field in a Keel schema will become a StringQueryInput in the GraphQL schema. The StringQueryInput looks like this:

input StringQueryInput {
  contains: String
  endsWith: String
  equals: String
  notEquals: String
  oneOf: [String]
  startsWith: String
}

All Keel types have a corresponding GraphQL query type that allow filtering in ways that make sense for that type, for example the StringQueryInput type has a contains field whereas the IntQueryInput has a greaterThan field.

Connection pattern

The response type of a list action in GraphQL follows the Relay Connection Spec (opens in a new tab) which means that for a model called Book the response type of a list action in GraphQL will be a BookConnection.

type BookConnection {
  edges: [BookEdge!]!
  pageInfo: PageInfo!
}
 
type BookEdge {
  node: Book!
}
 
type PageInfo {
  count: Int!
  endCursor: String!
  hasNextPage: Boolean!
  startCursor: String!
  totalCount: Int!
}

Pagination

The input type for a list action has four fields that relate to pagination.

  • first - Only return the first N records. Can be used with after.
  • last - Only return the last N records. Can be used with before.
  • after - Return records after this cursor.
  • before - Return records before this cursor.

The startCursor and endCursor fields of a PageType can be passed to after or before in subsequent queries. For example if you fetched the first 10 book records and in the response the endCursor value was "1234" then to get the next 10 records you would pass "1234" to the after input.

Functions

These docs talk about actions, which is the general term for both built-in actions (implemented by the Keel runtime) and functions (implemented by you using code). There is no difference between how built-in actions and functions are surfaced in GraphQL.