LWC: Accessing GraphQL data with the Apollo client



If you're a front-end developer calling GraphQL endpoints from your UI, you must have heard about the JavaScript Apollo Client library. This library is a state management library that let developers execute GraphQL queries against GraphQL servers as well as a local store. It handles caching, change notifications and features interesting capabilities like optimistic UI.

The flagship implementation from the Apollo team is dedicated to React but, fortunately, the implementation is split in two layers:
  • A pure JavaScript Apollo Client library
    This library is not related to any client technology. It implements all the necessary plumbing to support GraphQL queries in JavaScript.
  • View integrations
    Provided on top of the aforementioned library, these integrations make it easier to consume the Apollo client with your technology of choice (React, Angular, Vue, Svelte, Ember, ...). If the React and Angular integrations are done by the Apollo team, the others are provided by third party contributors.

Integrating LWC

There is a Web Component integration available, which can work with LWC as well. Actually, the LWC adapter code on top of this library would be minimal. On the other hand, it won't take advantage of the LWC specific features, in particular the wire adapters. So the idea is to create a new integration specifically for LWC, leveraging wire adapters similarly to hooks in the React integration.

For the impatient, the integration is available here: apollo-client. It provides a sample application that shows CRUD operations on a server, in memory list of books.

GraphQL Queries

A GraphQL query in the React integration uses a hook named useQuery.  Here is an example:

  const { loading, error, data } = useQuery(MY_QUERY);

A very similar syntax can be achieved with LWC wire adapters. We even keep the same name, useQuery, for the wire adapter:

  @wire(useQuery, { query: MY_QUERY }) results;

The results property features the following sub properties, similarly to the React hook:

  { loading, error, data }

Any of these sub properties is available at any time, so the component template can display appropriate content, with pseudo code like:
  <template if:true={results.loading}>
    <img src="loading.gif"/>Loading...
  </template>
  <template if:true={results.error}>
    Man, there is an error {results.error}
  </template>
  <template if:true={results.data}>
    Here is the data: {results.data}
  </template>

The results property also features even more sub properties. In particular, it offers a fetch() method that re-fetches the query.

Finally, a wire adapter has more options than just the query: it can use a specific Apollo client,  defer the query execution with lazy,  use variables and any other standard options used by the Apollo client watchQuery(). See README.md for a more exhaustive list of options.

Here is a example of a query using variables for pagination, assuming that the query supports offset and limit parameters:

  const BOOK_LIST = gql`
      query($offset: Int!, $limit: Int!) {
          books(offset: $offset, limit: $limit) {
            id,
            title
            author
          }
      }`;
  

  class Booklist extends LightningElement {

    variables = {
        offset: 0,
        limit: 10
    }

    @wire(useQuery, {
        query: BOOK_LIST,
        variables: '$variables'
    }) books;


A more complete example is available in the project sample application.

Because wire adapters options can only observe changes to root properties, we created the variables property to hold the query variables. Then, when we want to update one of its value (ex: offset), then we have to assign a brand new object to that property:
    handleFirst() {
        this.variables = {
            ...this.variables,
            offset: 0
        }
    }


Finally, if it is possible to use the query inline, it is better to declare it as a constant (BOOK_LIST in the example). This is because the current wire adapter implementation will repeat that value multiple times in the generated code.

GraphQL Mutations

Mutations are also provided using wire adapters, although they should be executed on demand as opposed to when the component is connected. The wire adapter provides a mutate method that can be called anytime to execute the mutation.

Bellow is a simple sample that creates a book:

  // Define the mutation query
  const BOOK_CREATE = gql`
    mutation createBook($book: BookInput!) {
        createBook(book: $book) {
            id
            title
            author
            genre
            publisher
        }
    }
  `;


  // Mutation adapter use in the component class
  @wire(useMutation, {mutation: BOOK_CREATE}) bookCreate;
  onCreateBook() {

    const newBookValues = {
      book: {
        title: "A book",
        author: "John Doe",
        genre: "Novel",
        publisher: "MyBook"
      }
    };
    this.bookCreate.mutate({
     
newBookValues
    });
  }

The sample app coming with the project shows all the CRUD operations on books: Create, Read, Update and Delete.

And What's Next?

Well this project is a first shot. A lot more has to be done, but this is a good start, isn't it? It needs your contributions to be on par with the React implementation. Please feel free to submit your ideas, code or examples.

Comments