Catch the highlights of GraphQLConf 2023! Click for recordings. Or check out our recap blog post.
v3 (latest)
Fetching
Mutations

Mutations

In addition to fetching data using queries, Apollo also handles GraphQL mutations. Mutations are identical to queries in syntax, the only difference being that you use the keyword mutation instead of query to indicate that the operation is used to change the dataset behind the schema.

mutation upvotePost {
  upvotePost(postId: 12) {
    id
    votes
  }
}

GraphQL's mutations consist of two parts:

  1. The mutation name with arguments (upvotePost), which represents the actual operation to be done on the server
  2. The fields you want back from the result of the mutation to update the client (id and vote)

The result of the above mutation might be:

{
  "data": {
    "upvotePost": {
      "id": 12,
      "votes": 123
    }
  }
}

When we use mutations in Apollo, the result is typically integrated into the cache automatically based on the id of the result, which in turn updates UI automatically, so we don't explicitly handle the results ourselves. In order for the client to correctly do this, we need to ensure we select the correct fields (as in all the fields that we care about that may have changed).

Basic Mutations

Using Apollo it's easy to call mutation. You can simply use mutate method.

import { Apollo, gql } from 'apollo-angular';
import { Component } from '@angular/core';
 
const UPVOTE_POST = gql`
  mutation UpvotePost {
    upvotePost(postId: 12) {
      id
      votes
    }
  }
`;
 
@Component({
  // ...
})
class UpvotePostComponent {
  constructor(private apollo: Apollo) {}
 
  newRepository() {
    this.apollo.mutate({ mutation: UPVOTE_POST }).subscribe();
  }
}

Calling mutations

Most mutations will require arguments in the form of query variables, and you may wish to provide other options to ApolloClient#mutate. You can directly pass options to mutate when you call it in the wrapped component:

import { Apollo, gql } from 'apollo-angular';
import { Component } from '@angular/core';
 
const UPVOTE_POST = gql`
  mutation UpvotePost($postId: Int!) {
    upvotePost(postId: $postId) {
      id
      votes
    }
  }
`;
 
@Component({
  // ...
})
class UpvotePostComponent {
  constructor(private apollo: Apollo) {}
 
  upvote() {
    this.apollo
      .mutate({
        mutation: UPVOTE_POST,
        variables: {
          postId: 12,
        },
      })
      .subscribe(
        ({ data }) => {
          console.log('got data', data);
        },
        error => {
          console.log('there was an error sending the query', error);
        },
      );
  }
}

As you can see, mutate method returns an Observable that resolves with ApolloQueryResult. It is the same result we get when we fetch queries.

However, typically you'd want to keep the concern of understanding the mutation's structure out of your presentational component. The best way to do this is to use a service to bind your mutate function:

import { Apollo, gql } from 'apollo-angular';
import { Component, Injectable } from '@angular/core';
 
const UPVOTE_POST = gql`
  mutation UpvotePost($postId: Int!) {
    upvotePost(postId: $postId) {
      id
      votes
    }
  }
`;
 
@Injectable({
  providedIn: 'root',
})
class UpvoteService {
  constructor(private apollo: Apollo) {}
 
  upvote(postId: string) {
    return this.apollo.mutate({
      mutation: UPVOTE_POST,
      variables: {
        postId,
      },
    });
  }
}
 
@Component({
  // ...
})
class UpvoteComponent {
  constructor(private upvoteService: UpvoteService) {}
 
  newRepository() {
    this.upvoteService.upvote(12).subscribe(
      ({ data }) => {
        console.log('got data', data);
      },
      error => {
        console.log('there was an error sending the query', error);
      },
    );
  }
}
💡

Note: in general you shouldn't attempt to use the results from the mutation callback directly, instead you can rely on Apollo's id-based cache updating to take care of it for you, or if necessary passing an updateQueries callback to update the result of relevant queries with your mutation results.

Optimistic UI

Sometimes your client code can easily predict the result of the mutation, if it succeeds, even before the server responds with the result. For instance, in GitHunt, when a user comments on a repository, we want to show the new comment in context immediately, without waiting on the latency of a round trip to the server, giving the user the experience of a snappy UI. This is what we call Optimistic UI. This is possible if the client can predict an Optimistic Response for the mutation.

Apollo Client gives you a way to specify the optimisticResponse option, that will be used to update active queries immediately, in the same way that the server's mutation response will. Once the actual mutation response returns, the optimistic part will be thrown away and replaced with the real result.

import { Apollo, gql } from 'apollo-angular';
import { Component } from '@angular/core';
 
const CHANGE_POST_TITLE = gql`
  mutation ChangePostTitle($postId: Int!, $title: String!) {
    changePostTitle(postId: $postId, title: $title) {
      id
      title
    }
  }
`;
 
@Component({
  // ...
})
class PostComponent {
  currentUser: User;
 
  constructor(private apollo: Apollo) {}
 
  upvote({ postId, title }) {
    this.apollo
      .mutate({
        mutation: CHANGE_POST_TITLE,
        variables: { postId, title },
        optimisticResponse: {
          __typename: 'Mutation',
          changePostTitle: {
            __typename: 'Post',
            id: postId,
            title,
          },
        },
      })
      .subscribe();
  }
}

For the example above, it is easy to construct an optimistic response, since we know the shape of the new comment and can approximately predict the created date. The optimistic response doesn't have to be exactly correct because it will always will be replaced with the real result from the server, but it should be close enough to make users feel like there is no delay.

💡

As this comment is new and not visible in the UI before the mutation, it won't appear automatically on the screen as a result of the mutation. You can use updateQueries to make it appear in this case.

Loading State

The result of Apollo.mutate() contains loading property. By default, it's always false and the result is emitted with the response from the ApolloLink execution chain. In order to correct it you can enable useMutationLoading flag in configuration.

import { APOLLO_FLAGS, APOLLO_OPTIONS, ApolloModule } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { HttpClientModule } from '@angular/common/http';
import { InMemoryCache } from '@apollo/client/core';
 
@NgModule({
  imports: [BrowserModule, ApolloModule, HttpClientModule],
  providers: [
    {
      provide: APOLLO_FLAGS,
      useValue: {
        useMutationLoading: true, // enable it here
      },
    },
    {
      provide: APOLLO_OPTIONS,
      useFactory: (httpLink: HttpLink) => {
        return {
          cache: new InMemoryCache(),
          link: httpLink.create({
            uri: 'https://48p1r2roz4.sse.codesandbox.io',
          }),
        };
      },
      deps: [HttpLink],
    },
  ],
})
export class AppModule {}
⚠️

useMutationLoading is disabled to avoid any breaking changes, this may be enabled in next major version.

import { Apollo, gql } from 'apollo-angular';
import { Injectable } from '@angular/core';
 
const UPVOTE_POST = gql`
  mutation UpvotePost($postId: Int!) {
    upvotePost(postId: $postId) {
      id
      votes
    }
  }
`;
 
@Injectable({
  providedIn: 'root',
})
class UpvoteService {
  constructor(private apollo: Apollo) {}
 
  upvote(postId: string) {
    return this.apollo
      .mutate({
        mutation: UPVOTE_POST,
        variables: { postId },
      })
      .subscribe(result => {
        console.log({
          loading: result.loading,
          data: result.data,
        });
 
        // First call:
        //  { loading: true }
        // Second call:
        //  { loading: false, data: {...} }
      });
  }
}

Designing Mutation Results

When people talk about GraphQL, they often focus on the data fetching side of things, because that's where GraphQL brings the most value. Mutations can be pretty nice if done well, but the principles of designing good mutations, and especially good mutation result types, are not yet well-understood in the open source community. So when you are working with mutations it might often feel like you need to make a lot of application-specific decisions.

In GraphQL, mutations can return any type, and that type can be queried just like a regular GraphQL query. So the question is - what type should a particular mutation return?

In GraphQL itself, there isn't any specification about how this is supposed to work. In most cases, the data available from a mutation result should be the server developer's best guess of the data a client would need to understand what happened on the server. For example, a mutation that creates a new comment on a blog post might return the comment itself. A mutation that reorders an array might need to return the new array.