Debouncing GraphQL Apollo in Vue


Scenario
You have an input that must make a real-time query to the database. This is often an autocomplete, but in my case it is checking to see if a subdomain is unique.
Input Component
I am using PrimeVue, so that is where the components are coming from.
<div class="flex flex-col gap-2">
<label for="subdomain">Organization Subdomain</label>
<InputText
id="subdomain"
v-model="subdomain"
placeholder="i.e. grace-church-bc"
:class="{ 'p-invalid': errors.subdomain }"
fluid
/>
<Message v-if="errors.subdomain" severity="error" variant="simple" size="small">{{
errors.subdomain
}}</Message>
<Message
v-if="subdomainMessage.message && !subdomainLoading"
:severity="subdomainMessage.severity"
variant="simple"
size="small"
>
{{ subdomainMessage.message }}
</Message>
</div>
Nice.
GraphQL File Structure
When I work with GraphQL, Apollo, and Vue Apollo , I find separation extremely important. GraphQL is flexible in its ability to fetch just what you need, but it requires a lot of boiler plate. I find the following setup very good for the frontend portion—
src
|_graphql
|_operations
|_index.ts // barrel file
|_subdomain.ts // one of many operation files
|_composables
|_index.ts // barrel file
|_useCheckSubdomainAvailability.ts // one of many composables
|_types
|_index.ts // generated by codegen
|_App.vue
...
Operations
import { gql } from "graphql-tag";
export const CHECK_SUBDOMAIN_AVAILABILITY = gql`
query CheckSubdomainAvailability($subdomain: String!) {
checkSubdomainAvailability(subdomain: $subdomain) {
available
subdomain
}
}
`;
This is a simple GraphQL client query that uses the graphql-tag
library to parse the GQL syntax. It takes a string and then calls our resolver checkSubdomainAvailability
with the string as its argument. We are asking that the resolver give back available
and subdomain
. We don’t really need subdomain
, but I don’t feel like changing the existing code at the moment.
Composables
import { useQuery } from "@vue/apollo-composable";
import { CHECK_SUBDOMAIN_AVAILABILITY } from "../operations/subdomain";
import { computed, type Ref } from "vue";
import { CheckSubdomainAvailabilityQuery } from "../types";
export function useCheckSubdomainAvailability(
subdomain: Ref<string | undefined, string | undefined>,
) {
const { result, loading, error, refetch } = useQuery<CheckSubdomainAvailabilityQuery>(
CHECK_SUBDOMAIN_AVAILABILITY,
computed(() => ({ subdomain: subdomain.value ?? "" })),
{
fetchPolicy: "no-cache",
debounce: 500,
},
);
const isAvailable = computed(() => result.value?.checkSubdomainAvailability?.available);
return {
loading,
error,
isAvailable,
refetch,
};
}
This is where the magic really happens.
Ref
and not a String
. This is vital as Vue Apollo’s useQuery()
composable will refetch automatically when a ref is updated!We then break out the usual variables from the useQuery
composable. Notice we add the generated type using a TypeScript Generic, which allows us to have type safety wherever we use this composable throughout the application.
Next, notice that we are able to pass an options object to the useQuery
composable. We pass fetchPolicy: "no-cache"
just in case another user creates the same subdomain within the cache duration. This keeps the data fresh. Secondly, we pass debounce: 500
. This means the composable will only make the query 500ms
after the data changes and also cancel any previous requests it had started.
The Impact
This is crucial as we would otherwise overload our server with requests on every keystroke.
But, with debounce, things work great.
I love that debounce is built into the Vue Apollo composable so we do not need to create a separate function and utilize a third party library like VueUse or Lodash to accomplish this task.
Subscribe to my newsletter
Read articles from Brian Scramlin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Brian Scramlin
Brian Scramlin
I am a full-stack developer from Battle Creek, MI. My focus is on enterprise TypeScript applications. I have worked with startups and legacy companies as a senior developer and currently serve the team at GradeCam.