Abdelrahman Awad

Announcing Villus - A tiny and fast GraphQL Client for Vue.js

Published 11 Jan, 2020|

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 or POST with query in query params or the body respectively.

That means this is a valid GraphQL request:

fetch('/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:

yarn 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:

yarn 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:

import { 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.

<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:

<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:

const 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 👋

Subscribe for my latest content