Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
39 changes: 15 additions & 24 deletions site/docs/Learn-Queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,36 +181,27 @@ Most discussions of GraphQL focus on data fetching, but any complete data platfo

In REST, any request might end up causing some side-effects on the server, but by convention it's suggested that one doesn't use `GET` requests to modify data. GraphQL is similar - technically any query could be implemented to cause a data write. However, it's useful to establish a convention that any operations that cause writes should be sent explicitly via a mutation.

Here's an example of a simple mutation:
Just like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update. Let's look at a simple example mutation:

```graphql
mutation CreateCharacterInEpisode($name: String!, $appearsIn: Episode!) {
createCharacter(name: $name)
addCharacterToEpisode(name: $name, episode: $appearsIn)
# { "graphiql": true, "variables": { "ep": "JEDI", "review": { "stars": 5, "commentary": "This is a great movie!" } } }
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
```

You can see that a mutation can contain multiple fields, just like a query. There's one important distinction between queries, and mutations, other than the name:

**While query fields are executed in parallel, mutation fields run in series, one after the other.**

This means that even though we sent `createCharacter` and `addCharacterToEpisode` in one request, the first is guaranteed to finish before the second begins, ensuring that we create the character before trying to add it an episode.
In this case, the `incrementCredits` mutation field returns a `Human` object, so we can query the new value of `totalCredits` after giving that character some more credits. Otherwise, we would have needed to send two requests - one to update the credits, and another to get the new value - or guess at the new amount based on outdated data.

#### Returning data from mutations
#### Multiple fields in mutations

Just like in queries, you can ask for nested fields in the mutation result. This can be useful for fetching the new state of an object after an update:
A mutation can contain multiple fields, just like a query. There's one important distinction between queries and mutations, other than the name:

```graphql
mutation IncrementCredits($characterId: ID!) {
incrementCredits(characterId: $characterId) {
totalCredits
}
}
```

In this case, the `incrementCredits` mutation field returns a `Character` object, so we can query the new value of `totalCredits` after giving that character some more credits. Otherwise, we would have needed to send two requests - one to update the credits, and another to get the new value - or guess at the new amount based on outdated data.
**While query fields are executed in parallel, mutation fields run in series, one after the other.**

That's all! Now you know everything you need to know about GraphQL queries and mutations to build a pretty good application. For more advanced features and tips, check out the advanced section.
This means that if we send two `incrementCredits` mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don't end up with a race condition with ourselves.

### Fragments and type conditions

Expand All @@ -226,8 +217,8 @@ query HeroForEpisode($ep: Episode!) {
... on Droid {
primaryFunction
}
... on Droid {
homePlanet
... on Human {
height
}
}
}
Expand All @@ -237,4 +228,4 @@ In this query, the `hero` field returns the type `Character`, which might be eit

To ask for a field on the concrete type, you need to use an _inline fragment_ with a type condition. Because the first fragment is labeled as `... on Droid`, the `primaryFunction` field will only be executed if the `Character` returned from `hero` is of the `Droid` type. Similarly for the `homePlanet` field for the `Human` type.

Named fragments can also be used in the same way, since a named fragment always has a type condition attached.
Named fragments can also be used in the same way, since a named fragment always has a type attached.
76 changes: 48 additions & 28 deletions site/docs/Learn-Schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The most basic components of a GraphQL schema are object types, which just repre
```graphql
type Character {
name: String!
appearsIn: [Episode]
appearsIn: [Episode]!
}
```

Expand All @@ -55,6 +55,11 @@ The language is pretty readable, but let's go over it so that we can have a shar

Now you know what a GraphQL object type looks like, and how to read the basics of the GraphQL type language.

### Arguments

TODO
- Include the idea of default arguments

### The Query and Mutation types

Most types in your schema will just be normal object types, but there are two types that are special within a schema:
Expand All @@ -74,7 +79,7 @@ query {
hero {
name
}
droid(id: "2001") {
droid(id: "2000") {
name
}
}
Expand Down Expand Up @@ -155,7 +160,7 @@ Object types, scalars, and enums are the only kinds of types you can define in G
```graphql
type Character {
name: String!
appearsIn: [Episode]
appearsIn: [Episode]!
}
```

Expand All @@ -165,7 +170,7 @@ The Non-Null type modifier can also be used when defining arguments for a field,

```graphql
# { "graphiql": true, "variables": { "id": null } }
query DroidById($id: String!) {
query DroidById($id: ID!) {
droid(id: $id) {
name
}
Expand Down Expand Up @@ -212,10 +217,10 @@ For example, you could have an interface `Character` that represents any charact

```graphql
interface Character {
id: String!
name: String
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]
appearsIn: [Episode]!
}
```

Expand All @@ -225,25 +230,28 @@ For example, here are some types that might implement `Character`:

```graphql
type Human implements Character {
id: String!
name: String
id: ID!
name: String!
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's probably better to avoid overuse of !. For one, it's noisy when teaching graphql concepts, but also it's a dangerous default state when built on languages that can throw errors

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On the other hand, in some typed languages having everything be nullable makes it a bit annoying to work with the data. I talked to Dan and he also had a few ideas about it which basically came down to - data that involves fetches should always be nullable in case the fetch fails, but scalar fields on objects that are returned as one unit should maybe be non-null.

But that's mostly an academic concern and I agree too many ! clutter the schema.

friends: [Character]
appearsIn: [Episode]
homePlanet: String
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}

type Droid implements Character {
id: String!
name: String
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]
appearsIn: [Episode]!
primaryFunction: String
}
```

You can see that both of these types have all of the fields from the `Character` interface, but also bring in extra fields, `homePlanet` and `primaryFunction`, that are specific to that particular type of character.

Interfaces are useful when you want to return an object or set of objects, but those might be of several different types. For example, in the following query:
Interfaces are useful when you want to return an object or set of objects, but those might be of several different types.

For example, note that the following query produces an error:

```graphql
# { "graphiql": true, "variables": { "ep": "JEDI" } }
Expand All @@ -255,7 +263,9 @@ query HeroForEpisode($ep: Episode!) {
}
```

The `hero` field returns the type `Character`, which means it might be either a `Human` or a `Droid` depending on the `episode` argument. In the query above, you can only ask for fields that exist on the `Character` interface, and to ask for a field on a specific object type, you need to use an inline fragment:
The `hero` field returns the type `Character`, which means it might be either a `Human` or a `Droid` depending on the `episode` argument. In the query above, you can only ask for fields that exist on the `Character` interface, which doesn't include `primaryFunction`.

To ask for a field on a specific object type, you need to use an inline fragment:

```graphql
# { "graphiql": true, "variables": { "ep": "JEDI" } }
Expand All @@ -275,20 +285,30 @@ Learn more about this in the [inline fragments](XXX) section in the query guide.

Union types are very similar to interfaces, but they don't get to specify any common fields between the types.

XXX no example in SWAPI

```graphql
union SearchResult = Photo | Person
union SearchResult = Human | Droid | Starship
```

type Person {
name: String
age: Int
}
Wherever we return a `SearchResult` type in our schema, we might get a `Human`, a `Droid`, or a `Starship`. Note that members of a union type need to be concrete object types; you can't create a union type out of interfaces or other unions.

type Photo {
height: Int
width: Int
In this case, if you query a field that returns the `SearchResult` union type, you need to use a conditional fragment to be able to query any fields at all:

```graphql
# { "graphiql": true}
{
search(text: "an") {
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
```

In this case, if you query a field that returns the `SearchResult` union type, you need to use a conditional fragment to be able to query any fields at all.
Loading