diff --git a/docs/source/data/data-sources.mdx b/docs/source/data/data-sources.mdx index 191c539d2d7..ca98d7934f4 100644 --- a/docs/source/data/data-sources.mdx +++ b/docs/source/data/data-sources.mdx @@ -30,7 +30,7 @@ flowchart LR; ## Open-source implementations -All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-server` library. Subclasses define whatever logic is required to communicate with a particular store or API. +All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-datasource` package. Subclasses of a `DataSource` should define whatever logic is required to communicate with a particular store or API. Apollo and the larger community maintain the following open-source implementatons: diff --git a/docs/source/data/resolvers.mdx b/docs/source/data/resolvers.mdx index 8bf39ba061b..5040322f9dd 100644 --- a/docs/source/data/resolvers.mdx +++ b/docs/source/data/resolvers.mdx @@ -164,7 +164,13 @@ Note that you can define your resolvers across as many different files and objec ## Resolver chains -Whenever a query asks for a field that contains an object type, the query _also_ asks for _at least one field_ of that object (if it didn't, there would be no reason to include the object in the query). A query always "bottoms out" on fields that contain either a scalar or a list of scalars. +Whenever a query asks for a field that contains an object type, the query _also_ asks for _at least one field_ of that object (if it didn't, there would be no reason to include the object in the query). A query always "bottoms out" on fields that contain either a scalar or an enum, of any list depth and nullability, for example: + +```graphql +type Product { + variants: [String!] +} +``` Therefore, whenever Apollo Server _resolves_ a field that contains an object type, it always then resolves one or more fields of that object. Those subfields might in turn _also_ contain object types. Depending on your schema, this object-field pattern can continue to an arbitrary depth, creating what's called a **resolver chain**. @@ -388,7 +394,7 @@ const server = new ApolloServer({ } ``` -> The fields of the object passed to your `context` function differ if you're using middleware besides Express. [See the API reference for details.](/api/apollo-server/#middleware-specific-context-fields) +> This example assumes you are using either the `apollo-server` or `apollo-server-express` package, both of which use Express. The fields of the object passed to your `context` function may differ if you're using middleware other than Express. [See the API reference for details.](../api/apollo-server/#middleware-specific-context-fields) For more information on middleware in general, see the [integrations page](../integrations/middleware/). Context initialization can be asynchronous, allowing database connections and other operations to complete: @@ -411,7 +417,7 @@ A resolver function's return value is treated differently by Apollo Server depen |---|---| | Scalar / object |
A resolver can return a single value or an object, as shown in [Defining a resolver](#defining-a-resolver). This return value is passed down to any nested resolvers via the `parent` argument.
| | `Array` |Return an array if and only if your schema indicates that the resolver's associated field contains a list.
After you return an array, Apollo Server executes nested resolvers for each item in the array.
| -| `null` / `undefined` |Indicates that the value for the field could not be found.
If your schema indicates that this resolver's field is nullable, then the operation result has a `null` value at the field's position.
If this resolver's field is _not_ nullable, Apollo Server sets the field's _parent_ to `null`. If necessary, this process continues up the resolver chain until it reaches a field that _is_ nullable. This ensures that a response never includes a `null` value for a non-nullable field.
| +| `null` / `undefined` |Indicates that the value for the field could not be found.
If your schema indicates that this resolver's field is nullable, then the operation result has a `null` value at the field's position.
If this resolver's field is _not_ nullable, Apollo Server sets the field's _parent_ to `null`. If necessary, this process continues up the resolver chain until it reaches a field that _is_ nullable. This ensures that a response never includes a `null` value for a non-nullable field. When this happens, the response's `errors` property will be populated with relevant errors concerning the nullability of that field.
| | [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) |Resolvers often perform asynchronous actions, such as fetching from a database or back-end API. To support this, a resolver can return a promise that resolves to any other supported return type.
| diff --git a/docs/source/monitoring/health-checks.md b/docs/source/monitoring/health-checks.md index 4be22d2bc49..8f783ac29c3 100644 --- a/docs/source/monitoring/health-checks.md +++ b/docs/source/monitoring/health-checks.md @@ -3,14 +3,12 @@ title: Health checks description: Determining the health status of the Apollo Server --- -Health checks are often used by load balancers to determine if a server is available and ready to start serving traffic. By default, Apollo Server provides a health check endpoint at `/.well-known/apollo/server-health` which returns a 200 status code if the server has started. +Health checks are often used by load balancers to determine if a server is available and ready to start serving traffic. By default, Apollo Server provides a health check endpoint at `/.well-known/apollo/server-health` which returns a 200 status code if the server has started. -This basic health check may not be comprehensive enough for some applications and depending on individual circumstances, it may be beneficial to provide a more thorough implementation by defining an `onHealthCheck` function to the `ApolloServer` constructor options. If defined, this `onHealthCheck` function should return a `Promise` which _rejects_ if there is an error, or _resolves_ if the server is deemed _ready_. A `Promise` _rejection_ will result in an HTTP status code of 503, and a _resolution_ will result in an HTTP status code of 200, which is generally desired by most health-check tooling (e.g. Kubernetes, AWS, etc.). +This basic health check may not be comprehensive enough for some applications and depending on individual circumstances, it may be beneficial to provide a more thorough implementation by defining an `onHealthCheck` function to the `ApolloServer` constructor options. If defined, this `onHealthCheck` async function should return if the server is deemed _ready_ or `throw` if there is an error. Returning (resolving the `Promise`) will result in an HTTP status code of 200, which is generally desired by most health-check tooling (e.g. Kubernetes, AWS, etc.), while `throw`ing (rejecting the `Promise`) will result in an HTTP status code of 503. -> **Note:** Alternatively, the `onHealthCheck` can be defined as an `async` function which `throw`s if it encounters an error and returns when conditions are considered normal. - -```js{10-19} -const { ApolloServer, gql } = require('apollo-server'); +```js{10-17} +import { ApolloServer, gql } from 'apollo-server'; // Undefined for brevity. const typeDefs = gql``; @@ -19,15 +17,13 @@ const resolvers = {}; const server = new ApolloServer({ typeDefs, resolvers, - onHealthCheck: () => { - return new Promise((resolve, reject) => { - // Replace the `true` in this conditional with more specific checks! - if (true) { - resolve(); - } else { - reject(); - } - }); + async onHealthCheck() { + // Replace the `true` in this conditional with more specific checks! + if (true) { + return; + } else { + throw new Error('...'); + } }, }); diff --git a/docs/source/performance/caching.md b/docs/source/performance/caching.md index ba1090aa9f9..16a17e5e411 100644 --- a/docs/source/performance/caching.md +++ b/docs/source/performance/caching.md @@ -6,6 +6,8 @@ description: Configure caching behavior on a per-field basis > **New in Apollo Server 3**: You must manually define the `@cacheControl` directive in your schema to use static cache hints. [See below.](#in-your-schema-static) +> Note: Apollo Federation doesn't currently support @cacheControl out-of-the-box. There is [an issue](https://github.com/apollographql/federation/issues/356) on the Federation repo which discusses this and proposes possible workarounds. + Apollo Server enables you to define cache control settings (`maxAge` and `scope`) for each field in your schema: ```graphql{5,7} @@ -306,7 +308,7 @@ If you run Apollo Server behind a CDN or another caching proxy, you can configur Because CDNs and caching proxies only cache GET requests (not POST requests, which Apollo Client sends for all operations by default), we recommend enabling [automatic persisted queries](./apq/) and the [`useGETForHashedQueries` option](./apq/) in Apollo Client. -Alternatively, you can set the `useGETForQueries` option of [HttpLink](https://www.apollographql.com/docs/react/api/link/apollo-link-http) in your `ApolloClient` instance, but **this is less secure** because your query string and GraphQL variables are sent as plaintext URL query parameters. +Alternatively, you can set the `useGETForQueries` option of [HttpLink](https://www.apollographql.com/docs/react/api/link/apollo-link-http) in your `ApolloClient` instance. However, most browsers enforce a size limit on GET requests, and large query strings might exceed this limit. ## Disabling cache control diff --git a/docs/source/proxy-configuration.md b/docs/source/proxy-configuration.md index 5fdae327863..bf4049b248d 100644 --- a/docs/source/proxy-configuration.md +++ b/docs/source/proxy-configuration.md @@ -3,20 +3,15 @@ title: Proxy configuration description: Configuring proxy settings for outgoing requests --- -Certain features of the Apollo platform require Apollo Server to make outgoing requests to Apollo Studio. These include: +Certain features of the Apollo platform (such as [managed federation](https://www.apollographql.com/docs/federation/managed-federation/overview/)) require Apollo Server to make outgoing requests to Apollo Studio. Depending on security policies, you might need to configure an outgoing HTTP proxy in order to allow these requests. -* Managed federation -* Operation registry +Although Apollo Server supports standard Node.js "agent" configuration via [`https.globalAgent`](https://nodejs.org/api/https.html#https_https_globalagent) and [`http.globalAgent`](https://nodejs.org/api/http.html#http_http_globalagent) directly, we recommend using the [`global-agent`](https://github.com/gajus/global-agent#global-agent) package to reduce the amount of necessary configuration involved with [creating a custom agent](https://nodejs.org/api/http.html#http_class_http_agent). -Depending on security policies, you might need to configure an outgoing HTTP proxy in order to allow these requests. - -While Apollo Server supports standard Node.js "agent" configuration via [`https.globalAgent`](https://nodejs.org/api/https.html#https_https_globalagent) and [`http.globalAgent`](https://nodejs.org/api/http.html#http_http_globalagent) directly, we recommend using the [`global-agent`](https://github.com/gajus/global-agent#global-agent) package to reduce the amount of necessary configuration involved with [creating a custom agent](https://nodejs.org/api/http.html#http_class_http_agent). - -The `global-agent` package allows support for the common technique of setting proxy settings using environment variables (e.g. `HTTP_PROXY`, `NO_AGENT`, etc.), which is not supported by Node.js itself (and [may never be](https://github.com/nodejs/node/issues/15620)). +The `global-agent` package enables the common technique of setting proxy settings using environment variables (e.g. `HTTP_PROXY`, `NO_AGENT`, etc.), which is not supported by Node.js itself (and [may never be](https://github.com/nodejs/node/issues/15620)). ## Configuring the proxy agent -This guide covers the `global-agent` package which is supported by Node.js versions v10 and higher. When using Node.js versions _prior_ to v10, consider using the [`global-tunnel-ng`](https://github.com/np-maintain/global-tunnel) which behaves similarly, but is configured differently. +This guide covers the `global-agent` package, which is supported by Node.js version 10 and later. ### Installing the `global-agent` dependency @@ -98,6 +93,6 @@ This can be done [via Node.js' `NODE_EXTRA_CA_CERTS` environment variable](https ```shell $ NODE_EXTRA_CA_CERTS=/full/path/to/certificate.pem \ - GLOBAL_AGENT_HTTP_PROXY=http://proxy:3128/ \ - node index.js + GLOBAL_AGENT_HTTP_PROXY=http://proxy:3128/ \ + node index.js ``` diff --git a/docs/source/schema/custom-scalars.md b/docs/source/schema/custom-scalars.md index 25af1d98988..a608006235b 100644 --- a/docs/source/schema/custom-scalars.md +++ b/docs/source/schema/custom-scalars.md @@ -12,7 +12,9 @@ To define a custom scalar, add it to your schema like so: scalar MyCustomScalar ``` -Object types in your schema can now contain fields of type `MyCustomScalar`. However, Apollo Server still needs to know how to interact with values of this new scalar type. +You can now use `MyCustomScalar` in your schema anywhere you can use a default scalar (e.g., as the type of an object field, input type field, or argument). + +However, Apollo Server still needs to know how to interact with values of this new scalar type. ## Defining custom scalar logic @@ -70,13 +72,13 @@ In the example above, the `Date` scalar is represented on the backend by the `Da ### `parseValue` -The `parseValue` method converts the scalar's `serialize`d JSON value to its back-end representation before it's added to a resolver's `args`. +The `parseValue` method converts the scalar's JSON value to its back-end representation before it's added to a resolver's `args`. Apollo Server calls this method when the scalar is provided by a client as a [GraphQL variable](https://graphql.org/learn/queries/#variables) for an argument. (When a scalar is provided as a hard-coded argument in the operation string, [`parseLiteral`](#parseliteral) is called instead.) ### `parseLiteral` -When an incoming query string includes the scalar as a hard-coded argument value, that value is part of the query document's abstract syntax tree (AST). Apollo Server calls the `parseLiteral` method to convert the value's AST representation (which is always a string) to the scalar's back-end representation. +When an incoming query string includes the scalar as a hard-coded argument value, that value is part of the query document's abstract syntax tree (AST). Apollo Server calls the `parseLiteral` method to convert the value's AST representation to the scalar's back-end representation. In [the example above](#example-the-date-scalar), `parseLiteral` converts the AST value from a string to an integer, and _then_ converts from integer to `Date` to match the result of `parseValue`. @@ -120,7 +122,7 @@ const server = new ApolloServer({ In this example, we create a custom scalar called `Odd` that can only contain odd integers: -```js{19-30} +```js:title=index.js const { ApolloServer, gql, UserInputError } = require('apollo-server'); const { GraphQLScalarType, Kind } = require('graphql'); @@ -128,17 +130,18 @@ const { GraphQLScalarType, Kind } = require('graphql'); const typeDefs = gql` scalar Odd - type MyType { - oddValue: Odd + type Query { + # Echoes the provided odd integer + echoOdd(odd: Odd!): Odd! } `; -// Validation function +// Validation function for checking "oddness" function oddValue(value) { - if (typeof value === "number" && value % 2 === 1) { + if (typeof value === "number" && Number.isInteger(value) && value % 2 !== 0) { return value; } - throw new UserInputError("provided value is not an odd number"); + throw new UserInputError("Provided value is not an odd integer"); } const resolvers = { @@ -151,9 +154,14 @@ const resolvers = { if (ast.kind === Kind.INT) { return oddValue(parseInt(ast.value, 10)); } - return null; + throw new UserInputError("Provided value is not an odd integer"); }, }), + Query: { + echoOdd(_, {odd}) { + return odd; + } + } }; const server = new ApolloServer({ typeDefs, resolvers }); diff --git a/docs/source/schema/directives.md b/docs/source/schema/directives.md index abcf1001f46..de9724d663c 100644 --- a/docs/source/schema/directives.md +++ b/docs/source/schema/directives.md @@ -29,16 +29,37 @@ For example, here's the GraphQL spec's definition of the `@deprecated` directive ```graphql directive @deprecated( reason: String = "No longer supported" -) on FIELD_DEFINITION | ENUM_VALUE +) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE ``` -This indicates that `@deprecated` can decorate either a schema `FIELD_DEFINITION` (as shown at the top of the article) or a schema `ENUM_VALUE` definition (as shown here): +This indicates that `@deprecated` can decorate any of the four listed locations. Also note that its `reason` argument is optional and provides a default value. Usage examples of each location are provided below: ```graphql:title=schema.graphql +# ARGUMENT_DEFINITION +# Note: @deprecated arguments _must_ be optional. +directive @withDeprecatedArgs( + deprecatedArg: String @deprecated(reason: "Use `newArg`"), + newArg: String +) on FIELD + +type MyType { + # ARGUMENT_DEFINITION (alternate example on a field's args) + fieldWithDeprecatedArgs(name: String! @deprecated): String + # FIELD_DEFINITION + deprecatedField: String @deprecated +} + enum MyEnum { + # ENUM_VALUE OLD_VALUE @deprecated(reason: "Use `NEW_VALUE`.") NEW_VALUE } + +input SomeInputType { + nonDeprecated: String + # INPUT_FIELD_DEFINITION + deprecated: String @deprecated +} ``` If `@deprecated` appears elsewhere in a GraphQL document, it produces an error. diff --git a/docs/source/schema/schema.md b/docs/source/schema/schema.md index 7d58403f840..5933a8c7230 100644 --- a/docs/source/schema/schema.md +++ b/docs/source/schema/schema.md @@ -1,18 +1,19 @@ --- -title: Schema basics +title: GraphQL schema basics +sidebar_title: Schema basics --- -Your GraphQL server uses a **schema** to describe the shape of your data graph. This schema defines a hierarchy of **types** with fields that are populated from your back-end data stores. The schema also specifies exactly which **queries** and **mutations** are available for clients to execute against your data graph. +Your GraphQL server uses a **schema** to describe the shape of your available data. This schema defines a hierarchy of **types** with **fields** that are populated from your back-end data stores. The schema also specifies exactly which **queries** and **mutations** are available for clients to execute. This article describes the fundamental building blocks of a schema and how to create one for your GraphQL server. ## The schema definition language -The GraphQL specification includes a human-readable **schema definition language** (or **SDL**) that you use to define your schema and store it as a string. +The GraphQL specification defines a human-readable **schema definition language** (or **SDL**) that you use to define your schema and store it as a string. -Here's a short example schema that defines two object types: `Book` and `Author`: +Here's a short example schema that defines two **object types**: `Book` and `Author`: -```graphql +```graphql:title=schema.graphql type Book { title: String author: Author @@ -24,24 +25,78 @@ type Author { } ``` -A schema defines a collection of types and the relationships _between_ those types. In the example schema above, every `Book` has an `author`, and every `Author` has a list of `books`. By defining these type relationships in a unified schema, we enable client developers to see exactly what data is available and request a specific subset of that data with a single optimized query. +A schema defines a collection of types and the relationships _between_ those types. In the example schema above, a `Book` can have an associated `author`, and an `Author` can have a **list** of `books`. + +Because these relationships are defined in a unified schema, client developers can see exactly what data is available and then request a specific subset of that data with a single optimized query. Note that the schema is **not** responsible for defining where data comes from or how it's stored. It is entirely implementation-agnostic. +## Field definitions + +_Most_ of the schema types you define have one or more **fields**: + +```graphql +# This Book type has two fields: title and author +type Book { + title: String # returns a String + author: Author # returns an Author +} +``` + +Each field returns data of the type specified. A field's return type can be a [scalar](#scalar-types), [object](#object-types), [enum](#enum-types), [union](#union-and-interface-types), or [interface](#union-and-interface-types) (all described below). + +### List fields + +A field can return a **list** containing items of a particular type. You indicate list fields with square brackets `[]`, like so: + +```graphql{3} +type Author { + name: String + books: [Book] # A list of Books +} +``` + +### Field nullability + +By default, it's valid for any field in your schema to return `null` instead of its specified type. You can require that a particular field _doesn't_ return `null` with an exclamation mark `!`, like so: + +```graphql{2} +type Author { + name: String! # Can't return null + books: [Book] +} +``` + +These fields are **non-nullable**. If your server attempts to return `null` for a non-nullable field, an error is thrown. + +#### Nullability and lists + +With a list field, an exclamation mark `!` can appear in any combination of _two_ locations: + +```graphql{2} +type Author { + books: [Book!]! # This list can't be null AND its list *items* can't be null +} +``` + +* If `!` appears _inside_ the square brackets, the returned list can't include _items_ that are `null`. +* If `!` appears _outside_ the square brackets, _the list itself_ can't be `null`. +* In _any_ case, it's valid for a list field to return an _empty_ list. + + ## Supported types Every type definition in a GraphQL schema belongs to one of the following categories: -* [Scalar types](#scalar-types) -* [Object types](#object-types) -* [The `Query` type](#the-query-type) -* [The `Mutation` type](#the-mutation-type) -* [Input types](#input-types) -* [Enum types](#enum-types) - -Each of these is defined in detail below. +* [Scalar](#scalar-types) +* [Object](#object-types) + * This includes the three special **root operation types**: [`Query`](#the-query-type), [`Mutation`](#the-mutation-type), and [`Subscription`](#the-subscription-type). +* [Input](#input-types) +* [Enum](#enum-types) +* [Union](#union-and-interface-types) +* [Interface](#union-and-interface-types) -You can monitor the performance and usage of each field within these declarations with [Apollo Studio](https://studio.apollographql.com/), providing you with data that helps inform decisions about changes to your graph. +Each of these is described below. ### Scalar types @@ -59,7 +114,7 @@ These primitive types cover the majority of use cases. For more specific use cas ### Object types -Most of the types you define in a GraphQL schema are object types. An object type contains a collection of fields, each of which can be either a scalar type or _another_ object type. +Most of the types you define in a GraphQL schema are object types. An object type contains a collection of [fields](#field-definitions), each of which has its _own_ type. Two object types _can_ include each other as fields, as is the case in our example schema from earlier: @@ -77,7 +132,7 @@ type Author { ### The `Query` type -The `Query` type defines all of the top-level **entry points** for queries that clients execute against your data graph. It resembles an [object type](#object-types), but its name is always `Query`. +The `Query` type is a special object type that defines all of the top-level **entry points** for queries that clients execute against your server. Each field of the `Query` type defines the name and return type of a different entry point. The `Query` type for our example schema might resemble the following: @@ -208,7 +263,7 @@ As with queries, our server would respond to this mutation with a result that ma } ``` -A single client request can include multiple mutations to execute. To prevent race conditions, mutations are executed serially in the order they're listed. +A single mutation operation can include multiple top-level fields of the `Mutation` type. This usually means that the operation will execute multiple back-end writes (at least one for each field). To prevent race conditions, top-level `Mutation` fields are [resolved](../data/resolvers/) serially in the order they're listed (all other fields can be resolved in parallel). [Learn more about designing mutations](#designing-mutations) @@ -218,48 +273,49 @@ See [Subscriptions](../data/subscriptions). ### Input types -Input types are special object types that allow you to pass objects as arguments to queries and mutations (as opposed to passing only scalar types). Input types help keep operation signatures clean, much like how accepting a single `options` object in a JavaScript function can be cleaner than repeatedly adding arguments to the function's signature. +Input types are special object types that allow you to provide hierarchical data as **arguments** to fields (as opposed to providing only flat scalar arguments). -Consider this mutation that creates a blog post: +An input type's definition is similar to an object type's, but it uses the `input` keyword: ```graphql -type Mutation { - createPost(title: String, body: String, mediaUrls: [String]): Post +input BlogPostContent { + title: String + body: String } ``` -Instead of accepting three arguments, this mutation could accept a _single_ input type that includes all of these fields. This comes in extra handy if we decide to accept an additional argument in the future, such as an `author`. - -An input type's definition is similar to an object type's, but it uses the `input` keyword: +Each field of an input type can be only a [scalar](#scalar-types), an [enum](#enum-types), or _another_ input type: ```graphql -type Mutation { - createPost(post: PostAndMediaInput): Post -} - -input PostAndMediaInput { +input BlogPostContent { title: String body: String - mediaUrls: [String] + media: [MediaDetails!] +} + +input MediaDetails { + format: MediaFormat! + url: String! +} + +enum MediaFormat { + IMAGE + VIDEO } ``` -Not only does this facilitate passing the `PostAndMediaInput` type around within our schema, it also provides a basis for annotating fields with descriptions that are automatically exposed by GraphQL-enabled tools: +After you define an input type, any number of different object fields can accept that type as an argument: ```graphql -input PostAndMediaInput { - "A main title for the post" - title: String - "The text body of the post." - body: String - "A list of URLs to render in the post." - mediaUrls: [String] +type Mutation { + createBlogPost(content: BlogPostContent!): Post + updateBlogPost(id: ID!, content: BlogPostContent!): Post } ``` Input types can sometimes be useful when multiple operations require the exact same set of information, but you should reuse them sparingly. Operations might eventually diverge in their sets of required arguments. -> **Do not use the same input type for both queries and mutations**. In many cases, arguments that are _required_ for a mutation are _optional_ for a corresponding query. +> **Take care if using the same input type for fields of both `Query` and `Mutation`**. In many cases, arguments that are _required_ for a mutation are _optional_ for a corresponding query. You might want to create separate input types for each operation type. ### Enum types @@ -341,14 +397,14 @@ Most _additive_ changes to a schema are safe and backward compatible. However, c * Removing a type or field * Renaming a type or field -* Adding nullability to a field +* Adding [nullability](#field-nullability) to a field * Removing a field's arguments A graph management tool such as [Apollo Studio](https://studio.apollographql.com/) helps you understand whether a potential schema change will impact any of your active clients. Studio also provides field-level performance metrics, schema history tracking, and advanced security via operation safelisting. -## Documentation strings +## Descriptions (docstrings) -GraphQL's schema definition language (SDL) supports markdown-enabled documentation strings. These help consumers of your data graph discover fields and learn how to use them. +GraphQL's schema definition language (SDL) supports Markdown-enabled documentation strings, called **descriptions**. These help consumers of your data graph discover fields and learn how to use them. The following snippet shows how to use both single-line string literals and multi-line blocks: @@ -368,9 +424,7 @@ type MyObjectType { } ``` -A well-documented schema offers an enhanced development experience since GraphQL development tools (such as the -[Apollo VS Code extension](https://marketplace.visualstudio.com/items?itemName=apollographql.vscode-apollo) -and GraphQL Playground) auto-complete field names along with descriptions when they're provided. Furthermore, [Apollo Studio](https://studio.apollographql.com/) displays descriptions alongside field-usage and performance details when using its metrics reporting and client-awareness features. +A well documented schema helps provide an enhanced development experience, because GraphQL development tools (such as [Apollo Explorer](https://www.apollographql.com/docs/studio/explorer/)) auto-complete field names along with descriptions when they're provided. Furthermore, [Apollo Studio](https://studio.apollographql.com/) displays descriptions alongside field usage and performance details when using its metrics reporting and client awareness features. ## Naming conventions @@ -415,17 +469,17 @@ Because we know this is the structure of data that would be helpful for our clie ```graphql type Query { - upcomingEvents: [Event] + upcomingEvents: [Event!]! } type Event { - name: String - date: String + name: String! + date: String! location: Location } type Location { - name: String + name: String! weather: WeatherInfo } @@ -490,9 +544,9 @@ A single mutation can modify multiple types, or multiple instances of the _same_ Additionally, mutations are much more likely than queries to cause errors, because they modify data. A mutation might even result in a _partial_ error, in which it successfully modifies one piece of data and fails to modify another. Regardless of the type of error, it's important that the error is communicated back to the client in a consistent way. -To help resolve both of these concerns, we recommend defining a `MutationResponse` interface in your schema, along with a collection of object types that _implement_ that interface (one for each of your mutations). +To help resolve both of these concerns, we recommend defining a `MutationResponse` [interface](../schema/unions-interfaces/#interface-type) in your schema, along with a collection of object types that _implement_ that interface (one for each of your mutations). -Here's what the `MutationResponse` interface looks like: +Here's what a `MutationResponse` interface might look like: ```graphql interface MutationResponse { @@ -502,7 +556,7 @@ interface MutationResponse { } ``` -And here's what an implementing object type looks like: +And here's what an object that _implements_ `MutationResponse` might look like: ```graphql type UpdateUserEmailMutationResponse implements MutationResponse { diff --git a/docs/source/schema/unions-interfaces.md b/docs/source/schema/unions-interfaces.md index 8ed0fc51bbd..6475cce4442 100644 --- a/docs/source/schema/unions-interfaces.md +++ b/docs/source/schema/unions-interfaces.md @@ -13,11 +13,11 @@ When you define a union type, you declare which object types are included in the union Media = Book | Movie ``` -A field can have a union as its return type. In this case, it can return any object type that's included in the union: +A field can have a union (or a list of that union) as its return type. In this case, it can return any object type that's included in the union: ```graphql type Query { - allMedia: [Media] # This list can include both Books and Movies + allMedia: [Media] # This list can include both Book and Movie objects } ``` @@ -25,25 +25,25 @@ All of a union's included types must be [object types](./schema/#object-types) ( ### Example -The following schema defines a `Result` union type that can return either a `Book` or an `Author`: +The following schema defines a `SearchResult` union type that can return either a `Book` or an `Author`: ```graphql -union Result = Book | Author +union SearchResult = Book | Author type Book { - title: String + title: String! } type Author { - name: String + name: String! } type Query { - search(contains: String): [Result] + search(contains: String): [SearchResult!] } ``` -The `Result` union enables `Query.search` to return a list that includes both `Book`s and `Author`s. +The `SearchResult` union enables `Query.search` to return a list that includes both `Book`s and `Author`s. ### Querying a union @@ -54,6 +54,10 @@ Here's a valid query for the schema above: ```graphql query GetSearchResults { search(contains: "Shakespeare") { + # Querying for __typename is almost always recommended, + # but it's even more important when querying a field that + # might return one of multiple types. + __typename ... on Book { title } @@ -66,24 +70,51 @@ query GetSearchResults { This query uses [inline fragments](https://graphql.org/learn/queries/#inline-fragments) to fetch a `Result`'s `title` (if it's a `Book`) or its `name` (if it's an `Author`). +Here's a valid result for the above query: + +```json +{ + "data": { + "search": [ + { + "__typename": "Book", + "title": "The Complete Works of William Shakespeare" + }, + { + "__typename": "Author", + "name": "William Shakespeare" + } + ] + } +} +``` + + For more information, see [Using fragments with unions and interfaces](https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces). ### Resolving a union +> Before reading this section, [learn about resolvers](../data/resolvers/). + To fully resolve a union, Apollo Server needs to specify _which_ of the union's types is being returned. To achieve this, you define a `__resolveType` function for the union in your resolver map. -The `__resolveType` function uses a returned object's fields to determine its type. It then returns the name of that type as a string. +The `__resolveType` function is responsible for determining an object's corresponding GraphQL type and returning the name of that type as a string. It can use any logic to do so, such as: -Here's an example `__resolveType` function for the `Result` union defined above: +* Checking for the presence or absence of fields that are unique to a particular type in the union +* Using `instanceof`, if the _JavaScript_ object's type is related to its _GraphQL_ object type -```js{3-11} +Here's a basic `__resolveType` function for the `SearchResult` union defined above: + +```js{3-13} const resolvers = { - Result: { + SearchResult: { __resolveType(obj, context, info){ + // Only Author has a name field if(obj.name){ return 'Author'; } + // Only Book has a title field if(obj.title){ return 'Book'; } @@ -113,8 +144,8 @@ An interface specifies a set of fields that multiple object types can include: ```graphql interface Book { - title: String - author: Author + title: String! + author: Author! } ``` @@ -122,17 +153,17 @@ If an object type `implements` an interface, it _must_ include _all_ of that int ```graphql type Textbook implements Book { - title: String # Must be present - author: Author # Must be present - courses: [Course] + title: String! # Must be present + author: Author! # Must be present + courses: [Course!]! } ``` -A field can have an interface as its return type. In this case, it can return any object type that `implements` that interface: +A field can have an interface (or a list of that interface) as its return type. In this case, it can return any object type that `implements` that interface: ```graphql type Query { - schoolBooks: [Book] # Can include Textbooks + books: [Book!] # Can include Textbook objects } ``` @@ -142,24 +173,24 @@ The following schema defines a `Book` interface, along with two object types tha ```graphql interface Book { - title: String - author: Author + title: String! + author: Author! } type Textbook implements Book { - title: String - author: Author - courses: [Course] + title: String! + author: Author! + courses: [Course!]! } type ColoringBook implements Book { - title: String - author: Author - colors: [Color] + title: String! + author: Author! + colors: [String!]! } type Query { - schoolBooks: [Book] + books: [Book!]! } ``` @@ -171,7 +202,7 @@ If a field's return type is an interface, clients can query that field for any s ```graphql query GetBooks { - schoolBooks { + books { title author } @@ -182,17 +213,19 @@ Clients can _also_ query for subfields that _aren't_ included in the interface: ```graphql query GetBooks { - schoolBooks { - title # Always present (part of Book interface) + books { + # Querying for __typename is almost always recommended, + # but it's even more important when querying a field that + # might return one of multiple types. + __typename + title ... on Textbook { courses { # Only present in Textbook name } } ... on ColoringBook { - colors { # Only present in ColoringBook - name - } + colors # Only present in ColoringBook } } } @@ -200,22 +233,53 @@ query GetBooks { This query uses [inline fragments](https://graphql.org/learn/queries/#inline-fragments) to fetch a `Book`'s `courses` (if it's a `Textbook`) or its `colors` (if it's a `ColoringBook`). +Here's a valid result for the above query: + +```json +{ + "data": { + "books": [ + { + "__typename": "Textbook", + "title": "Wheelock's Latin", + "courses": [ + { + "name": "Latin I" + } + ] + }, + { + "__typename": "ColoringBook", + "title": "Oops All Water", + "colors": [ + "Blue" + ] + } + ] + } +} +``` + For more information, see [Using fragments with unions and interfaces](https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces). ### Resolving an interface +> Before reading this section, [learn about resolvers](../data/resolvers/). + [As with union types](#resolving-a-union), Apollo Server requires interfaces to define a `__resolveType` function to determine which implementing object type is being returned. Here's an example `__resolveType` function for the `Book` interface defined above: -```js{3-11} +```js{3-13} const resolvers = { Book: { __resolveType(book, context, info){ + // Only Textbook has a courses field if(book.courses){ return 'Textbook'; } + // Only ColoringBook has a colors field if(book.colors){ return 'ColoringBook'; }