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 type | GraphQL type |
---|---|
Text | String |
Number | Int |
Boolean | Boolean |
Timestamp | Timestamp (custom type) |
Date | Date (custom type) |
File | See Files |
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.
model Profile {
fields {
username Text
realname Text?
}
}
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.
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.
Placeholder | Description | Example (for 2023-01-02 14:09:05) |
---|---|---|
YYYY | full year | "2023" |
YY | short year | "23" |
MMMM | full month | "January" |
MMM | abbreviated month | "Jan" |
MM | zero-padded month number | "01" |
M | month number | "1" |
DDDD | zero-padded day of year | "002" |
DD | zero-padded day of month | "02" |
D | day of month | "2" |
dddd | full day of week | "Thursday" |
ddd | abbreviated day of week | "Thu" |
HH | zero-padded hour (24-hour) | "14" |
hh | zero-padded hour (12-hour) | "02" |
h | hour (12-hour) | "2" |
A | upper-case AM/PM | "PM" |
a | lower-case am/pm | "pm" |
mm | zero-padded minute | "09" |
m | minute | "9" |
ss | zero-padded seconds | "05" |
s | seconds | "5" |
ZZZ | time-zone with name | "UTC" |
Models
Models defined in your Keel schema become types in your GraphQL API.
model Profile {
fields {
username Text
}
}
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.
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.
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.
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.
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 withafter
.last
- Only return the last N records. Can be used withbefore
.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.