openapi-vue-query
v0.0.5
Published
Fast, type-safe @tanstack/vue-query client to work with your OpenAPI schema.
Maintainers
Readme
openapi-vue-query
A type-safe and lightweight (1kb) wrapper around @tanstack/vue-query for consuming OpenAPI schemas in Vue.js applications.
Built on top of openapi-fetch and openapi-typescript, providing:
- ✅ 100% type-safe - No typos in URLs, params, request bodies, or responses
- ✅ Zero runtime overhead - Types are generated at build time
- ✅ Automatic type inference - No manual typing of your API
- ✅ Eliminates
anytypes that hide bugs - ✅ Eliminates
astype overrides that can hide bugs - ✅ Vue 3 Composition API support with full reactivity
- ✅ All @tanstack/vue-query features - Caching, background updates, optimistic updates, etc.
🚀 Quick Start
Installation
npm i openapi-vue-query openapi-fetch
npm i -D openapi-typescript typescriptGenerate Types
Generate TypeScript types from your OpenAPI schema:
npx openapi-typescript ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.tsBasic Usage
<script setup lang="ts">
import createFetchClient from "openapi-fetch";
import createClient from "openapi-vue-query";
import type { paths } from "./lib/api/v1"; // generated by openapi-typescript
// Create clients
const fetchClient = createFetchClient<paths>({
baseUrl: "https://api.example.com/v1",
});
const $api = createClient(fetchClient);
// Use in your components
const { data, error, isPending } = $api.useQuery(
"get",
"/blogposts/{post_id}",
{
params: {
path: { post_id: 5 },
},
},
);
</script>
<template>
<div>
<template v-if="isPending"> Loading... </template>
<template v-else-if="error"> Error: {{ error.message }} </template>
<template v-else>
<h1>{{ data?.title }}</h1>
<p>{{ data?.content }}</p>
</template>
</div>
</template>🔧 API Reference
useQuery
For fetching data. Wraps @tanstack/vue-query's useQuery.
<script setup lang="ts">
const { data, error, isPending, refetch } = $api.useQuery(
"get",
"/users/{id}",
{
params: { path: { id: "123" } },
},
{
// Standard vue-query options
enabled: computed(() => !!userId.value),
staleTime: 5 * 60 * 1000, // 5 minutes
},
);
</script>useMutation
For creating, updating, or deleting data. Wraps @tanstack/vue-query's useMutation.
<script setup lang="ts">
const createPost = $api.useMutation("post", "/posts", {
onSuccess: () => {
// Invalidate and refetch posts
queryClient.invalidateQueries({ queryKey: ["get", "/posts"] });
},
});
const handleSubmit = () => {
createPost.mutate({
body: {
title: "New Post",
content: "Post content...",
},
});
};
</script>
<template>
<button @click="handleSubmit" :disabled="createPost.isPending">
{{ createPost.isPending ? "Creating..." : "Create Post" }}
</button>
</template>useInfiniteQuery
For paginated data. Wraps @tanstack/vue-query's useInfiniteQuery.
<script setup lang="ts">
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
$api.useInfiniteQuery(
"get",
"/posts",
{
params: {
query: { limit: 10 },
},
},
{
getNextPageParam: (lastPage, allPages) => {
return lastPage.hasMore ? allPages.length : undefined;
},
pageParamName: "page", // Query parameter name for pagination
},
);
</script>
<template>
<div>
<div v-for="page in data?.pages" :key="page.page">
<article v-for="post in page.posts" :key="post.id">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</article>
</div>
<button
v-if="hasNextPage"
@click="fetchNextPage"
:disabled="isFetchingNextPage"
>
{{ isFetchingNextPage ? "Loading..." : "Load More" }}
</button>
</div>
</template>queryOptions
For creating reusable query configurations.
// composables/usePostQuery.ts
export function usePostQuery(postId: Ref<string>) {
return $api.queryOptions("get", "/posts/{id}", {
params: { path: { id: postId } },
});
}
// In component
const postQuery = usePostQuery(postId);
const { data } = useQuery(postQuery);🎯 Advanced Usage
With Composables
Create reusable API composables:
// composables/useAuth.ts
export function useAuth() {
const login = $api.useMutation("post", "/auth/login");
const logout = $api.useMutation("post", "/auth/logout");
const { data: user } = $api.useQuery("get", "/auth/me", undefined, {
enabled: computed(() => !!getToken()),
});
return {
user: readonly(user),
login: login.mutate,
logout: logout.mutate,
isLoading: computed(() => login.isPending || logout.isPending),
};
}Error Handling
<script setup lang="ts">
const { data, error } = $api.useQuery("get", "/posts/{id}", {
params: { path: { id: "123" } },
});
// Error is fully typed based on your OpenAPI schema
watchEffect(() => {
if (error.value) {
if (error.value.status === 404) {
console.log("Post not found");
} else if (error.value.status === 401) {
console.log("Unauthorized");
}
}
});
</script>Custom Fetch Client Configuration
import createFetchClient from "openapi-fetch";
import createClient from "openapi-vue-query";
const fetchClient = createFetchClient<paths>({
baseUrl: "https://api.example.com/v1",
headers: {
"User-Agent": "MyApp/1.0",
},
});
// Add auth middleware
fetchClient.use({
onRequest({ request }) {
const token = getToken();
if (token) {
request.headers.set("Authorization", `Bearer ${token}`);
}
},
onResponse({ response }) {
if (response.status === 401) {
// Handle unauthorized
logout();
}
},
});
export const $api = createClient(fetchClient);🤝 Integration with Vue Query
openapi-vue-query is fully compatible with @tanstack/vue-query. You can use all vue-query features:
<script setup lang="ts">
import { useQueryClient } from "@tanstack/vue-query";
const queryClient = useQueryClient();
// Prefetch data
onMounted(() => {
queryClient.prefetchQuery($api.queryOptions("get", "/posts"));
});
// Optimistic updates
const updatePost = $api.useMutation("put", "/posts/{id}", {
onMutate: async (variables) => {
await queryClient.cancelQueries({
queryKey: ["get", "/posts", variables.params.path.id],
});
const previousPost = queryClient.getQueryData([
"get",
"/posts",
variables.params.path.id,
]);
queryClient.setQueryData(["get", "/posts", variables.params.path.id], {
...previousPost,
...variables.body,
});
return { previousPost };
},
onError: (error, variables, context) => {
if (context?.previousPost) {
queryClient.setQueryData(
["get", "/posts", variables.params.path.id],
context.previousPost,
);
}
},
});
</script>📚 Examples
🔗 Related Projects
- openapi-fetch - The underlying fetch client
- openapi-typescript - TypeScript code generator
- @tanstack/vue-query - The query library being wrapped
📖 Documentation
For detailed documentation and examples, visit: https://openapi-ts-vue.dev/openapi-vue-query/
🐛 Issues & Feedback
Found a bug or have a feature request? Please open an issue on GitHub.
📄 License
MIT License. See LICENSE for details.
