Skip to content
KeystoneJS LogoKeystoneJSv5

Introduction to the GraphQL API

Before you begin: This guide assumes you have 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: Search and Filtering.

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 search and filtering 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 (ie; 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
  }
}

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 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,
    },
  ],
});

Have you found a mistake, something that is missing, or could be improved on this page? Please edit the Markdown file on GitHub and submit a PR with your changes.

Edit Page