GraphQL API Introduction
Before you begin: This guide assumes you have a running instance of KeystoneJS with the GraphQL App configured, and a list with some data to query. (Get started in 5min by running npx create-keystone-app and select the Starter project)
Examples in this guide will refer to a Users list, however the queries, mutations and methods listed here would be the same for any KeystoneJS list.
For each list, KeystoneJS generates four top level queries. Given the following example:
keystone.createList('User', {
  fields: {
    name: { type: Text },
  },
});
Queries
KeystoneJS would generate the following queries:
- allUsers
- _allUsersMeta
- User
- _UsersMeta
allUsers
Retrieves all items from the User list. The allUsers query also allows you to search, limit and filter results. See: Filter, Limit and Sorting.
Usage
query {
  allUsers {
    id
  }
}
_allUsersMeta
Retrieves meta information about items in the User list such as a count of all items which can be used for pagination. The _allUsersMeta query accepts the same filter, limit and sorting parameters as the allUsers query.
Usage
query {
  _allUsersMeta {
    count
  }
}
User
Retrieves a single item from the User list. The single entity query accepts a where parameter which must provide an id.
Usage
query {
  User(where: { id: $id }) {
    name
  }
}
_UsersMeta
Retrieves meta information about the User list itself (i.e. not about items in the list) such as access control information. This query accepts no parameters.
Mutations
For each list KeystoneJS generates six top level mutations:
- createUser
- createUsers
- updateUser
- updateUsers
- deleteUser
- deleteUsers
createUser
Add a single User to the User list. Requires a data parameter that is an object where keys match the field names in the list definition and the values are the data to create.
Usage
mutation {
  createUser(data: { name: "Mike" }) {
    id
  }
}
createUsers
Creates multiple Users. Parameters are the same as createUser except the data parameter should be an array of objects.
Usage
mutation {
  createUsers(data: [{ name: "Mike" }]) {
    id
  }
}
updateUser
Update a User by ID. Accepts an id parameter that should match the id of a User item. The object should contain keys matching the field definition of the list. updateUser performs a partial update, meaning only keys that you wish to update need to be provided.
Usage
mutation {
  updateUser(id: ID, data: { name: "Simon" }) {
    id
  }
}
updateUsers
Update multiple Users by ID. Accepts a single data parameter that contains an array of objects. The object parameters are the same as createUser and should contain an id and nested data parameter with the field data.
mutation {
  updateUsers(data: [{ id: ID, data: { name: "Simon" } }]) {
    id
  }
}
deleteUser
Delete a single Entity by ID. Accepts a single parameter where the id matches a User id.
mutation {
  deleteUser(id: ID)
}
deleteUsers
Delete multiple entities by ID. Similar to deleteUser where the id parameter is an array of ids.
mutation {
  deleteUsers(id: [ID])
}
Executing Queries and Mutations
Before you begin writing application code, a great place test queries and mutations is the GraphQL Playground.
The default path for KeystoneJS' GraphQl Playground is http://localhost:3000/admin/graphql.
Here you can execute queries and mutations against the KeystoneJS API without writing any JavaScript.
Once you have determined the correct query or mutation, you can add this to your application. To do this you will need to submit a POST request to KeystoneJS' API. The default API endpoint is: http://localhost:3000/admin/api.
In our examples we're going to use the browser's Fetch API to make a POST request.
We're going to write a query and store it in a variable named GET_ALL_USERS. Once you have a query you can execute this query using a POST request:
const GET_ALL_USERS = `
query GetUsers {
  allUsers {
    name
    id
  }
}`;
fetch('/admin/api', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: {
    query: GET_ALL_USERS,
  },
}).then(result => result.json());
The result should contain a JSON payload with the results from the query.
Executing mutations is the same, however we need to pass variables along with the mutation. The key for mutations in the post request is still query. Let's write a mutation called ADD_USER, and pass a variables object along with the mutation in the POST request:
const ADD_USER = `
mutation AddUser($name: String!) {
  createUser(data: { name: $name }) {
    name
    id
  }
}`;
fetch('/admin/api', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: {
    query: ADD_USER,
    variables: { name: 'Mike' },
  },
}).then(result => result.json());
A good next step is to write an executeQuery function that accepts a query and variables and returns the results from the API. Take a look at the todo sample application in the cli for examples of this.
Note: If you have configured Access Control it can effect the result of some queries.
Executing Queries and Mutations on the Server
In addition to executing queries via the API, you can execute queries and mutations on the server using the keystone.executeQuery() method.
Note: No access control checks are run when executing queries on the server.
Any queries or mutations that checked for context.req in the resolver may also return different results as the req object is set to {}.
See: Keystone executeQuery()
Filter, Limit and Sorting
When executing queries and mutations there are a number of ways you can filter, limit and sort items. These include:
- where
- search
- skip
- first
- orderby
where
Limit results to items matching the where clause. Where clauses are used to query fields in a keystone list before retrieving data.
The options available in a where clause depend on the field types.
query {
  allUsers (where: { name_starts_with_i: 'A'} ) {
    id
  }
}
Note: The documentation in the GraphQL Playground provides a complete reference of filters for any field type in your application.
Relationship where filters
- {relatedList}_every: whereInput
- {relatedList}_some: whereInput
- {relatedList}_none: whereInput
- {relatedList}_is_null: Boolean
String where filters
- {Field}:String
- {Field}_not: String
- {Field}_contains: String
- {Field}_not_contains: String
- {Field}_starts_with: String
- {Field}_not_starts_with: String
- {Field}_ends_with: String
- {Field}_not_ends_with: String
- {Field}_i: String
- {Field}_not_i: String
- {Field}_contains_i: String
- {Field}_not_contains_i: String
- {Field}_starts_with_i: String
- {Field}_not_starts_with_i: String
- {Field}_ends_with_i: String
- {Field}_not_ends_with_i: String
- {Field}_in: [String]
- {Field}_not_in: [String]
ID where filters
- {Field}: ID
- {Field}_not: ID
- {Field}_in: [ID!]
- {Field}_not_in: [ID!]
Integer where filters
- {Field}: Int
- {Field}_not: Int
- {Field}_lt: Int
- {Field}_lte: Int
- {Field}_gt: Int
- {Field}_gte: Int
- {Field}_in: [Int]
- {Field}_not_in: [Int]
Operators
You can combine multiple where clauses with AND or OR operators.
- AND: [whereInput]
- OR: [whereInput]
Usage
query {
  allUsers (where: {
    OR: [
      { name_starts_with_i: 'A' },
      { email_starts_with_i: 'A' },
    ]
  } ) {
    id
  }
}
search
Will search the list to limit results.
query {
  allUsers(search: "Mike") {
    id
  }
}
orderBy
Order results. The orderBy string should match the format <field>_<ASC|DESC>. For example, to order by name descending (alphabetical order, A -> Z):
query {
  allUsers(orderBy: "name_DESC") {
    id
  }
}
first
Limits the number of items returned from the query. Limits will be applied after skip, orderBy, where and search values are applied.
If less results are available, the number of available results will be returned.
query {
  allUsers(first: 10) {
    id
  }
}
skip
Skip the number of results specified. Is applied before first parameter, but after orderBy, where and search values.
If the value of skip is greater than the number of available results, zero results will be returned.
query {
  allUsers(skip: 10) {
    id
  }
}
Combining query arguments for pagination
When first and skip are used together with the count from _allUsersMeta, this is sufficient to implement pagination on the list.
It is important to provide the same where and search arguments to both the allUser and _allUserMeta queries. For example:
query {
  allUsers (search:'a', skip: 10, first: 10) {
    id
  }
  _allUsersMeta(search: 'a') {
    count
  }
}
When first and skip are used together, skip works as an offset for the first argument. For example(skip:10, first:5) selects results 11 through 15.
Both skip and first respect the values of the where, search and orderBy arguments.
Custom Queries and Mutations
You can add to Keystone's generated schema with custom types, queries, and mutations using the keystone.extendGraphQLSchema() method.
Usage
keystone.extendGraphQLSchema({
  types: [{ type: 'type FooBar { foo: Int, bar: Float }' }],
  queries: [
    {
      schema: 'double(x: Int): Int',
      resolver: (_, { x }) => 2 * x,
    },
  ],
  mutations: [
    {
      schema: 'double(x: Int): Int',
      resolver: (_, { x }) => 2 * x,
    },
  ],
});