Join the Newsletter!

Exclusive content & updates. No spam.

Skip to content

Type-Safe GraphQL Queries in Vue 3 with GraphQL Code Generator

Published: at 
GraphQL Codegen Workflow
GraphQL Codegen Workflow

Why plain TypeScript isn’t enough

If you hover over the result from useQuery in last week’s code, you’ll still see Ref<any>. That means:

<li v-for="c in result?.countries2" :key="c.code">

…slips right past TypeScript.

Any is bad
`any` sneaking past TypeScript

It’s time to bring in GraphQL Code Generator which gives us:

Step 1: Install the right packages

Let’s start by installing the necessary dependencies:

npm i graphql
npm i -D typescript @graphql-codegen/cli
npm i -D @parcel/watcher

🚨 @parcel/watcher is a dev dependency.

Step 2: Create a clean codegen.ts

Next, use the CLI to generate your config file:

npx graphql-code-generator init

When prompted, answer as follows:

? What type of application are you building? Application built with Vue
? Where is your schema?: (path or url) https://countries.trevorblades.com/graphql
? Where are your operations and fragments?: src/**/*.vue
? Where to write the output: src/gql/
? Do you want to generate an introspection file? No
? How to name the config file? codegen.ts
? What script in package.json should run the codegen? codegen
Fetching latest versions of selected plugins...

Your generated codegen.ts should look like this:


import type { import CodegenConfigCodegenConfig } from '@graphql-codegen/cli';

const const config: CodegenConfigconfig: import CodegenConfigCodegenConfig = {
  overwrite: booleanoverwrite: true,
  schema: stringschema: "https://countries.trevorblades.com/graphql",
  documents: stringdocuments: "src/**/*.vue",
  
generates: {
    "src/gql/": {
        preset: string;
        plugins: never[];
    };
}
generates
: {
"src/gql/": { preset: stringpreset: "client", plugins: never[]plugins: [] } } }; export default const config: CodegenConfigconfig;

Step 3: Add dev scripts and watch mode

Update your package.json scripts to streamline development:

{
  "scripts": {
    "codegen": "graphql-codegen --config codegen.ts",
    "codegen:watch": "graphql-codegen --watch --config codegen.ts"
  }
}

Step 4: Write your first typed query

Create a new file at src/queries/countries.graphql:

query AllCountries {
  countries {
    code
    name
    emoji
  }
}

Then, generate your types:

npm run codegen

The command writes all generated types to src/gql/.

Update your CountryList.vue component to use the generated types

<script setup lang="ts">
import { import useQueryuseQuery } from '@vue/apollo-composable'
import { import AllCountriesDocumentAllCountriesDocument } from '../gql/graphql'
import { 
const computed: {
    <T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ComputedRef<T>;
    <T, S = T>(options: WritableComputedOptions<T, S>, debugOptions?: DebuggerOptions): WritableComputedRef<T, S>;
}
computed
} from 'vue'
const { const result: anyresult, const loading: anyloading, const error: anyerror } = import useQueryuseQuery(import AllCountriesDocumentAllCountriesDocument) const const countries: ComputedRef<any>countries = computed<any>(getter: ComputedGetter<any>, debugOptions?: DebuggerOptions): ComputedRef<any> (+1 overload)
Takes a getter function and returns a readonly reactive ref object for the returned value from the getter. It can also take an object with get and set functions to create a writable ref object.
@example```js // Creating a readonly computed ref: const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // error ``` ```js // Creating a writable computed ref: const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: (val) => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0 ```@paramgetter - Function that produces the next value.@paramdebugOptions - For debugging. See {@link https://vuejs.org/guide/extras/reactivity-in-depth.html#computed-debugging}.@see{@link https://vuejs.org/api/reactivity-core.html#computed}
computed
(() => const result: anyresult.value?.countries)
</script>
Typed query
Goodbye `any`, hello types

Inline queries with the generated graphql tag

Alternatively, define the query directly in your component using the generated graphql tag:

<script setup lang="ts">
import { import useQueryuseQuery } from '@vue/apollo-composable'
import { import graphqlgraphql } from '../gql'
import { 
const computed: {
    <T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ComputedRef<T>;
    <T, S = T>(options: WritableComputedOptions<T, S>, debugOptions?: DebuggerOptions): WritableComputedRef<T, S>;
}
computed
} from 'vue'
const const COUNTRIES_QUERY: anyCOUNTRIES_QUERY = import graphqlgraphql(` query AllCountries { countries { code name emoji } } `) const { const result: anyresult, const loading: anyloading, const error: anyerror } = import useQueryuseQuery(const COUNTRIES_QUERY: anyCOUNTRIES_QUERY) const const countries: ComputedRef<any>countries = computed<any>(getter: ComputedGetter<any>, debugOptions?: DebuggerOptions): ComputedRef<any> (+1 overload)
Takes a getter function and returns a readonly reactive ref object for the returned value from the getter. It can also take an object with get and set functions to create a writable ref object.
@example```js // Creating a readonly computed ref: const count = ref(1) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // error ``` ```js // Creating a writable computed ref: const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: (val) => { count.value = val - 1 } }) plusOne.value = 1 console.log(count.value) // 0 ```@paramgetter - Function that produces the next value.@paramdebugOptions - For debugging. See {@link https://vuejs.org/guide/extras/reactivity-in-depth.html#computed-debugging}.@see{@link https://vuejs.org/api/reactivity-core.html#computed}
computed
(() => const result: anyresult.value?.countries)
</script>

Watch mode

With @parcel/watcher installed, you can enable watch mode for a smoother development experience. If you frequently change your GraphQL schema while developing, simply run:

npm run codegen:watch

GraphQL Code Generator immediately throws an error when your local operations drift from the live schema. Remember, your GraphQL server needs to be running for this to work.

Bonus: Proper validation out of the box

A powerful benefit of this setup is automatic validation. If the Countries GraphQL API ever changes—say, it renames code to code2—you’ll get an error when generating types.

For example, if you query for code2, you’ll see:

 Generate outputs
 Generate to src/gql/
 Load GraphQL schemas
 Load GraphQL documents
 GraphQL Document Validation failed with 1 errors;
      Error 0: Cannot query field "code2" on type "Country". Did you mean "code"?

Should you commit generated files?

A common question: should you commit the generated types to your repository?

StrategyProsCons
Commit themFast onboarding · Diff visibilityNoisy PRs · Merge conflicts
Ignore themClean history · Zero conflictsExtra npm run generate in CI/local

Many teams choose to commit generated files, but enforce npm run generate -- --check in CI to guard against stale artifacts.

Up next (Part 3)

Summary & Key Takeaways

In this part of the Vue 3 + GraphQL series, we:

What you learned

Actionable reminders

Stay tuned for Part 3, where we’ll cover fragments and avoid repetition in your queries!

Source Code

Find the full demo for this series here: example

Note:
The code for this tutorial is on the part-two branch.
After cloning the repository, make sure to check out the correct branch:

git clone https://github.com/alexanderop/vue-graphql-simple-example.git
cd vue-graphql-simple-example
git checkout part-two

View the branch directly on GitHub

Stay Updated!

Subscribe to my newsletter for more TypeScript, Vue, and web dev insights directly in your inbox.

  • Background information about the articles
  • Weekly Summary of all the interesting blog posts that I read
  • Small tips and trick
Subscribe Now

Related Posts