Announcing Villus - A tiny and fast GraphQL Client for Vue.js
GraphQL at its simplest level is just an HTTP request with predictable requests and responses, you donât really need apollo to make GraphQL requests and more often than not all the bells that comes packed into it gets into your way, at least for me. So villus
is a simpler GraphQL client that aims to keep everything simple and tiny while having most of the needed features for an advanced client.
You donât need all of Apollo
The apollo libraries have done a great job bringing GraphQL to a vast audience and offered them a lot of the tools to work with it. I have no problem with using apollo in any application today, however, I think most of its features donât translate well into our Vue.js applications.
The first time I used GraphQL, I wrestled with it to make use of our existing Vuex stores, since the apollo client came with its own internal store it meant that we will use the client as a simple GraphQL fetcher which is an overkill. We wanted to have our own custom logic for persisting our queries, not being able to use Vuex was a deal-breaker for us.
So we settled for using apollo links without the client library and it worked great for us, except it weighted a few kilobytes more than what we justified as a âGraphQL Fetcherâ, it was a little bit big than what we were using it for.
In my most recent apps I used raw fetch
to do my GraphQL queries and it worked great for us, it was simple, flexible and we could introduce whatever layer of persistence without too much hassle. Of course, it meant we would be implementing some of the features we needed like querying de-duplication, batching and fetching policies.
apollo does offer a bunch of features that Iâve never used like optimistic responses (although I always implemented my custom logic), vue-apollo
has a few more features like pagination, querying skipping and quite a few of neat stuff that I never used.
For me stuff like pagination is always different in each project, sometimes itâs a simple slicing with skip and limit, sometimes it is a bit more complicated with cursors. Providing an abstraction for that is very tricky and there is always the risk to be very limited.
Conditionally applying queries has always been a part of the business logic, there is not a lot of value to provide an abstraction for that either. On top of that, I canât count the times I couldnât debug a GraphQL response because apollo has its own errors that invalidate the response even though you can see it successful in the network tab.
I might come as ignorant by now, but for me apollo has always been an overkill. Maybe I never built a large-enough application to warrant it, but it means there is a need here that could be fulfilled.
I just want to make HTTP requests
I felt there was this barrier at first when I was learning GraphQL and it occurred to me that:
Why do I have to do all of that to execute a simple HTTP request?
Yes, it would take about 10 minutes to get a simple hello world example going, but come on! I could run a simple graphql query with the fetch
API in under 2 minutes. Why is it so hard to do what boils down to a simple get
or post
request?
I remember having to read the source code for the apollo-http-link
because their documentation contains no useful information about using the apollo stuff manually, the documentation was only covering the react library and each frameworkâs implementation had their own docs with their own caveats, although I think vue-apollo is doing a great job reducing the entry overhead.
What makes a GraphQL request
Iâve met a few people who treat GraphQL as a magical thing thatâs not meant to be dissected and understood.
Here is a few stuff that you need to know:
- GraphQL is transport-independent, it doesnât matter if you are using HTTP or whatever to make the request.
- To make a simple HTTP GraphQL request, send a request with
GET
orPOST
withquery
in query params or the body respectively.
That means this is a valid GraphQL request:
jsfetch('/graphql', {
method: 'post',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
query: `{
posts {
id
title
}
}`,
}),
});
Thatâs about 10 lines of code, and while you donât get all the fully-featured apollo bells and whistles, you could iterate over that and build a minimal GraphQL client that does exactly what you need and nothing more.
Now it would be great if we had a similar client with the same goals in mind, right?
Enter Villus
Villus is a GraphQL client that I have prototyped the last year, I never thought to introduce it as a library because it wasnât really doing a lot. However, when I stumbled upon urql which is a similar library for React, I got inspired for the level of abstraction I was going for.
In other words, I knew what to implement and what not to.
Back then at Baianat, we released vue-gql
which was a library implementation for the concept, however since we parted ways I had other plans in mind and decided to fork it recently into what is now called villus
.
The goals of this project are:
- Having a very small footprint.
- Giving you useful abstractions for queries, mutations, and subscriptions.
- Multiple ways to use it, you can use the composition functions API, higher-order components or the raw client and still get all the features for all of them.
What is interesting here is that villus
is Vue 3 ready, at the time of this writing Vue 3 is sitting at its first alpha release. villus
uses the composition API in its core, it is also tree-shakeable and the entire thing is sitting at just 3kb.
Villus is available for both Vue 3, you can install it with:
shyarn add villus@next graphql
# or
npm install villus@next graphql
It is also available for Vue 2 with the @vue/composition-api
extension with limited features:
shyarn add villus @vue/composition-api graphql
# or
npm install villus @vue/composition-api graphql
Querying
First you need to configure your GraphQL endpoint, villus does this by using Vueâs Provider/Inject
API, you can do that with the useClient
function:
jsimport { useClient } from 'villus';
// Your App component
export default {
setup() {
useClient({
url: '/graphql', // your endpoint.
});
},
};
Under the hood, villus
is using provide
function to allow child components to utilize the configuration seamlessly. It also means you could have multiple endpoints by using different useClient
calls.
And that is all you need to configure the client for your entire application.
To query, villus
gives you access to a useQuery
function and a Query
component, I will be showcasing the former.
vue<template>
<div>
<ul v-if="data">
<li v-for="todo in data.todos">{{ todo.text }}</li>
</ul>
</div>
</template>
<script>
import { useQuery } from 'villus';
export default {
setup() {
const { data } = useQuery({
query: '{ todos { text } }',
});
return { data };
},
};
</script>
And thatâs it, the same thing goes for mutations and subscriptions. How quick was that?
But wait, there is more
You can have what I call Reactive Queries and villus
will handle them automatically for you:
vue<template>
<div>
<div v-if="data">
<h1>{{ data.post.title }}</h1>
</div>
<button @click="id = 13"></button>
</div>
</template>
<script>
import { ref, computed } from 'vue';
import { useQuery } from 'villus';
export default {
setup() {
const id = ref(12);
const query = computed(() => {
return `{ post (id: ${id.value}) { id title } }`;
});
const { data } = useQuery({
query,
});
return { data, id };
},
};
</script>
Variables can also be reactive or static depending on your needs:
jsconst variables = ref({ id: 1 });
const query = `query getPost ($id: Int!) { post (id: $id) { title } }`;
const { data } = useQuery({
query,
variables,
});
If you change your variables
, the query will be automatically re-fetched and updated.
villus
can do a lot more, for example, it allows you to batch and cache queries. See the documentation to see what you can do with it.
You can find an example for villus
here.
Suspense
The Suspense
component is an exciting Vue 3.0 upcoming feature, it allows you to render a component asynchronously with the ability to render a placeholder until the component is done doing its thing. If you would like to learn more about the Suspend
component I suggest you read Filip Rakowskiâs excellent article on it.
villus already supports Suspense
usage with the same API as earlier, instead of calling useQuery
You can use useQuery.suspend
to take advantage of that behavior. The difference between the two is that useQuery
will execute the query on mounted, but useQuery.suspend
variant is asynchronous and will execute the query immediately. They both have the exact same API to allow you to switch freely between them as you see fit.
Here is an example project with Suspense
.
What villus is not
villus
is not a vue-apollo
replacement or anything like that, on the contrary it aims to keep a much lower profile and much smaller API surface, becaus villus
is aimed to be an efficient and fast GraphQL client. The tradeoff being it has much smaller scope, so it wonât implement a lot of fancy stuff. Only the most important features.
If villus
doesnât sound like the right tool for you, or that it lacks some deal-breaker features that you need, then use vue-apollo
.
Roadmap
I have not yet created a roadmap but here is a few items in my bucket list:
- Error Handling and provide an abstraction if necessary.
- Better docs with live examples.
- Typing the returned responses.
- Extendable API for allowing users to persist queries into indexedDB or other more permanent storage.
Try it and give feedback
Let me know if you have any more improvements, suggestions you can add or if you encounter any bugs.
Thanks for reading đ