Build a Twitter Reaction Counter Using Fauna, Netlify and the Nuxt framework.
Tags: fauna, nuxt, netlify
Published: 2021-06-23 17:25:55


> Fauna is at the peak of serverless databases and providing scalable and secure infrastructure to organizations and individuals with easy integration with various frameworks. This article will build a Twitter counter with Fauna, Netlify, and the Nuxt framework. I used Fauna as my serverless database of choice because of its high scalability and consistency.


## Audience

This article will be easier to follow if you have intermediate Vue.js/Nuxt.js knowledge. You should also know how to deploy a Nuxt project on Netlify.

## Goals

After reading this article, you will know how to use a serverless database like Fauna with a Nuxt jamstack site to create a twitter counter .

## What We Need

The purpose of this tutorial is to implement a kind of Twitter reaction counter in a blog post. I will be using the personal portfolio website I quickly built as a reference during this tutorial. In the next paragraph, we will set up Netlify and Fauna. We will be using the Nuxt content module as our content management system to write our blog posts and the blog slug to identify the blog post and properly route each article. Slug will be of great use as we implement the reaction system.


## Set-up Project

First, we will set up a Fauna account. To start, navigate to their official site, create an account with them [(here)](https://dashboard.fauna.com/accounts/register), and then log into the account and create our database for the project. 

![Create Database](https://paper-attachments.dropbox.com/s_B53A232721A6E3CA9ACE240FD32EFB00EDC78C7EC34DE5BE0C32C02304FF9A8D_1623100173379_createDB.png)


We need a way to store the reaction counter for the articles in our blogs, so we add a collection in our new database to do this.

![collection](https://paper-attachments.dropbox.com/s_B53A232721A6E3CA9ACE240FD32EFB00EDC78C7EC34DE5BE0C32C02304FF9A8D_1623100792260_collection.png)


Create an index that allows for easy organization and retrieval of documents by attributes according to their queried way


![add  index](https://paper-attachments.dropbox.com/s_B53A232721A6E3CA9ACE240FD32EFB00EDC78C7EC34DE5BE0C32C02304FF9A8D_1623151523716_myindex.png)


 From the image above, we create an index named “reaction_count” under the reactions collection created to use as the source collection. In the terms field, we have added data.slug as one of the terms that can be searched.


Using our Fauna configuration, we generate our Fauna API key that we can use in our Nuxt application to query our `reaction_counter_database` successfully. To do this, we go to the dashboard and click on the security section to generate the API.
  When we generate the API, we have to store it somewhere safe, like our environment variable file as `YOUR_FAUNA_SECRET_KEY`, so that it cannot be accessed by a third party.

## Installing Fauna in our Nuxt App

Now we can set up Fauna in our Nuxt application, by installing the fauna package.

    npm install --save faunadb 

When setting up my Nuxt project, i had already selected the `@nuxtjs/axios` plugin in making API request, so keep this in mind. You can manually install it by running:

    npm install @nuxtjs/axios

Also, in your Nuxt config file you can add in the modules

    export default {
      modules: ['@nuxtjs/axios']
    }

In the `.env` file created at the root of our application, we set our fauna API key

    YOUR_FAUNA_SECRET_KEY = THE_KEY

We want to be able to to test our app in development so we set configuration for the local and development deployment link like this in our Nuxt config file
```
        publicRuntimeConfig: {
            axios: {
              baseURL: process.env.NODE_ENV === 'production' ? process.env.BASE_URL || 'http://localhost:8000/' : 'http://localhost:8000/',
            }
          },
```
Now if we run the app locally we have it running at port `8000` , this is to avoid any errors when running our serverless function

## Securing Fauna's Secret Key on Netlify

We want to hide our Fauna API secret from prying eyes. Use of Netlify functions to protect your Fauna API secret. Netlify functions, based on AWS lambda, give you powerful abilities when building serverless functions allowing you to auto-scale your projects. In simple terms, they are functions that run on the server. To do this, you can visit Netlify and add the environmental variables we defined earlier.

![API key](https://paper-attachments.dropbox.com/s_B53A232721A6E3CA9ACE240FD32EFB00EDC78C7EC34DE5BE0C32C02304FF9A8D_1623186581375_faunasecret.png)


We store our Fauna secret key here to query our Fauna database when we deploy successfully. After setting up Netlify, Fauna, and Nuxt, we can dive into writing code. *Yayy!!*

## Writing our Fauna Functions

When our page loads, we want to fetch all the reactions on our blog and want to be able to increase the reaction count. The Fauna functions are what we will focus on implementing for now.
Write a function that allows us to fetch our reaction counter, but first, create a functions folder at the root of our Nuxt project, and in this folder, we create a fetch_reactions.js file to write our fetch reaction functions.

***Note****: Once deployed, Netlify automatically locates the functions folder where all our serverless functions are located.*
```

    const faunadb = require('faunadb');
    exports.handler = async (event) => {
      const q = faunadb.query;
      const client = new faunadb.Client({
        secret: process.env.YOUR_FAUNA_SECRET_KEY,
      });
      const { slug } = event.queryStringParameters;
      if (!slug) {
        return {
          statusCode: 400,
          body: JSON.stringify({
            message: 'Blog slug not available',
          }),
        };
      }
      const doesDocExist = await client.query(
        q.Exists(q.Match(q.Index('reaction_count'), slug))
      );
      if (!doesDocExist) {
        await client.query(
          q.Create(q.Collection('reactions'), {
            data: { slug: slug, reactions: 1 },
          })
        );
      }
      const document = await client.query(
        q.Get(q.Match(q.Index('reaction_count'), slug))
      );
      return {
        statusCode: 200,
        body: JSON.stringify({
          reactions: document.data.reactions,
        }),
      };
    };
```
Several things are going on above; we initialized Fauna and set up the Fauna client with our secret password. We check if the bog slug is provided as a query parameter, If it isn't, we return a status code of 400. After doing this, we check if the Fauna document that we created exists. If it does not exist, we create a new document with our reaction collection, and set the initial reaction count to `1` using our slug as a unique identifier. We send a query for the reaction_count index we created and retrieve our reactions and return the reaction count of the blog.

## **Increase Reaction Count Function**

The primary purpose of this function is to increment the reaction count in our blog post. As we will see below, it is almost the same as the fetch reaction function. The difference is that we are increasing the reaction count. 
***Note****: This function is the function that will be called when a reader on the blog post clicks the reaction button.*
To do this, we create a new file in our functions folder called increment_reactions.js and paste the code.

```
    const faunadb = require('faunadb');
    exports.handler = async (event) => {
      const q = faunadb.query;
      const client = new faunadb.Client({
        secret: process.env.YOUR_FAUNA_SECRET_KEY,
      });
      const { slug } = event.queryStringParameters;
      if (!slug) {
        return {
          statusCode: 400,
          body: JSON.stringify({
            message: 'Blog slug not available',
          }),
        };
      }
      const doesDocExist = await client.query(
        q.Exists(q.Match(q.Index('reaction_count'), slug))
      );
      
      if (!doesDocExist) {
        await client.query(
          q.Create(q.Collection('reactions'), {
            data: { slug: slug, reactions: 1 },
          })
        );
      }
      const document = await client.query(
        q.Get(q.Match(q.Index('reaction_count'), slug))
      );
      await client.query(
        q.Update(document.ref, {
          data: {
            reactions: document.data.reactions + 1,
          },
        })
      );
      const updatedDocument = await client.query(
        q.Get(q.Match(q.Index('reaction_count'), slug))
      );
      return {
        statusCode: 200,
        body: JSON.stringify({
          reactions: updatedDocument.data.reactions,
        }),
      };
    };
```
After we query our database, we increment the reaction counter by `1` by running an update in our database.

## Using Our Functions In Our Nuxt App

Using the functions we created earlier, we will create a Twitter love reaction button using SVG and assign a click event. When we click on the love reaction, a function runs that accesses our serverless functions that fetch and increment the love reaction.

***Note:***  *When we deploy our application, our functions can be located at .netlify/functions/OUR_FUNCTION_NAME*
In our component folder in our application, we will create a TwitterReaction.vue component where we will house our love reaction and button.
```

    // TwitterReaction.vue
    <template>
        <div>
            <button @click="addReaction" class="focus:outline-none">
                {{ initialReaction }}
                <svg
                    class="w-6 h-6"
                    fill="none"
                    stroke="currentColor"
                    viewBox="0 0 24 24"
                    xmlns="http://www.w3.org/2000/svg"
                >
                    <path
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        stroke-width="2"
                        d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
                    ></path>
                </svg>
            </button>
        </div>
    </template>
    
```
We can see above that we have addReaction function that runs when we click the button. The following function below runs in our script:
```

    // TwitterReaction.vue
    <script>
    export default {
        data() {
            return {
                initialReaction: null
            }
        },
        async fetch() {
            const { data } = await this.$axios.get(
                `/.netlify/functions/fetch_reactions?slug=${this.$route.params.slug}`
            )
            this.initialReaction = data.reactions
        },
        fetchOnServer: false,
        methods: {
            addReaction() {
                this.initialReaction++
                this.incrementLikes()
            },
            async incrementLikes() {
                await this.$axios.post(
                    `/.netlify/functions/increment_reactions?slug=${this.$route.params.slug}`
                )
            }
        }
    }
    </script>
```
What happens above is we set up a reactive reaction counter `initialReaction`, and using the fetch_reactions functions we fetch the reactions and set it to our `initialReaction`. Since we are using static mode, we set `fetchOnServer` to `false` to ensure the fetch hook is called whenever our component is mounted.
Then whenever a reader clicks on the reaction button, we run the `addReaction` method, which increments our reaction count.

## **Conclusion**

*Yayy!* We have finally come to the end of the tutorial. In this tutorial, we learned how to use a serverless database in a jamstack site, we also looked at setting up Netlify and hiding API keys. In the end, we created a  “Twitter-like” reaction counter.
If you have any questions, feel free to send me a message on [Twitter](https://twitter.com/umavictor_).

## **Resources**
- Read more about [Netlify Functions](https://www.netlify.com/products/functions/)
- [Indexing](https://docs.fauna.com/fauna/current/api/fql/indexes) in Fauna
- [Github Repo](https://github.com/uma-victor1/portfolio)