Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update validation page. #1803

Merged
merged 2 commits into from
Nov 15, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 84 additions & 89 deletions src/pages/learn/validation.mdx
Original file line number Diff line number Diff line change
@@ -1,97 +1,47 @@
# Validation

By using the type system, it can be predetermined whether a GraphQL query
is valid or not. This allows servers and clients to effectively inform
developers when an invalid query has been created, without having to rely
on runtime checks.
<p className="learn-subtitle">Learn how GraphQL validates operations using a schema</p>

For our Star Wars example, the file
[starWarsValidation-test.ts](https://github.com/graphql/graphql-js/blob/main/src/__tests__/starWarsValidation-test.ts)
contains a number of queries demonstrating various invalidities, and is a test
file that can be run to exercise the reference implementation's validator.
On this page, we'll explore an important phase in the lifecycle of a GraphQL request called [validation](https://spec.graphql.org/draft/#sec-Validation). A request must be syntactically correct to run, but it should also be valid when checked against the API's schema.

To start, let's take a complex valid query. This is a nested query, similar to
an example from the previous section, but with the duplicated fields factored
out into a fragment:
In practice, when a GraphQL operation reaches the server, the document is first parsed and then validated using the type system. This allows servers and clients to effectively inform developers when an invalid query has been created, and without relying on runtime checks. Once the operation is validated, it can be [executed](/learn/execution/) on the server and a response will be delivered to the client.
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

```graphql
# { "graphiql": true }
{
hero {
...NameAndAppearances
friends {
...NameAndAppearances
friends {
...NameAndAppearances
}
}
}
}

fragment NameAndAppearances on Character {
name
appearsIn
}
```

And this query is valid. Let's take a look at some invalid queries...

A fragment cannot refer to itself or create a cycle, as this could result in
an unbounded result! Here's the same query above but without the explicit three
levels of nesting:
## Validation examples

```graphql
# { "graphiql": true }
{
hero {
...NameAndAppearancesAndFriends
}
}
The GraphQL specification describes the detailed conditions that must be satisfied for a request to be considered valid. In the sections that follow, we'll look at a few examples of common validation issues that occur in GraphQL operations.
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

fragment NameAndAppearancesAndFriends on Character {
name
appearsIn
friends {
...NameAndAppearancesAndFriends
}
}
```
### Requesting non-existent fields

When we query for fields, we have to query for a field that exists on the
given type. So as `hero` returns a `Character`, we have to query for a field
on `Character`. That type does not have a `favoriteSpaceship` field, so this
query is invalid:
When we query for fields, we have to request a field on the given type. So as `hero` returns a `Character` type, we have to query for a field that's defined on `Character`. That type does not have a `favoriteSpaceship` field, so this query is invalid:
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

```graphql
# { "graphiql": true }
# INVALID: favoriteSpaceship does not exist on Character
{
query {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interested to know why you're moving away from the query shorthand here? It tends to be very rare that I see this - either the operation has a name and/or variables in which case you need query, or it has neither in which case it's very rare to see it in my experience.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tended to see the opposite (inclusion of the query keyword) so I included it for clarity after all of the keywords are introduced on the operation pages. But I can remove it throughout if the shorthand syntax is the preferred convention.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's worry about that afterwards, if at all; it's much easier to replace /^query {$/ with { than the reverse - so keep going with the query keyword for now.

Interesting that your experience differs; I wonder what the ecosystem as a whole sees!

hero {
favoriteSpaceship
}
}
```

Whenever we query for a field and it returns something other than a scalar
or an enum, we need to specify what data we want to get back from the field.
Hero returns a `Character`, and we've been requesting fields like `name` and
`appearsIn` on it; if we omit that, the query will not be valid:
### Selecting invalid leaf fields
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

Whenever we query for a field and it returns something other than a Scalar or Enum type, we need to specify what data we want to get back from the field. The `hero` query field returns a `Character`, and we've already seen examples that request fields like `name` and `appearsIn` on it. If we omit those leaf field selections, then the query will not be valid:
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

```graphql
# { "graphiql": true }
# INVALID: hero is not a scalar, so fields are needed
{
query {
hero
}
```

Similarly, if a field is a scalar, it doesn't make sense to query for
additional fields on it, and doing so will make the query invalid:
Similarly, if the leaf field is a scalar value, it doesn't make sense to query for additional fields on it, and doing so will make the query invalid:
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

```graphql
# { "graphiql": true }
# INVALID: name is a scalar, so fields are not permitted
{
query {
hero {
name {
firstCharacterOfName
Expand All @@ -100,32 +50,26 @@ additional fields on it, and doing so will make the query invalid:
}
```

Earlier, it was noted that a query can only query for fields on the type
in question; when we query for `hero` which returns a `Character`, we
can only query for fields that exist on `Character`. What happens if we
want to query for R2-D2s primary function, though?
### Missing fragments for fields that output abstract types

Earlier, it was noted that a query can only ask for fields on the type in question. So when we query for `hero` which returns a `Character`, we can only request fields that exist on the `Character` Interface type. What happens if we want to query for R2-D2's primary function, though?

```graphql
# { "graphiql": true }
# INVALID: primaryFunction does not exist on Character
{
query {
hero {
name
primaryFunction
}
}
```

That query is invalid, because `primaryFunction` is not a field on `Character`.
We want some way of indicating that we wish to fetch `primaryFunction` if the
`Character` is a `Droid`, and to ignore that field otherwise. We can use
the fragments we introduced earlier to do this. By setting up a fragment defined
on `Droid` and including it, we ensure that we only query for `primaryFunction`
where it is defined.
That query is invalid, because `primaryFunction` is not one of the shared fields defined by the `Character` Interface type. We want some way of indicating that we wish to fetch `primaryFunction` if the `Character` is a `Droid`, and to ignore that field otherwise. We can use the [fragments](/learn/queries/#fragments) to do this. By setting up a fragment defined on `Droid` and including it in the selection set, we ensure that we only query for `primaryFunction` where it is defined:

```graphql
# { "graphiql": true }
{
query {
hero {
name
...DroidFields
Expand All @@ -137,15 +81,11 @@ fragment DroidFields on Droid {
}
```

This query is valid, but it's a bit verbose; named fragments were valuable
above when we used them multiple times, but we're only using this one once.
Instead of using a named fragment, we can use an inline fragment; this
still allows us to indicate the type we are querying on, but without naming
a separate fragment:
This query is valid, but it's a bit verbose; named fragments were valuable above when we used them multiple times, but we're only using this one once. Instead of using a named fragment, we can use an [inline fragment](/learn/queries/#inline-fragments); this still allows us to indicate the type we are querying on, but without naming a separate fragment:

```graphql
# { "graphiql": true }
{
query {
hero {
name
... on Droid {
Expand All @@ -155,10 +95,65 @@ a separate fragment:
}
```

This has just scratched the surface of the validation system; there
are a number of validation rules in place to ensure that a GraphQL query
is semantically meaningful. The specification goes into more detail about this
topic in the "Validation" section, and the
[validation](https://github.com/graphql/graphql-js/blob/main/src/validation)
directory in GraphQL.js contains code implementing a
specification-compliant GraphQL validator.
### Cyclic fragment spreads

To start, let's take a complex valid query. This is a nested query, but with the duplicated fields factored out into a fragment:

```graphql
# { "graphiql": true }
query {
hero {
...NameAndAppearances
friends {
...NameAndAppearances
friends {
...NameAndAppearances
}
}
}
}

fragment NameAndAppearances on Character {
name
appearsIn
}
```

And this query is valid. Let's take a look at some invalid queries...

A fragment cannot refer to itself or create a cycle, as this could result in an unbounded result! Here's the same query above but without the explicit three levels of nesting:
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

```graphql
# { "graphiql": true }
query {
hero {
...NameAndAppearancesAndFriends
}
}

fragment NameAndAppearancesAndFriends on Character {
name
appearsIn
friends {
...NameAndAppearancesAndFriends
}
}
```

This has just scratched the surface of the validation system; there are a number of validation rules in place to ensure that a GraphQL operation is semantically meaningful. The specification goes into more detail about this topic in the [validation section](https://spec.graphql.org/draft/#sec-Validation), and the [validation directory](https://github.com/graphql/graphql-js/blob/main/src/validation) in GraphQL.js contains code implementing a specification-compliant GraphQL validator.
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

## Validation errors

As we have seen in the examples above, when a GraphQL server encounters a validation error in a request, it will return information about what happened in the `errors` key of the response. Specifically, when GraphQL detects a validation issue in a document, it raises a _request error_ before execution begins, meaning that no partial data will be included in the response.

And because the GraphQL specification requires all implementations to validate incoming requests against the schema, developers won't need to write specific runtime logic to handle these validation issues manually.

## Next steps

To recap what we've learned about validation:

- The GraphQL documents that clients submit in their requests must be syntactically correct and considered valid when checked against the schema
- The GraphQL specification requires that implementations check that incoming requests contain valid field selections, correct fragment usage, and more
- When a validation issue occurs, the server will raise a request error and return information about what happened to the client before field execution occurs
mandiwise marked this conversation as resolved.
Show resolved Hide resolved

Head over to the [Execution](/learn/execution/) page to learn how GraphQL provides data for each field in a request after the validation step is complete.
mandiwise marked this conversation as resolved.
Show resolved Hide resolved