Kweri supports two Vue usage patterns. If your backend has an OpenAPI spec, use path-based hooks with generated types. If you’re defining endpoints manually, use useQuery and useMutation directly.
With code generation (recommended)
After running kweri-gen, bind composables to the generated EndpointByMethod map. You get fully-typed useGet, usePost, usePut, usePatch, and useDelete composables without writing any endpoint definitions yourself.
Setup
// src/composables/useKweri.ts
import { ref, watch, onUnmounted } from 'vue'
import { createVuePathHooks } from 'kweri'
import { EndpointByMethod } from 'kweri/generated'
import { kweri } from '@/lib/kweri'
export const { useGet, usePost, usePut, usePatch, useDelete } =
createVuePathHooks({ ref, watch, onUnmounted }, kweri, EndpointByMethod)
export { kweri }
Create the composables once and export them. Don’t call createVuePathHooks inside a component.
useGet
function useGet(
path: string,
params?: any,
options?: VueQueryOptions
): VueQueryResult
<script setup lang="ts">
import { useGet } from '@/composables/useKweri'
const { data, isLoading, isError, error } = useGet('/users')
</script>
<template>
<p v-if="isLoading.value">Loading...</p>
<p v-else-if="isError.value">Error: {{ error.value?.message }}</p>
<ul v-else>
<li v-for="user in data.value" :key="user.id">{{ user.name }}</li>
</ul>
</template>
Mutation hooks
function usePost(path: string): VueMutationResult
function usePut(path: string): VueMutationResult
function usePatch(path: string): VueMutationResult
function useDelete(path: string): VueMutationResult
Full CRUD example
<script setup lang="ts">
import { useGet, usePost, useDelete, kweri } from '@/composables/useKweri'
const usersQuery = useGet('/users')
const createMutation = usePost('/users')
const deleteMutation = useDelete('/users/{id}')
async function createUser(name: string) {
await createMutation.mutateAsync({ body: { name } })
kweri.invalidateByPath('/users')
}
async function deleteUser(id: number) {
// Optimistic remove
const prev = usersQuery.data.value ?? []
usersQuery.data.value = prev.filter(u => u.id !== id)
try {
await deleteMutation.mutateAsync({ path: { id: String(id) } })
kweri.invalidateByPath('/users')
} catch {
usersQuery.data.value = prev // rollback
}
}
</script>
Path hooks resolve the endpoint schema at call time. If the path isn’t in the generated map, they throw [kweri] No endpoint registered for GET /unknown-path.
Without code generation
Use useQuery and useMutation when you’re defining endpoints manually with defineEndpoint.
Setup
// src/composables/useKweri.ts
import { ref, watch, onUnmounted } from 'vue'
import { createVueQueryHooks } from 'kweri'
import { kweri } from '@/lib/kweri'
export const { useQuery, useMutation } =
createVueQueryHooks({ ref, watch, onUnmounted }, kweri)
export { kweri }
Create the composables once and export them. Don’t call createVueQueryHooks inside a component.
useQuery
function useQuery<E extends Endpoint>(
kweri: Kweri,
endpoint: E,
params: InferParams<E> | Ref<InferParams<E>>,
options?: VueQueryOptions
): VueQueryResult<InferResponse<E>, Error>
VueQueryOptions
interface VueQueryOptions {
enabled?: boolean | Ref<boolean> // default: true
}
VueQueryResult
interface VueQueryResult<TData, TError> {
data: Ref<TData | undefined>
status: Ref<'idle' | 'loading' | 'success' | 'error'>
error: Ref<TError | undefined>
refetch: () => Promise<void>
isLoading: Ref<boolean>
isSuccess: Ref<boolean>
isError: Ref<boolean>
}
Example
<script setup lang="ts">
import { useQuery } from '@/composables/useKweri'
import { getUsers } from '@/api/users'
const { data, isLoading, isError, error, refetch } = useQuery(getUsers, {})
</script>
<template>
<p v-if="isLoading.value">Loading...</p>
<p v-else-if="isError.value">
Error: {{ error.value?.message }}
<button @click="refetch()">Retry</button>
</p>
<ul v-else>
<li v-for="user in data.value" :key="user.id">{{ user.name }}</li>
</ul>
</template>
Reactive params
Pass a Ref as params and the query re-executes automatically when the ref changes:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useQuery, kweri } from '@/composables/useKweri'
import { getUserById } from '@/api/users'
const userId = ref(1)
const { data } = useQuery(getUserById, computed(() => ({ path: { id: userId.value } })))
function viewUser(id: number) {
userId.value = id // query re-fires automatically
}
</script>
Conditional fetching
<script setup lang="ts">
import { ref } from 'vue'
import { useQuery, kweri } from '@/composables/useKweri'
import { getProfile } from '@/api/profile'
const isLoggedIn = ref(false)
const { data } = useQuery(getProfile, {}, { enabled: isLoggedIn })
</script>
useMutation
function useMutation<E extends Endpoint>(
kweri: Kweri,
endpoint: E
): VueMutationResult<InferResponse<E>, Error>
VueMutationResult
interface VueMutationResult<TData, TError> {
mutate: (vars?: unknown) => void
mutateAsync: (vars?: unknown) => Promise<TData>
status: Ref<'idle' | 'loading' | 'success' | 'error'>
error: Ref<TError | undefined>
reset: () => void
isLoading: Ref<boolean>
isSuccess: Ref<boolean>
isError: Ref<boolean>
}
Example
<script setup lang="ts">
import { ref } from 'vue'
import { useMutation, kweri } from '@/composables/useKweri'
import { createUser } from '@/api/users'
const name = ref('')
const mutation = useMutation(createUser)
async function handleSubmit() {
try {
await mutation.mutateAsync({ body: { name: name.value } })
kweri.invalidateByPath('/users')
} catch {}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="name" />
<button type="submit" :disabled="mutation.isLoading.value">
{{ mutation.isLoading.value ? 'Saving...' : 'Create User' }}
</button>
<p v-if="mutation.isError.value" @click="mutation.reset()">
{{ mutation.error.value?.message }}
</p>
</form>
</template>
Cleanup
useQuery automatically unsubscribes from the kweri cache when the component is unmounted (via onUnmounted). No manual cleanup needed.