From c50a7be734114d3eb9750858c0ff816ab853324c Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 11 Sep 2019 11:11:31 +1000 Subject: [PATCH] Website: Update graphQL documentation (#1594) --- docs/guides/intro-to-graphql.md | 415 +++++++++++++++++++++++++++++++- packages/app-graphql/README.md | 40 ++- packages/keystone/README.md | 23 +- 3 files changed, 461 insertions(+), 17 deletions(-) diff --git a/docs/guides/intro-to-graphql.md b/docs/guides/intro-to-graphql.md index 9f3f177cbbf..0d5e4c41e9a 100644 --- a/docs/guides/intro-to-graphql.md +++ b/docs/guides/intro-to-graphql.md @@ -1,9 +1,416 @@ -# An Introduction To KeystoneJS's GraphQL API +# Introduction to the GraphQL API -In this tutorial we will introduce you to GraphQL and show you show to use the KeystoneJS GraphQL API to interact with your system. +_Before you begin:_ This guide assumes you have running instance of Keystone 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 Keystone list. + +For each list, Keystone generates four top level queries. Given the following example: + +```javascript +keystone.createList('User', { + fields: { + name: { type: Text }, + }, +}); +``` + +## Queries + +Keystone 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](#search-and-filtering). + +#### Usage + +```gql +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](#search-and-filtering) parameters as the `allUsers` query. + +#### Usage + +```gql +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 + +```gql +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 Keystone 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 + +```gql +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 + +```gql +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 + +```gql +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. + +```gql +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. + +```gql +mutation { + deleteUser(id: ID) +} +``` + +### deleteUsers + +Delete multiple entities by ID. Similar to `deleteUser` where the `id` parameter is an array of ids. + +```gql +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](https://www.apollographql.com/docs/apollo-server/features/graphql-playground/). The default path for Keystone's GraphQl Playground is `http://localhost:3000/admin/graphql`. Here you can execute queries and mutations against the Keystone 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 Keystone's API. The default API endpoint is: `http://localhost:3000/admin/api`. + +In our examples we're going to use the browser's [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/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: + +```javascript +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: + +```javascript +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](https://v5.keystonejs.com/api/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](https://v5.keystonejs.com/keystone-alpha/keystone/#executequeryquerystring-config). + +**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()](https://v5.keystonejs.com/keystone-alpha/keystone/#executequeryquerystring-config) + +## 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. + +```gql +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 + +```gql +query { + allUsers (where: { + OR: [ + { name_starts_with_i: 'A' }, + { email_starts_with_i: 'A' }, + ] + } ) { + id + } +} +``` + +#### `search` + +Will search the list to limit results. + +```gql +query { + allUsers(search: "Mike") { + id + } +} +``` + +#### `orderBy` + +Order results. The orderBy string should match the format `_`. For example, to order by name descending (alphabetical order, A -> Z): + +```gql +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. + +```gql +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. + +```gql +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: + +```gql +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 + +```javascript +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, + }, + ], +}); +``` diff --git a/packages/app-graphql/README.md b/packages/app-graphql/README.md index a2a53c00d78..1a43cae2362 100644 --- a/packages/app-graphql/README.md +++ b/packages/app-graphql/README.md @@ -5,4 +5,42 @@ title: GraphQL API draft: true [meta]--> -# GraphQL API +# GraphQL App + +A Keystone App that creates a GraphQL API and Apollo GraphQL playground. + +For information about writing queries and mutations for Keystone see the [Introduction to Keystone's GraphQL API](https://v5.keystonejs.com/guides/intro-to-graphql). + +## Usage + +```javascript +const { Keystone } = require('@keystone-alpha/keystone'); +const { GraphQLApp } = require('@keystone-alpha/app-graphql'); +const { AdminUIApp } = require('@keystone-alpha/app-admin-ui'); + +module.exports = { + new Keystone(), + apps: [ + new GraphQLApp({ + // All config keys are optional. Default values are shown here for completeness. + cors : { origin: true, credentials: true }, + apiPath : '/admin/api', + graphiqlPath : '/admin/graphiql', + schemaName : 'admin', + apollo : {}, + }), + new AdminUIApp() + ], +}; +``` + +## Config + +| Option | Type | Default | Description | +| -------------- | -------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `cors` | `Object` | `{ origin: true, credentials: true }` | Options passed directly to the express [cors](https://github.com/expressjs/cors) middleware package. | +| | +| `apiPath` | `String` | `/admin/api` | Change the API path | +| `graphiqlPath` | `String` | `/admin/graphiql` | Change the Apollo GraphQL playground path | +| `schemaName` | `String` | `admin` | Change the graphQL schema name (not recommended) | +| `apollo` | `Object` | `{}` | Options passed directly to Apollo Server | diff --git a/packages/keystone/README.md b/packages/keystone/README.md index 5e640ba4b57..94ca2101e3f 100644 --- a/packages/keystone/README.md +++ b/packages/keystone/README.md @@ -185,11 +185,20 @@ Disconnect all adapters. ## executeQuery(queryString, config) -### Usage - Use this method to execute queries or mutations directly against a Keystone instance. +**Note:** When querying or mutating via `keystone.executeQuery`, there are differences to keep in mind: + +- No access control checks are run (everything is set to `() => true`) +- The `context.req` object is set to `{}` (you can override this if necessary, + see options below) +- Attempting to authenticate will throw errors (due to `req` being mocked) + +Returns a Promise representing the result of the given query or mutation. + +### Usage + ```javascript keystone.executeQuery(queryString, config); ``` @@ -223,13 +232,3 @@ mutation newTodo($name: String) { | ----------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | | `variables` | `Object` | `{}` | The variables passed to the graphql query for the given queryString. | | `context` | `Object` | `{}` | Override the default `context` object passed to the GraphQL engine. Useful for adding a `req` or setting the `schemaName` | - -ℹ️ When querying or mutating via `keystone.executeQuery`, there are differences -to keep in mind: - -- No access control checks are run (everything is set to `() => true`) -- The `context.req` object is set to `{}` (you can override this if necessary, - see options below) -- Attempting to authenticate will throw errors (due to `req` being mocked) - -Returns a Promise representing the result of the given query or mutation.