In this VueJS GraphQL tutorial, we will be building a polls app, the idea is pretty simple, the user will have the ability to create a poll. Then, other users can vote for one of the options and see real-time results.
We will be using a boilerplate that I have created in an earlier tutorial. However, if you’re comfortable using other boilerplates, following this tutorial and implementing the code will be easy.
Our idea is very simple. We will basically have:
- Polls created by users which contain many options.
- Options used inside a poll and each user can vote for the option he likes.
- Users can create polls.
- Any user can vote for an option in a poll.
- Users can see real-time voting results.
The code for this tutorial is available on GitHub.
We will start by setting up our project step by step using the boilerplate.
First of all, let’s clone the boilerplate from Github:
Then, we will cd inside the cloned folder and copy the .env.example file into a new file called .env and change the MongoDB database name:
This will allow our server and client to recognize our configuration.
Next, we need to install the required dependencies, we can do that by just running:
As a result, the dependencies will be installed and thus, we can start our server:
$> yarn server:dev
Similarly, we can start the client by running in another terminal:
$> yarn web:dev
If we go to
localhost:4000 you should see the GraphQL playground:
And if we go to
localhost:8080 you should see the example Vue app:
Understanding the structure
We have two main folders:
- src: contains the Vue app with vue-apollo configured. It’s generated using Vue CLI v3.
- server: contains the apollo-server, GraphQL types, and services folder.
For the backend, we have our entry file, called index.js which does these things in order:
- Loads and merges all the files ending with .graphql from the schema folder
- Loads all the resolvers from the resolvers folder by importing
- Connects to MongoDB
- Launches Apollo-server on port 4000
The main business logic will live inside the services folder, and we will use all the methods we create there inside our resolvers, more on this later.
So without further ado, let’s start building!
Building the Backend with Node
The first thing we will do is to create the backend of our project. This will provide GraphQL queries, mutations, and subscriptions for the consumption by our frontend later.
Our GraphQL types and Mongoose models
We can start by creating the Mongoose model. The poll model will be like this:
Notice that first, we didn’t create an options model, since we can just embed options inside the poll model directly.
Furthermore, we have used sub-documents, this is because we want each option to have an id, so that later when we want to vote for it we can identify it directly in our options array:
Alright, we got our model set up, now we will create the GraphQL schemas.
Starting with the Option type, let’s create
Pretty simple schema, using the predefined GraphQL types. Likewise, we do the same for the Poll type:
As you can see, we have specified that our options attribute should be a list of the Option type, also using exclamation points we are forcing the existence of the options list and the content inside it, in other words, neither the list nor any of its content shall be null.
We don’t need to do anything else, our current project setup will pick up the new types. If we navigate to the playground we can see our new types on the right:
Next, we will create our first mutation, which is the
createPoll mutation. Using it will allow us to insert new polls in the database.
First, we will create the mutation inside the Mutation type in the
In addition, we will create the resolver for the mutation that will call a service which will handle inserting the data.
Let’s go ahead and create a
poll.resovlers.js file that will be responsible for resolving (handling) all queries, mutations, and subscriptions related to the poll type:
Moreover, we have to create the poll service, which will be responsible for the business logic implementation. So inside the
server/services let’s create
Finally, we should spread our resolver’s mutation, queries, and subscriptions inside the main resolvers file which is
If we go ahead and try the mutation in the playground we should see our poll created:
Next, we will create our first query, which is the polls query. By using it, we will get a list of all the polls we have in the database.
First, we will create the query inside the Query type in the
Further, we will add the query resolver to our resolvers file:
And finally, we will implement the
findAll() service method:
If we go ahead and run this query in the playground we should see an array of polls:
Voting on polls
In this section, we will implement the voting functionality. Each user can vote on the option he likes for a certain poll. In order to implement this, we will create a mutation called
voteOnPoll that will accept an option id:
We made the mutation simply return the poll being updated.
Now we will do the usual process. First, implement the resolver:
Then, we will implement the service method:
We have used
findOneAndUpdate which will look for the document that contains an option with the provided id. Then, it will increment that specific option votes with one vote. Finally, it will return the whole document in it’s latest state. Let’s give it a shot:
You have to change the
optionId in the variables with an id that you already have, use the polls query to get it.
Real-time results using GraphQL subscriptions
In our idea description, we specified that we want to see real-time voting. Furthermore, we want to see newly created polls without refreshing the page.
To implement this kind of business logic, we have to use the apollo-server subscriptions. Where the client-side will establish a WebSocket connection with the server to be updated with new data without sending any requests.
We can define subscriptions inside the
Subscription.graphql file, a subscription is very similar to a mutation, it can accept arguments and it must specify a return type. In our case, we will start by defining the
Now inside our resolvers, we have to register this subscription using a new
PubSub instance to generate the event we need. Since the subscription will just send the data to the client, we need a way to publish it from anywhere:
If you have noticed, we have used a
POLL_CREATED constant, this is because we have used and will continue to use the
pollCreated string in multiple places, so it makes sense to save it in an
Finally, we will publish data to the
pollCreated subscription when we create a new poll inside the
createPoll resolver method:
To test this we will head over to the playground:
Now we will add the second subscription, which is the
optionVoted, it will just return the option that has been voted since we don’t really need the whole poll:
And now we will implement the subscription and update the
voteOnPoll mutation resolver:
And we’ll also give it a try:
Building the Frontend with VueJS
In this section we will start building the front-end part of our app, we will be displaying the polls, voting on polls and adding a form to create new polls.
Fetching the polls
We will be using bootstrap to style our app, luckily we have bootstrap-vue which is an implementation of the bootstrap framework as Vue components. The setup is easy, first, we will install it by running:
> yarn add bootstrap-vue
Then, we will configure it in our
We have just imported the styles and registered the
BootstrapVue plugin in our Vue app.
After that, we will fetch our data using GraphQL queries, if you take a look at
src/gql/person.queries.js you will see that we’re exporting a query using
graphql-tag will transform our query string into AST, which is required by
apollo-client to handle the operation. In our case, we will rename
poll.queries.js, and change the query content:
Further, we have to rename
Polls.vue, since this is the view that will render when we load our app.
After that, we need to update and change the import path inside our
router.js routing configuration:
Now, if you go to
localhost:8080 you will probably find some errors, that’s because our
Polls.vue component is still referencing some old files and code. Let’s fix it and use the poll query to fetch our polls.
We will update the imports and supply our imported query to the vue options
As a result, the response data of the query will be stored inside the
polls attribute, which will become reactive data when the component is rendered.
Moreover, if we go ahead and inspect our
Polls component using the Vue Devtools we will see that we have our data fetched:
Displaying the polls
We will use bootstrap to display the polls in the form of cards in a what’s called a card column group:
And this will result in something like this:
As you can see, we’re just looping over the polls and creating a card element for each poll, the card element from bootstrap-vue accepts a title, and a sub-title which we used as a description.
Next, inside the body of the card, we’re displaying a list of radio buttons with our options. The value for each radio button is the option’s id, that we will use later for voting, pretty straight forward.
The final step here is to export the card into a separate component which we will call
SinglePoll.vue that will live inside
Then, we will use it inside our
Poll.vue component and replace the old code:
Voting for options
To vote for options, we will have two operations to implement, Firstly, we will use the
voteOnPoll mutation, which will increment the options’s votes. In addition to that, we will register an
optionVoted subscription which will update our data in real-time.
Starting with the mutation, we will first create a
src/gql/poll.mutations.js file which will export the GraphQL mutation query:
After that, we will import it into the
Polls.vue component and use it whenever we have an
optionVote event triggered:
We’re just executing the mutation using Apollo Client which has a reference inside the $apollo attribute of the component. We’re supplying the
optionId passed as an argument to the event handler.
The only thing missing now is emitting the event from the single poll component:
When clicking on a radio input, the
<b-form-radio-group /> component will emit a change event with the value of the radio input, which in an earlier section we set to be the option’s id.
We’re passing this event up to our
Polls.vue component to trigger the handler and execute the mutation.
If we try our app in the current state we should be able to vote on options, However, we can only see the new result after refreshing the page:
Subscribing for real-time results
To make our client updates when the option is voted, we will subscribe to the
optionVoted subscription. First, we will define the subscription query inside
Next, we will import it into our
Polls.vue component and use it inside the polls query:
Let me now explain this new code, so what we did here is that we used the subscription query inside the polls query in a something called
subscribeToMore, as the name implies, will make apollo-client subscribe to more data coming from the server in real-time.
The thing here is that since the
subscribeToMore is attached to a query, when the subscription updated the data in the client, the query result will also be updated since, in our example, a poll and an option are related to each other. In other words, if a poll contains the option being updated, it will also be updated and subsequently, the view will be re-rendered.
Finally, we’re reaching the end of this tutorial, just a few steps until we wrap it up. Now, we will only have to do the poll creation, we will make that “create a poll” button show a poll creation modal, and we will make it as simple as possible.
The modal will show a form that will contain a title text input, a description text area, and a text input for the first option with a button to add more text inputs for additional options:
Our modal will be hidden since we have
isModalVisible set to false by default, the only thing that will show up is the Create a poll button, once we click on it the boolean will change to true and our modal will show up.
There, we can write the title, description and add as many options as we want. We have implemented a method that will add an option to the options array with an empty name, and since we can use v-model with array elements, in the template, we’re just looping over the options and doing so.
Once we click on the ok button in the modal we will emit a
createPoll event with the poll data.
Now we will use this component in the
Polls.vue component and handle the event by running the
We’re only missing the CREATE_POLL mutation query, let’s go ahead and add it to
Let’s give it a try:
It works, but it will appear after we reload the page.
Showing real-time created polls
Now, remember I said that this was the last thing we will do at the beginning of this section, Well, I was not quite telling the truth, because the real last thing that we will do, is making this real-time, the poll creation.
First, we will create the subscription query inside
Now we will import it inside the
Polls.vue component and use it inside the
subscribeToMore object, which we will turn into an array of objects since we have two subscriptions now:
So the only difference between the option voted and poll created subscription is that in the latter we have implemented something called
Remember that in an earlier section I said that if the subscription is going to update something that exists in the apollo-cache, Apollo will do it automatically for us, and it will also update all the queries that relate to the entity being updated in the cache.
However, if the subscription is introducing something that doesn’t exist in the cache already, like a new poll in our example, we have to add it manually.
updateQuery method will provide us with the
previousResult, in our case, it will be the list of polls that we already have, and the
subscriptionData, which will contain the data of the newly created poll.
Our mission is to merge them together and return the final result. Then, we let Apollo handle the rest.
So what we did here is that we have returned a new array, which will contain the new poll as the first element since we want to see it at the beginning of the list, and then we have spread the list of the older polls.
Let’s give it a try:
I hope that you have learned something from this tutorial. If you have found this useful don’t forget to share and spread the knowledge. See you on the next one!
You can also follow me on: