RESTful APIs adhere to a reliable architectural standard for transferring data statelessly over the HTTP protocol. Every endpoint of an API semantically describes how a resource should be created (POST), read (GET), updated (PUT/PATCH), deleted (DELETE), etc. Large, data-driven applications consume data from multiple third-party/in-house sources, and each one exposes a unique set of endpoints to manage different resources. Adapting these applications to support a wide range of platforms and device sizes (commonly mobile, desktop and web) may present several problems:

  • Smaller layouts display less information than larger layouts. Additionally, decreasing the size of a browser window hides away more and more supplemental content the smaller the browser window becomes. Because RESTful APIs define the shape of the response sent back to the client, mobile clients tend to over-fetch data compared to their corresponding desktop/web clients, assuming both consume the same set of APIs.

  • Building UIs involves composing components into higher-order components. At the higher levels of the component hierarchy, these higher-order components represent sections and layouts of an application. Each component encapsulates its own logic and data. Therefore, if each component fetches different pieces of information, such as widgets on an analytics dashboard, then a client must send many simultaneous (or successive) requests to satisfy all of its data requirements. This bottleneck is known as the n+1 requests problem, which is REST-specific and happens when a client fetches one collection resource (i.e., /repositories), followed by n singleton resources (i.e., /repositories/<id>). Often, the collection resource under-fetches data because it lacks sufficient information for each of its items.

  • UIs constantly change to reflect the growing needs of users. Endpoints of RESTful APIs may be tied to a particular view or component. This tight coupling makes it difficult for developers to rapidly iterate upon the UI in response to user feedback because any adjustments to the UI may affect its data requirements and result in waiting for the addition of new endpoints and/or the modification of existing endpoints.

Using Facebook's GraphQL query language, the client specifies its exact data requirements to the server via a single endpoint. Establishing a schema (written with the syntax of the GraphQL Schema Definition Language) creates a contract between the client and server that defines what data can be read from and written to the data graph by the client. This data graph centralizes all of the APIs consumed by your application by mapping each field to a resolver that populates it with a value retrieved from an endpoint of one of these APIs, a database, etc.

A client can fetch data from a GraphQL server via plain HTTP and then manually update the UI accordingly. However, GraphQL clients such as Apollo Client abstract away the low-level implementation details of these features underneath a declarative API. Built by the Apollo GraphQL team, Apollo Client is an open-source GraphQL client that provides a lot of out-of-the-box functionality for communicating with a GraphQL server:

  • Sending Queries and Mutations via POST Requests

  • Built-In View Layer Integrations for React, iOS and Android

  • State Management

  • Caching Mechanisms for Pagination and Storing Recent Query Results

  • Customizable Data Flow with Apollo Link ("Middleware" Between Apollo Client and a GraphQL Server)

To integrate Apollo Client into an application using another JavaScript library/framework besides React, which Apollo Client already has built-in support for, there exists view integration libraries within the Apollo ecosystem that provide bindings for Vue, Svelte, Angular, Ember and Web Components.

The Vue Apollo library integrates Apollo Client into a Vue application. Different versions of Vue Apollo are compatible with different versions of Vue:

Although Vue Apollo v4 is still in active development (alpha phase), it offers support for Vue 3's Composition API, which collocates the methods corresponding to component options (watch, computed, etc.) and lifecycle hook registrations (onMounted, onUnmounted, etc.) within a single component option, setup. Using Vue Apollo v4 methods, such as useQuery and useMutation, the data requirements are also placed within the setup method. This approach makes it much easier to reason about a component's code compared to the Vue Apollo Options API approach, which places the data requirements within an apollo component option (independent of the other code placed within the remaining component options).

Below, I'm going to show you:

  • How to bootstrap a Vue CLI project.

  • How to integrate Apollo Client into a Vue 3 application using the Vue Apollo library.

  • The improved readability of Vue code using the Composition API.

  • A demo of Vue 3 and Apollo using the GitHub GraphQL API.

Using Vue 3's Composition API and the Vue Apollo (v4) library, we will be building the following GitHub search client:

GitHub GraphQL API Demo

Preparation#

To start, download this project from GitHub:

This repository is based on a custom Vue CLI project template that runs Vue 3 and includes support for TypeScript, ESLint and Prettier.

If you want to learn how to manually set up the base structure of this project, then proceed to the next section ("Installation"). Otherwise, you may skip the "Installation" section, download the already prepared project structure and proceed directly to the "GitHub GraphQL API" section.

Installation#

To generate a new Vue CLI project, run the following command in the terminal:

When prompted with "Please pick a preset," select the "Manually select features" option:

Vue CLI - Picking a Preset

This project will support TypeScript. Press "Space" to select "TypeScript."

Vue CLI - Picking a Package Manager

When prompted with "Choose a version of Vue.js that you want to start the project with," select the "3.x (Preview)" option:

Vue CLI - Version

Vue components will not be written with the class syntax. The Class API was officially dropped.

Vue CLI - Class-Style Component Syntax

This project will use Babel alongside TypeScript.

Vue CLI - Babel and TypeScript

For linting and code formatting, select the "ESLint + Prettier" option:

Vue CLI - ESLint and Prettier

Anytime changes to a file are saved, run the linter.

For this project, let's place the Babel, ESLint, etc. configurations within their own dedicated files to avoid increasing the size of package.json.

Vue CLI - Configuration Files

These answers are only for this project. In the future, you may want to try out different sets of project configurations to determine what specific tools make you more productive.

Vue CLI - Preserve Presets

To integrate type definitions from the schema of GitHub's GraphQL API, install @octokit/graphql-schema:

Several type definitions are assigned nullable types, which will cause ESLint to raise the following error within your IDE.

Inside of the .eslintrc.js file, turn off the rule @typescript-eslint/no-non-null-assertion.

(.eslintrc.js)

GitHub's GraphQL API#

In 2017, GitHub publicly released its GraphQL API. GitHub's GraphQL API exposes a public schema for interacting with GitHub itself, whether fetching commit data or starring a repository, all accessible from a single endpoint.

To send requests to GitHub's GraphQL API, generate an access token. This access token must be set to each request's Authorization header.

Creating a GitHub Personal Access Token

When setting permissions for the access token, enable repository privileges.

Setting Permissions

Create a .env file at the root of the project directory. Copy the 40 character-long access token to your clipboard. Set the environment variable VUE_APP_GITHUB_ACCESS_TOKEN to this access token.

(.env)

Note: Environment variables prefixed with VUE_APP_ can be accessed via process.env within Vue applications. Without this prefix, environment variables are undefined.

Copying and Pasting Access Token

Integrating tailwindcss and vue-apollo into Vue 3 Application#

First, install graphql, @apollo/client and @vue/apollo-composable as dependencies:

  • graphql - A JavaScript implementation of GraphQL that provides functionality such as lexing, parsing, validating and executing GraphQL requests.

  • @apollo/client - A GraphQL client that offers a declarative API for state management, caching and updating the UI on queries/mutations. The Apollo ecosystem offers view-layer integrations for front-end technologies such as React and Vue.

  • @vue/apollo-composable - A library that integrates Apollo Client into Vue applications with compatibility for the Composition API.

To rapidly style the UI interface, we will be using the Tailwind CSS framework. Install tailwindcss, postcss and autoprefixer as dependencies:

  • tailwindcss - An atomic CSS framework that provides single-purpose, utility classes over component-based classes. This package installs Tailwind as a PostCSS plugin.

  • postcss - A tool for processing and transforming CSS via plugins.

  • autoprefixer - postcss plugin to automatically prepend css rules with vendor prefix

Then, create a minimal Tailwind configuration file (tailwind.config.js) at the root of the project directory.

Note: The -p flag creates a minimal PostCSS configuration file (postcss.config.js) at the root of the project directory, alongside the generated tailwind.config.js.

Inside of tailwind.config.js, set the purge option to a list of filenames/globs for PurgeCSS to analyze and remove unused CSS.

(tailwind.config.js)

Inside of public/index.html, add these two CSS classes to the <body /> element:

(public/index.html)

When you run the application, you will encounter the following error:

PostCSS 8 Error

Although the latest version of the tailwindcss PostCSS plugin (v2) is compatible with latest version PostCSS (v8), other tools within the PostCSS ecosystem may not yet be compatible with this version of PostCSS. To resolve this error, uninstall tailwindcss, postcss and autoprefixer, and then reinstall these dependencies with the PostCSS (v7) compatibility build.

Inside of main.ts (the entry point of the Vue 3 application), create an ApolloClient instance.

Let's pass an object containing configuration options to the Apollo Client:

  • link - Apollo Client supports middleware between itself and a GraphQL server via links. On eachoutgoing GraphQL request, a link can customize the request, such as adding/editing its headers, or introduce a side-effect, such as logging. Composing multiple links forms a complex, sequential chain of network behavior.

  • cache - Apollo Client caches the results of GraphQL queries to speed up the execution of subsequent queries and save bandwidth on repeat queries. This will be set to a new instance of an InMemoryCache, which is automatically provided by the @apollo/client library.

For this application, we will define two links:

  1. The first link (ApolloLink) sets the Authorization header to a bearer token with the GitHub personal access token.

  2. The last link (HttpLink) sends the GraphQL request to a destination (in this case, the GitHub GraphQL API). This is often the last link defined.

To inject the Apollo Client into the application and allow child components to access the Apollo Client, call the provide method within the createApp's setup method to "provide" this client to the application and its children components. Since this application only interacts with a single GraphQL API, set this client as the default client.

Putting it altogether...

(main.ts)

Our application requires three child components:

  • <SearchBar /> - The search bar of the search client. When the user types a query into the search bar, a new list of repositories is fetched based on the current query.

  • <RepositoryList /> - The list of repositories retrieved from the GitHub GraphQL API.

  • <Repository /> - An individual repository of the repository list. It has a button in the top-right corner for starring/unstarring the repository. Additionally, it displays the following information:

    • Owner's Name

    • Author's Name

    • Description (Truncated to One Line)

    • Primary Language

    • License

    • Timestamp of the Last Update

    • Related Topics

    • Total Number of Stargazers

Search Client Layout

By default, the reactive searchOptions object, which represents the arguments passed to the GitHub GraphQL API's search query, is dynamically assigned to the prop search-options of the <RepositoryList /> component. Any changes to searchOptions, particularly to query, which corresponds to the value of the search bar's input, will cause the <RepositoryList /> component to retrieve a new list of repositories. The value of query is changed whenever the search event is emitted from the <SearchBar /> component, which occurs on changes to the value of its input.

(src/App.vue)

Typing a query emits a "search" event with the current query and triggers the search function in the <App /> component. This search function sets the value of the query field in the reactive searchOptions object.

Debounce the handleInputChange event handler to avoid sending the search event on every single input change.

(src/components/SearchBar.vue)

When an event is fired, this debounce function starts a timer and waits for a specific time period to elapse before calling its corresponding event handler. If another event is fired during this time period, then the previous event is ignored. The timer resets and must wait for the specific time period (now reset) to elapse before calling the new event's corresponding event handler. This debounce function invokes the event handler on the trailing edge of the timeout.

(src/utils.ts)

Store the queries and mutations within a single file. This application requires only one query and two mutations:

  • search - A query for retrieving a list of repositories based on the arguments passed to it.

  • addStar - A mutation for starring a repository.

  • removeStar - A mutation for unstarring a repository.

If you decide to add more queries/mutations that return a repository/repositories, and you request for the same repository fields for those queries/mutations, then use the repo fragment to keep your code DRY.

(src/graphql/documents.ts)

useQuery and useResult#

Inside of the <RepositoryList /> component, fetch a list of repositories based on the searchOptions passed from the <App /> parent component. To fetch this list of repositories, the component executes the composition function useQuery, which accepts a GraphQL document (SEARCH_REPOS) as the first argument and query arguments (searchOptions) as the second argument. This function is compatible with the setup function of Vue 3's Composition API.

useQuery returns an object that contains several Ref values:

  • result - An object that represents the data returned by Apollo from a GraphQL API.

  • loading - A Boolean value that indicates whether the query is still fetching data or not.

  • error - An error that is encountered while fetching the data. It contains a message that describes the cause of the error.

Sometimes, a query may return multiple top-level objects. To pick a single object from the result object returned by useQuery, use the useResult composition function. Instead of referencing the repositories from the result object as result.search.edges in the component's template, it can just be referenced as repositories. Plus, the default value assigned to repositories is the second argument passed to useResult (in this case, an empty array).

(src/components/RepositoryList.vue)

useMutation#

Inside of the <Repository /> component, there is a button for starring/unstarring a repository, depending on whether or not you have starred the repository previously. If you have not yet starred the repository, then clicking the button will star the repository, and vice-versa. The event handler calls either the unstarRepo or starRepo functions to unstar or star a repository respectively. Each of these functions execute the composition function useMutation, which accepts a GraphQL document (ADD_STAR or REMOVE_STAR) as the first argument and options (an object containing mutation arguments via the variables property, etc.) as the second argument. Similar to useQuery, this function is compatible with the setup function of Vue 3's Composition API.

When a repository is starred/unstarred, we must update the cache to reflect this mutation. To understand why this is important, let's walkthrough an example. Imagine you typed the query "facebook" into the search bar's input. This will fetch all repositories relevant to "facebook" from GitHub's GraphQL API. Suppose you have already starred the facebook/react repository, and you decide to unstar it. After you unstar it, you decide to type the query "google" into the search bar's input. This will fetch all repositories relevant to "google" from GitHub's GraphQL API. If you again type the query "facebook" into the search bar's input, then this will fetch all repositories relevant to "facebook" from the Apollo Client's cache. What was cached previously for this query was a list of repositories relevant to "facebook," including the facebook/react repository. However, this repository was cached when it was still starred. Therefore, we must modify this repository in the cache to reflect that it was recently unstarred.

To update the cache, set the update property in the options object to a function that provides an instance of the cache and the data returned from a mutation.

This function will call the overrideMutationStarCache, which will read the already cached data (via the cache's readQuery method) and write the result of the mutation to the appropriate repository entity (via the cache's writeQuery method). Don't forget to also increment/decrement the stargazers count!

Putting it altogether...

(src/components/Repository.vue)

Run the application locally:

Visit localhost:8080 in a browser to interact with the application.

Next Steps#

In this blog post, we only explored a very small subset of the GitHub GraphQL API. Try experimenting with other aspects of the public schema of the GitHub GraphQL API.

For a more difficult challenge, try connecting an existing Vue 3 application to a custom GraphQL server.

If you want to learn more about Vue 3, then check out Fullstack Vue:

Fullstack Vue

Sources#