Skip to content

Update operation docs #1802

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

Merged
merged 26 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fddcb50
Add more mutations for Learn docs.
mandiwise Oct 20, 2024
a8dfe56
Add updated operation content.
mandiwise Oct 26, 2024
4960a1d
Update left nav with new pages.
mandiwise Oct 26, 2024
d472a10
Incorporate review feedback.
mandiwise Oct 29, 2024
bb48e66
Apply suggestions from code review
mandiwise Nov 17, 2024
4e4aa97
Apply suggestions from code review to queries page
mandiwise Nov 17, 2024
ddd7d8c
Update src/pages/learn/queries.mdx
mandiwise Nov 17, 2024
0487e6f
Update subheads.
mandiwise Nov 11, 2024
199c9fb
Update Learn introduction page. (#1799)
mandiwise Oct 31, 2024
9f199f8
Fix link to /learn/schema (#1809)
benjie Nov 1, 2024
18c1ee1
Add new python client library `ql` (#1765)
dsal3389 Nov 1, 2024
4cf14e4
Add referrer to "Found broken link" issue template (#1810)
benjie Nov 1, 2024
563b518
blog post: CfP for FOSDEM APIs and Friends devroom (#1811)
jemgillam Nov 4, 2024
d89e54e
Add new gateway and tool `hive` (#1815)
kamilkisiela Nov 7, 2024
57964b1
Update schema and types docs (#1800)
mandiwise Nov 8, 2024
27e1d6a
Remove Tipe from Tools (#1816)
kamilkisiela Nov 8, 2024
1a5a820
Remove Prisma from Tools (#1817)
kamilkisiela Nov 10, 2024
e257efc
fix videos for conf 2024 (#1819)
dimaMachina Nov 11, 2024
affc6f9
Update introspection docs (#1812)
mandiwise Nov 15, 2024
eabd316
Update execution docs (#1804)
mandiwise Nov 15, 2024
d676ea3
Update serving over HTTP docs. (#1813)
mandiwise Nov 15, 2024
b83f1ca
Update validation page. (#1803)
mandiwise Nov 15, 2024
c602564
Add additional mutation example.
mandiwise Nov 19, 2024
61af975
Merge branch 'source' into update-operation-docs
benjie Nov 21, 2024
afe1f30
Apply suggestions from code review
mandiwise Nov 21, 2024
1f3ae06
Apply more changes from code review.
mandiwise Nov 21, 2024
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: 39 additions & 0 deletions src/components/marked/swapi-schema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ const typeDefs = /* GraphQL */ `
"The mutation type, represents all updates we can make to our data"
type Mutation {
createReview(episode: Episode, review: ReviewInput!): Review
rateFilm(episode: Episode!, rating: FilmRating!): Film
updateHumanName(id: ID!, name: String!): Human
deleteStarship(id: ID!): ID
}

"The episodes in the Star Wars trilogy"
Expand All @@ -45,6 +48,24 @@ const typeDefs = /* GraphQL */ `
JEDI
}

"A personal rating for a Star Wars episode"
enum FilmRating {
"Negative rating"
THUMBS_DOWN

"Positive rating"
THUMBS_UP
}

"A film from the Star Wars trilogy"
type Film {
"The Star Wars episode portrayed in the film"
episode: Episode!

"The authenticated user's rating of the film"
viewerRating: FilmRating
}

"A character from the Star Wars universe"
interface Character {
"The ID of the character"
Expand Down Expand Up @@ -365,6 +386,24 @@ const resolvers = {
},
Mutation: {
createReview: (root, { episode, review }) => review,
rateFilm: (root, { episode, rating }) => ({
episode,
viewerRating: rating,
}),
updateHumanName: (root, { id, name }) => {
const human = humanData[id]
if (!human) {
throw new Error("Human not found")
}
return { ...human, name }
},
deleteStarship: (root, { id }) => {
const starship = getStarship(id)
if (!starship) {
throw new Error("Starship not found")
}
return id
},
},
Character: {
__resolveType(data, context, info) {
Expand Down
4 changes: 3 additions & 1 deletion src/pages/learn/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ export default {
title: "Learn",
},
index: "Introduction",
queries: "Queries and Mutations",
schema: "Schemas and Types",
queries: "",
mutations: "",
subscriptions: "",
validation: "",
execution: "",
response: "",
Expand Down
161 changes: 161 additions & 0 deletions src/pages/learn/mutations.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { Callout } from "nextra/components"

# Mutations

<p className="learn-subtitle">Learn how to modify data with a GraphQL server</p>

Most discussions of GraphQL focus on data fetching, but any complete data platform needs a way to modify server-side data as well.

In REST, any request might cause 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 field resolver could be implemented to cause a data write—but the [GraphQL specification states](https://spec.graphql.org/draft/#sel-GANRNDAB6DBmMn6D) that "the resolution of fields other than top-level mutation fields must always be side effect-free and idempotent." Thus, for any spec-compliant GraphQL schemas, only the top-level fields in mutation operations are allowed to cause side effects.

On this page, you'll learn how to use mutation operations to write data using GraphQL, and do so in a way that supports client use cases.

<Callout type="info">
All of the features of GraphQL operations that apply to queries also apply to mutations, so review the [Queries](/learn/queries/) page first before proceeding.
</Callout>

## Add new data

When creating new data with a REST API, you would send a `POST` request to a specific endpoint and include information about the entities to be created in the body of the request. GraphQL takes a different approach.

Let's look at an example mutation that's defined in our schema:

```graphql
enum Episode {
NEWHOPE
EMPIRE
JEDI
}

input ReviewInput {
stars: Int!
commentary: String
}

type Mutation {
createReview(episode: Episode, review: ReviewInput!): Review
}
```

Like queries, mutation fields are added to one of the [root operation types](https://spec.graphql.org/draft/#sec-Root-Operation-Types) that provide an entry point to the API. In this case, we define the `createReview` field on the `Mutation` type.

Mutation fields can also accept arguments and you might notice that the `review` argument has an input type set to `ReviewInput`. This is known as [Input Object type](/learn/schema/#input-object-types), which allows us to pass in a whole object to be created instead of individual scalar values only.

Also like just like queries, if the mutation field returns an Object type, then you specify a selection set of its fields in the operation:

```graphql
# { "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
}
}
```

While the `createReview` field could be defined with any valid output type in the schema, it's conventional to specify an output type that corresponds to whatever is modified during the mutation—in this case, the `Review` type. This can be useful for clients that need to fetch the new state of an object after an update.

Recall that GraphQL is meant to work with your existing code and data, so the actual creation of the review is up to you when clients send this operation to the GraphQL server. A hypothetical function that writes the new review to a database during a `createReview` mutation might look like this:

```js
Mutation: {
createReview(obj, args, context, info) {
return context.db
.createNewReview(args.episode, args.review)
.then((reviewData) => new Review(reviewData))
}
}
```

You can learn more about GraphQL provides data for fields on the [Execution page](/learn/execution).

## Update existing data

Similarly, we use mutations to update existing data. To change a human's name, we'll define a new mutation field and set that field's output type to the `Human` type so we can return the updated human's information to client after the server successfully writes the data:

```graphql
type Mutation {
updateHumanName(id: ID!, name: String!): Human
}
```

This operation will update Luke Skywalker's name:

```graphql
# { "graphiql": true, "variables": { "id": "1000", "name": "Luke Starkiller" } }
mutation UpdateHumanName($id: ID!, $name: String!) {
updateHumanName(id: $id, name: $name ) {
id
name
}
}
```

This example demonstrates an important distinction from REST. To update a human's properties using a REST API, you would likely send any updated data to a generalized endpoint for that resource using a `PATCH` request. With GraphQL, instead of simply creating an `updateHuman` mutation, you can define more specific mutation fields such as `updateHumanName`that are designed for the task at hand.

Purpose-built mutation fields can help make a schema more expressive by allowing the input types for field arguments to be Non-Null types (a generic `updateHuman` mutation would likely need to accept many nullable arguments to handle different update scenarios). Defining this requirement in the schema also eliminates the need for other runtime logic to determine that the appropriate values were submitted to perform the client's desired write operation.

GraphQL also allows us to express relationships between data that would be more difficult to model semantically with a basic CRUD-style request. For example, a user may wish to save a personal rating for a film. While the rating belongs to the user and doesn't modify anything related to a film itself, we can ergonomically associate it with a `Film` object as follows:

```graphql
# { "graphiql": true, "variables": { "episode": "EMPIRE", "rating": "THUMBS_UP" } }
mutation RateFilm($episode: Episode!, $rating: FilmRating!) {
rateFilm(episode: $episode, rating: $rating) {
episode
viewerRating
}
}
```

As a general rule, schemas should be designed to help clients get the data that they need from the GraphQL API, so the fields defined in a schema should be informed by those use cases.

## Remove existing data

Just as we can send a `DELETE` request to delete a resource with a REST API, we can use mutations to delete some existing data as well by defining another field on the `Mutation` type:
Copy link
Member

Choose a reason for hiding this comment

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

I think we should clarify that in GraphQL we send a POST request to delete

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 have an updated "Serving over HTTP" page almost ready to go where I go into this in detail based on the draft GraphQL over HTTP spec (including when GET may be used too). Does it make sense to duplicate that here? My goal was draw connections with REST patterns the reader would likely be familiar with while explaining how different kinds of mutations work without overloading with too much information. It can be tricky to find the right balance though! Let me know what you think.


```graphql
type Mutation {
deleteStarship(id: ID!): ID!
}
```

Here's an example of the new mutation field:

```graphql
# { "graphiql": true, "variables": { "id": "3003" } }
mutation DeleteStarship($id: ID!) {
deleteStarship(id: $id)
}
```

As with mutations that create and update data, the GraphQL specification doesn't indicate what should be returned from a successful mutation operation that deletes data, but we do have to specify some type as an output type for the field in the schema. Commonly, the deleted entity's ID or a payload object containing data about the entity will be used to indicate that the operation was successful.

## Multiple fields in mutations

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.**

Let's look at an example:

```graphql
# { "graphiql": true }
mutation {
firstShip: deleteStarship(id: "3001")
secondShip: deleteStarship(id: "3002")
}
```

[Serial execution](https://spec.graphql.org/draft/#sec-Normal-and-Serial-Execution) of these top-level fields means that if we send two `deleteStarship` mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don't end up in a race condition with ourselves.

Note that serial execution of top-level `Mutation` fields differs from the notion of a database transaction. Some mutation fields may resolve successfully while others return errors, and there's no way for GraphQL to revert the successful portions of the operation when this happens. So in the previous example, if the first starship is removed successfully but the `secondShip` field raises an error, there is no built-in way for GraphQL to revert the execution of the `firstShip` field afterward.

## Next steps

To recap what we've learned about mutations:

- Clients can create, update, and delete data using a GraphQL API, depending on what capabilities are exposed in the schema
- Depending on client requirements, mutations can be designed to accommodate granular use cases for write operations
- Top-level fields on the `Mutation` type will execute serially, unlike fields on other types which are often executed in parallel

Now that we know how to use a GraphQL server to read and write data, we're ready to learn how to fetch data in real time using [subscriptions](/learn/subscriptions). You may also wish to learn more about how GraphQL queries and mutations can be [served over HTTP](/learn/serving-over-http/).
Loading