Introduction

For over a year now, I’ve been working with GraphQL and a Backend-for-Frontend (BFF) at my job. Before this role, I had only worked with REST APIs and Axios, so it’s been a big learning curve. That’s why I want to share everything I’ve learned over the past months with you. I’ll start with a small introduction and continue adding more posts over time.
What is GraphQL and why should Vue developers care?
GraphQL is a query language for APIs.
You send a query describing the data you want, and the server gives you exactly that. Nothing more. Nothing less.
For Vue developers, this means:
- Less boilerplate — no stitching REST calls together
- Better typing — GraphQL schemas fit TypeScript perfectly
- Faster apps — fetch only what you need
GraphQL and the Vue 3 Composition API go together like coffee and morning sun.
Highly reactive. Highly type-safe. Way less code.
Try it yourself
Here is a GraphQL explorer you can use right now. Try this query:
query {
countries {
name
emoji
capital
}
}
💡 If the embed breaks, open it in a new tab.
Under the hood: GraphQL is just HTTP

GraphQL feels magical.
Underneath, it is just an HTTP POST request to a single endpoint like /graphql
.
Here is what a query looks like in code:
const COUNTRIES = gql`
query AllCountries {
countries {
code
name
emoji
}
}
`
Apollo transforms that into a regular POST request:
curl 'https://countries.trevorblades.com/graphql' \
-H 'content-type: application/json' \
--data-raw '{
"operationName": "AllCountries",
"variables": {},
"query": "query AllCountries { countries { code name emoji } }"
}'
Request parts:
operationName
: for debuggingvariables
: if your query needs inputsquery
: your actual GraphQL query
The server parses it, runs it, and spits back a JSON shaped exactly like you asked.
That is it. No special protocol. No magic. Just structured HTTP.
GraphQL as your BFF (Backend For Frontend)
One of GraphQL’s real superpowers: it makes an amazing Backend For Frontend layer.
When your frontend pulls from multiple services or APIs, GraphQL lets you:
- Merge everything into a single request
- Transform and normalize data easily
- Centralize error handling
- Create one clean source of truth
And thanks to caching:
- You make fewer requests
- You fetch smaller payloads
- You invalidate cache smartly based on types
Compared to fetch or axios juggling REST endpoints, GraphQL feels like you just switched from horse-drawn carriage to spaceship.
It gives you:
- Declarative fetching — describe the data, let GraphQL figure out the rest
- Type inference — strong IDE autocomplete, fewer runtime bugs
- Built-in caching — Apollo handles it for you
- Real-time updates — subscriptions for the win
- Better errors — clean structured error responses
Where Apollo fits in
Apollo Client is the most popular GraphQL client for a reason.
It gives you:
- Caching out of the box — like TanStack Query, but built for GraphQL
- Smart hooks —
useQuery
,useMutation
,useSubscription
- Fine control — decide when to refetch or serve from cache
- Real-time support — subscriptions with WebSockets made easy
If you know TanStack Query, the mapping is simple:
TanStack Query | Apollo Client |
---|---|
useQuery | useQuery |
useMutation | useMutation |
QueryClient cache | Apollo InMemoryCache |
Devtools | Apollo Devtools |
Main difference: Apollo speaks GraphQL natively. It understands operations, IDs, and types on a deeper level.
Now let us build something real.
1. Bootstrap a fresh Vue 3 project
npm create vite@latest vue3-graphql-setup -- --template vue-ts
cd vue3-graphql-setup
npm install
2. Install GraphQL and Apollo
npm install graphql graphql-tag @apollo/client @vue/apollo-composable
3. Create an Apollo plugin
Create src/plugins/apollo.ts
:
import { import DefaultApolloClient
DefaultApolloClient } from "@vue/apollo-composable";
import type { interface App<HostElement = any>
App } from "vue";
import { import ApolloClient
ApolloClient, import createHttpLink
createHttpLink, import InMemoryCache
InMemoryCache } from '@apollo/client/core'
const const httpLink: any
httpLink = import createHttpLink
createHttpLink({
uri: string
uri: 'https://countries.trevorblades.com/graphql'
})
const const cache: any
cache = new import InMemoryCache
InMemoryCache()
const const apolloClient: any
apolloClient = new import ApolloClient
ApolloClient({
link: any
link: const httpLink: any
httpLink,
cache: any
cache,
})
export const const apolloPlugin: {
install(app: App): void;
}
apolloPlugin = {
function install(app: App): void
install(app: App<any>
app: interface App<HostElement = any>
App) {
app: App<any>
app.App<any>.provide<any, any>(key: any, value: any): App<any>
provide(import DefaultApolloClient
DefaultApolloClient, const apolloClient: any
apolloClient);
},
};
This wraps Apollo cleanly inside a Vue plugin and provides it across the app.
4. Install the plugin
Edit src/main.ts
:
import { const createApp: CreateAppFunction<Element>
createApp } from 'vue'
import import App
App from './App.vue'
import { import apolloPlugin
apolloPlugin } from './plugins/apollo'
import './style.css'
const const app: App<Element>
app = function createApp(rootComponent: Component, rootProps?: Data | null): App<Element>
createApp(import App
App)
const app: App<Element>
app.App<Element>.use<[]>(plugin: Plugin<[]>): App<Element> (+1 overload)
use(import apolloPlugin
apolloPlugin)
const app: App<Element>
app.App<Element>.mount(rootContainer: string | Element, isHydrate?: boolean, namespace?: boolean | ElementNamespace, vnode?: VNode): ComponentPublicInstance
mount('#app')
Done. Apollo is now everywhere in your app.
5. Your first GraphQL query
Create src/components/CountryList.vue
:
<script setup lang="ts">
import { import useQuery
useQuery } from '@vue/apollo-composable'
import import gql
gql from 'graphql-tag'
const const COUNTRIES: any
COUNTRIES = import gql
gql`
query AllCountries {
countries {
code
name
emoji
}
}
`
const { const result: any
result, const loading: any
loading, const error: any
error } = import useQuery
useQuery(const COUNTRIES: any
COUNTRIES)
</script>
<template>
<section: HTMLAttributes & ReservedProps
section>
<h1: HTMLAttributes & ReservedProps
h1 HTMLAttributes.class?: any
class="text-2xl font-bold mb-4">🌎 Countries (GraphQL)</h1: HTMLAttributes & ReservedProps
h1>
<p: HTMLAttributes & ReservedProps
p v-if="const loading: any
loading">Loading…</p: HTMLAttributes & ReservedProps
p>
<p: HTMLAttributes & ReservedProps
p v-else-if="const error: any
error" HTMLAttributes.class?: any
class="text-red-600">{{ const error: any
error.message }}</p: HTMLAttributes & ReservedProps
p>
<ul: HTMLAttributes & ReservedProps
ul v-else HTMLAttributes.class?: any
class="grid gap-1">
<li: LiHTMLAttributes & ReservedProps
li v-for="const c: any
c in const result: any
result?.countries" key?: PropertyKey | undefined
:key="const c: any
c.code">
{{ const c: any
c.emoji }} {{ const c: any
c.name }}
</li: LiHTMLAttributes & ReservedProps
li>
</ul: HTMLAttributes & ReservedProps
ul>
</section: HTMLAttributes & ReservedProps
section>
</template>
Drop it into App.vue
:
<template>
<import CountryList
CountryList />
</template>
<script setup lang="ts">
import import CountryList
CountryList from './components/CountryList.vue'
</script>
Fire up your dev server:
npm run dev
You should see a live list of countries.
No REST call nightmares. No complex wiring.
6. Bonus: add stronger types (optional)
Apollo already types generically.
If you want perfect types per query, you can add GraphQL Code Generator.
I will show you how in the next post. For now, enjoy basic type-safety.
7. Recap and what is next
✅ Set up Vue 3 and Vite
✅ Installed Apollo Client and connected it
✅ Ran first GraphQL query and rendered data
✅ Learned about proper GraphQL package imports
👉 Coming next: Type-Safe Queries in Vue 3 with graphql-codegen
We will generate typed useQuery
composables and retire manual interfaces for good.
Source Code
Find the full demo here: example