← Back to Blog

FastAPI + Vue: TanStack Vue Query with a Python backend

·9 min read

The Vue adapter works differently from React in one key way: instead of a <api.Provider> component, you register pyrpc.plugin on your Vue app. Everything else (typed queries, mutations, reactive args) follows Vue 3 Composition API conventions.

Project layout

fastapi-vue/
  server/
    main.py             ← FastAPI app (same as React example)
    pyrpc.json
  client/
    src/
      pyrpc.ts          ← createPyrpcVue setup
      main.ts           ← createApp().use(pyrpc.plugin)
      App.vue           ← createQuery / createMutation calls

Client setup

// src/pyrpc.ts
import { createPyrpcVue } from "@pyrpc/vue"
import type { Types } from "@pyrpc/types"

export const pyrpc = createPyrpcVue<Types>({
  baseUrl: import.meta.env.VITE_API_URL ?? "http://localhost:8000",
})
// src/main.ts
import { createApp } from "vue"
import App from "./App.vue"
import { pyrpc } from "./pyrpc"

createApp(App).use(pyrpc.plugin).mount("#app")

Using the composables

<!-- src/App.vue -->
<script setup lang="ts">
import { ref } from "vue"
import { pyrpc } from "./pyrpc"

const name = ref("")

// @rpc.query → createQuery
const { data: greeting, isPending } = pyrpc.read_root.createQuery()

// pass reactive args as a getter so they re-fetch on change
const { data: item } = pyrpc.read_item.createQuery(
  () => ({ item_id: 42, q: "test" })
)

// @rpc.mutation → createMutation
const createItem = pyrpc.create_item.createMutation()

const handleCreate = () => {
  if (name.value.trim()) {
    createItem.mutate({ name: name.value, description: `Item: ${name.value}` })
    name.value = ""
  }
}
</script>

<template>
  <div>
    <p v-if="isPending">Loading…</p>
    <pre v-else>{{ JSON.stringify(greeting) }}</pre>

    <pre>{{ JSON.stringify(item) }}</pre>

    <input v-model="name" placeholder="Item name" />
    <button @click="handleCreate" :disabled="createItem.isPending.value">
      {{ createItem.isPending.value ? "Creating…" : "Create" }}
    </button>
    <pre v-if="createItem.isSuccess.value">
      {{ JSON.stringify(createItem.data.value) }}
    </pre>
  </div>
</template>

Key differences from React

Plugin vs Provider. Vue uses app.use(pyrpc.plugin) instead of a JSX Provider component. The plugin registers TanStack Vue Query under the hood.

Reactive values. Reactive data comes back as Ref values. Access them with .value in <script>, directly in templates.

Reactive args. Pass query args as a getter function (() => ({ ... })) so TanStack Vue Query tracks reactive dependencies and re-fetches automatically.

Run it

# Terminal 1
cd server && uv add pyrpc-core[fastapi] && pyrpc dev

# Terminal 2
cd client && npm install && npm run dev

Open http://localhost:5173.