diff --git a/src/pages/learn/_meta.ts b/src/pages/learn/_meta.ts index 4affaab60a..f9170ae997 100644 --- a/src/pages/learn/_meta.ts +++ b/src/pages/learn/_meta.ts @@ -8,6 +8,7 @@ export default { schema: "Schemas and Types", validation: "", execution: "", + response: "", introspection: "", "-- 2": { type: "separator", diff --git a/src/pages/learn/execution.mdx b/src/pages/learn/execution.mdx index 1e04bbf9f9..5fca44a03a 100644 --- a/src/pages/learn/execution.mdx +++ b/src/pages/learn/execution.mdx @@ -1,8 +1,14 @@ +import { Callout } from "nextra/components" + # Execution -After being validated, a GraphQL query is executed by a GraphQL server which returns a result that mirrors the shape of the requested query, typically as JSON. +

Learn how GraphQL provides data for requested fields

+ +After a parsed document is [validated](/learn/validation/), a client's request will be [executed](https://spec.graphql.org/draft/#sec-Execution) by the GraphQL server and the returned result will mirror the shape of the requested query. On this page, you'll learn about the execution phase of GraphQL operations where data is read from or written to an existing source depending on what fields are requested by a client. + +## Field resolvers -GraphQL cannot execute a query without a type system, let's use an example type system to illustrate executing a query. This is a part of the same type system used throughout the examples in these articles: +GraphQL cannot execute operations without a type system, so let's use an example type system to illustrate executing a query. This is a part of the same type system used throughout the examples in this guide: ```graphql type Query { @@ -26,11 +32,11 @@ type Starship { } ``` -In order to describe what happens when a query is executed, let's use an example to walk through. +To describe what happens when a query is executed, let's walk through an example: ```graphql # { "graphiql": true } -{ +query { human(id: 1002) { name appearsIn @@ -41,66 +47,66 @@ In order to describe what happens when a query is executed, let's use an example } ``` -You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. In fact, this is exactly how GraphQL works. Each field on each type is backed by a function called the _resolver_ which is provided by the GraphQL server developer. When a field is executed, the corresponding _resolver_ is called to produce the next value. +You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. This is exactly how GraphQL works—each field on each type is backed by a _resolver function_ that is written by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value. -If a field produces a scalar value like a string or number, then the execution completes. However if a field produces an object value then the query will contain another selection of fields which apply to that object. This continues until scalar values are reached. GraphQL queries always end at scalar values. +If a field produces a scalar value like a string or number, then the execution completes. However, if a field produces an object value then the query will contain another selection of fields that apply to that object. This continues until the leaf values are reached. GraphQL queries always end at Scalar or Enum types. -## Root fields & resolvers +## Root fields and resolvers -At the top level of every GraphQL server is a type that represents all of the possible entry points into the GraphQL API, it's often called the _Root_ type or the _Query_ type. +At the top level of every GraphQL server is an Object type that represents the possible entry points into the GraphQL API, it's often called "the `query` root operation type" or the `Query` type. If the API supports mutations to write data and subscriptions to fetch real-time data as well, then it will have `Mutation` and `Subscription` types that expose fields to perform these kinds of operations too. You can learn more about these types on the [Schema and Types page](/learn/schema/#the-query-mutation-and-subscription-types). -In this example, our Query type provides a field called `human` which accepts the argument `id`. The resolver function for this field likely accesses a database and then constructs and returns a `Human` object. +In this example, our `Query` type provides a field called `human` which accepts the argument `id`. The resolver function for this field likely accesses a database and then constructs and returns a `Human` type: ```js -Query: { - human(obj, args, context, info) { - return context.db.loadHumanByID(args.id).then( - userData => new Human(userData) - ) - } +function Query_human(obj, args, context, info) { + return context.db.loadHumanByID(args.id).then( + userData => new Human(userData) + ) } ``` -This example is written in JavaScript, however GraphQL servers can be built in [many different languages](/code/). A resolver function receives four arguments: +This example is written in JavaScript, however GraphQL servers can be built in [many different languages](/code/). In the reference implementation, a resolver function receives four arguments: -- `obj` The previous object, which for a field on the root Query type is often not used. -- `args` The arguments provided to the field in the GraphQL query. -- `context` A value which is provided to every resolver and holds important contextual information like the currently logged in user, or access to a database. -- `info` A value which holds field-specific information relevant to the current query as well as the schema details, also refer to [type GraphQLResolveInfo for more details](/graphql-js/type/#graphqlobjecttype). +- `obj`: The previous object (for a field on the root `Query` type, this argument is often not used). +- `args`: The arguments provided to the field in the GraphQL operation. +- `context`: A value provided to every resolver that may hold important contextual information like the currently logged in user, or access to a database. +- `info`: generally only used in advanced use-cases, this is a value holding field-specific information relevant to the current operation as well as the schema details; refer to [type GraphQLResolveInfo](/graphql-js/type/#graphqlobjecttype) for more details. + + +Note that while a query operation could technically write data to the underlying data system during its execution, mutation operations are conventionally used for requests that produce side effects during their execution. And because mutation operations produce side effects, GraphQL implementations can be expected to execute these fields serially. + ## Asynchronous resolvers -Let's take a closer look at what's happening in this resolver function. +Let's take a closer look at what's happening in this resolver function: ```js -human(obj, args, context, info) { +function Query_human(obj, args, context, info) { return context.db.loadHumanByID(args.id).then( userData => new Human(userData) ) } ``` -The `context` is used to provide access to a database which is used to load the data for a user by the `id` provided as an argument in the GraphQL query. Since loading from a database is an asynchronous operation, this returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). In JavaScript, Promises are used to work with asynchronous values, but the same concept exists in many languages, often called _Futures_, _Tasks_ or _Deferred_. When the database returns, we can construct and return a new `Human` object. +The `id` argument in the GraphQL query specifies the user whose data is requested, while `context` provides access to retrieve this data from a database. Since loading from a database is an asynchronous operation, this returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). In JavaScript, Promises are used to work with asynchronous values, but the same concept exists in many languages, often called _Futures_, _Tasks_, or _Deferred_. When the database returns the data, we can construct and return a new `Human` object. -Notice that while the resolver function needs to be aware of Promises, the GraphQL query does not. It simply expects the `human` field to return something which it can then ask the `name` of. During execution, GraphQL will wait for Promises, Futures, and Tasks to complete before continuing and will do so with optimal concurrency. +Notice that while the resolver function needs to be aware of Promises, the GraphQL query does not. It simply expects the `human` field to return something that can be further resolved to a scalar `name` value. During execution, GraphQL will wait for Promises, Futures, and Tasks to be completed before continuing and will do so with optimal concurrency. ## Trivial resolvers -Now that a `Human` object is available, GraphQL execution can continue with the fields requested on it. +Now that a `Human` object is available, GraphQL execution can continue with the fields requested for this type: ```js -Human: { - name(obj, args, context, info) { - return obj.name - } +function Human_name(obj, args, context, info) { + return obj.name } ``` -A GraphQL server is powered by a type system which is used to determine what to do next. Even before the `human` field returns anything, GraphQL knows that the next step will be to resolve fields on the `Human` type since the type system tells it that the `human` field will return a `Human`. +A GraphQL server is powered by a type system that is used to determine what to do next. Even before the `human` field returns anything, GraphQL knows the next step will be to resolve fields on the `Human` type since the type system tells it that the `human` field will return this output type. -Resolving the name in this case is very straight-forward. The name resolver function is called and the `obj` argument is the `new Human` object returned from the previous field. In this case, we expect that Human object to have a `name` property which we can read and return directly. +Resolving the name in this case is straightforward. The name resolver function is called and the `obj` argument is the new `Human` object returned from the previous field. In this case, we expect this object to have a `name` property, which we can read and return directly. -In fact, many GraphQL libraries will let you omit resolvers this simple and will just assume that if a resolver isn't provided for a field, that a property of the same name should be read and returned. +Many GraphQL libraries let you omit resolvers this simple, assuming that if a resolver isn't provided for a field, a property of the same name should be read and returned. ## Scalar coercion @@ -114,39 +120,37 @@ Human: { } ``` -Notice that our type system claims `appearsIn` will return Enum values with known values, however this function is returning numbers! Indeed if we look up at the result we'll see that the appropriate Enum values are being returned. What's going on? +Notice that our type system claims `appearsIn` will return Enum types with known values, however, this function is returning numbers! Indeed if we look up at the result we'll see that the appropriate values for the Enum type are being returned. What's going on? -This is an example of scalar coercion. The type system knows what to expect and will convert the values returned by a resolver function into something that upholds the API contract. In this case, there may be an Enum defined on our server which uses numbers like `4`, `5`, and `6` internally, but represents them as Enum values in the GraphQL type system. +This is an example of _scalar coercion_. The type system knows what to expect and will convert the values returned by a resolver function into something that upholds the API contract. In this case, there may be an Enum type defined on our server that uses numbers like `4`, `5`, and `6` internally, but represents them as the expected values in the GraphQL type system. ## List resolvers -We've already seen a bit of what happens when a field returns a list of things with the `appearsIn` field above. It returned a _list_ of enum values, and since that's what the type system expected, each item in the list was coerced to the appropriate enum value. What happens when the `starships` field is resolved? +We've already seen some of what happens when a field returns a list of things with the `appearsIn` field above. It returned a [List type](/learn/schema/#lists) containing Enum type values, and since that's what the type system expected, each item in the list was coerced to the appropriate value. What happens when the `starships` field is resolved? ```js -Human: { - starships(obj, args, context, info) { - return obj.starshipIDs.map( - id => context.db.loadStarshipByID(id).then( - shipData => new Starship(shipData) - ) +function Human_starships (obj, args, context, info) { + return obj.starshipIDs.map( + id => context.db.loadStarshipByID(id).then( + shipData => new Starship(shipData) ) - } + ) } ``` -The resolver for this field is not just returning a Promise, it's returning a _list_ of Promises. The `Human` object had a list of ids of the `Starships` they piloted, but we need to go load all of those ids to get real Starship objects. +The resolver for this field is not just returning a Promise, it's returning a _list_ of Promises. The `Human` object had a list of IDs of the `Starships` they piloted, but we need to load all of those IDs to get real Starship objects. -GraphQL will wait for all of these Promises concurrently before continuing, and when left with a list of objects, it will concurrently continue yet again to load the `name` field on each of these items. +GraphQL will wait for all of these Promises concurrently before continuing, and when left with a list of objects, it will continue yet again to load the `name` field on each of these items concurrently. ## Producing the result -As each field is resolved, the resulting value is placed into a key-value map with the field name (or alias) as the key and the resolved value as the value. This continues from the bottom leaf fields of the query all the way back up to the original field on the root Query type. Collectively these produce a structure that mirrors the original query which can then be sent (typically as JSON) to the client which requested it. +As each field is resolved, the resulting value is placed into a key-value map with the field name (or alias) as the key and the resolved value as the value. This continues from the bottom leaf fields of the query back up to the original field on the root `Query` type. Collectively these produce a structure that mirrors the original query which can then be sent (typically as JSON) to the client that requested it. Let's take one last look at the original query to see how all these resolving functions produce a result: ```graphql # { "graphiql": true } -{ +query { human(id: 1002) { name appearsIn @@ -156,3 +160,18 @@ Let's take one last look at the original query to see how all these resolving fu } } ``` + +As we can see, each field in the nested selection sets resolves to a scalar leaf value during execution of the query. + +## Next steps + +To recap what we've learned about execution: + +- Each field in a GraphQL type system will have a corresponding resolver function that provides data for the field from an existing data source +- Execution begins at the top-level `Query`, `Mutation`, or `Subscription` fields +- Resolvers may execute asynchronously +- Scalar coercion converts values into the types expected by the schema +- When a field on an Object type returns a List type of other objects, additional data may need to be fetched from the underlying data source to transform any foreign key-like references (such as IDs) into the related objects +- Once all of the requested fields have been resolved to the expected leaf values, the result is sent to the client, typically as JSON + +Now that we understand how operations are executed, we can move to the last stage of the lifecycle of a GraphQL request where the [response](/learn/response/) is delivered to a client. \ No newline at end of file diff --git a/src/pages/learn/response.mdx b/src/pages/learn/response.mdx new file mode 100644 index 0000000000..d93c1fb855 --- /dev/null +++ b/src/pages/learn/response.mdx @@ -0,0 +1,122 @@ +import { Callout } from "nextra/components" + +# Response + +

Learn how GraphQL returns data to clients

+ +After a GraphQL document has been [validated](/learn/validation/) and [executed](/learn/execution/), the server will return a [response](https://spec.graphql.org/draft/#sec-Response) to the requesting client. One of GraphQL's strengths is that the server response reflects the shape of the client's original request, but a response may also contain helpful information if something unexpected happened before or during the execution of an operation. On this page, we'll take a deeper exploration of this final phase in the lifecycle of a GraphQL request. + +## Data + +As we have seen in the examples throughout this guide, when a GraphQL request is executed the response is returned on a top-level `data` key. For example: + +```graphql +# { "graphiql": true } +query { + human(id: 1002) { + name + appearsIn + starships { + name + } + } +} +``` + +The inclusion of the `data` key isn't an arbitrary decision made by the underlying GraphQL implementation—it's described by the [GraphQL specification](https://spec.graphql.org/draft/#sec-Data). Under this key, you will find the result of the execution of the requested operation, which may include partial data for the requested fields if errors were raised during the execution of some field resolvers. + +One thing that the GraphQL specification doesn't require is a specific serialization format for the response. That said, responses are typically formatted as JSON, as in the example above. + +Additionally, the GraphQL specification doesn't require the use of a particular transport protocol for requests either, although it is [common for HTTP to be used](/learn/serving-over-http/) for stateless query and mutations operations. Long-lived, stateful subscription operations are often supported by WebSockets or server-sent events instead. + + +There is [a draft GraphQL over HTTP specification](https://graphql.github.io/graphql-over-http/draft/) available with further guidelines for using HTTP with GraphQL clients and servers. + + +## Errors + +In addition to the `data` key, the GraphQL specification outlines how [errors](https://spec.graphql.org/draft/#sec-Errors) should be formatted in the response. Whether the GraphQL implementation provides partial data with the error information in the response will depend on the type of error that was raised. Let's look at the different kinds of errors that can occur during the lifecycle of a GraphQL request. + +### Request errors + +Request errors typically occur because the client made a mistake. For example, there may be a _syntax error_ in the document, such as a missing bracket or the use of an unknown root operation type keyword: + +```graphql +# { "graphiql": true } +operation { + updateHumanName(id: "1000", name: "Luke Starkiller" ) { + id + name + } +} +``` + +The `message` key inside the `errors` object provides some helpful information for the developer to understand what kind of error was raised, and the `locations` key, if present, indicates where the error occurred in the document. + +Sometimes, a GraphQL request may be syntactically correct, but when the server parses and [validates](/learn/validation/) the document against the schema, it finds an issue with part of the operation and raises a _validation error_. + +For example, the client may request a field that does not exist on the `Starship` type: + +```graphql +# { "graphiql": true } +query { + starship(id: 3000) { + width + } +} +``` + +Validation errors also occur when clients specify incorrect variable types for their corresponding field arguments: + +```graphql +# { "graphiql": true, "variables": { "ep": "JEDI" } } +query HeroForEpisode($ep: String!) { + hero(episode: $ep) { + name + } +} +``` + +In the previous examples, we can see that when a request error occurs the `data` key will not be included because the server returns an error response to the client before the field resolvers are executed. + +### Field errors + +Field errors are raised if something unexpected happens during execution. For example, a resolver may raise an error directly, or may return invalid data such as a null value for a field that has a Non-Null output type. + +In these cases, GraphQL will attempt to continue executing the other fields and return a partial response, with the `data` key appearing alongside the `errors` key. + +Let's look at an example: + +```graphql +# { "graphiql": true } +mutation { + firstShip: deleteStarship(id: "3001") + secondShip: deleteStarship(id: "3010") +} +``` + +The mutation above attempts to delete two starships in a single operation, but no starship exists with an ID of `3010` so the server throws an error. In the response, we can see information about the error that occurred for the field that was aliased as `secondShip`. Under the `data` key, we also see that the ID of the first ship was returned to indicate successful deletion, while the second ship produced a null result due to the error raised in its resolver function. + +### Network errors + +As with network calls to any type of API, network errors that are not specific to GraphQL may happen at any point during a request. These kinds of errors will block communication between the client and server before the request is complete, such as an SSL error or a connection timeout. Depending on the GraphQL server and client libraries that you choose, there may be features built into them that support special network error handling such as retries for failed operations. + +## Extensions + +The final top-level key allowed by the GraphQL specification in a response is the `extentions` key. This key is reserved for GraphQL implementations to provide additional information about the response and though it must be an object if present, there are no other restrictions on what it may contain. + +For example, some GraphQL servers may include telemetry data or information about rate limit consumption under this key. Note that what data is available in `extensions` and whether this data is available in production or development environments will depend entirely on the specific GraphQL implementation. + +## Next steps + +To recap what we've learned about GraphQL response formats: + +- The GraphQL specification allows three top-level keys in a response: `data`, `errors`, and `extensions` +- At least one of `data` or `errors` will be present on the response (when it contains both it is a "partial response") +- The `data` key contains the result of the executed operation +- Information about raised errors is included in the `errors` key of the response +- Request errors (such as syntax or validation errors) are raised before execution begins so `data` won't be included in the response +- When a field error occurs during execution, there will be a description of the issue in the `errors` key and there may be partial data included with the `data` key +- GraphQL implementations may include additional arbitrary information about the response in the `extensions` key + +Now that you understand the different phases of a GraphQL request and how responses are provided to clients, head over to the [Introspection](/learn/introspection) page to learn about how a GraphQL server can query information about its own schema. \ No newline at end of file