DEV Community

Godwin Alexander Ekainu
Godwin Alexander Ekainu

Posted on • Edited on

A definitive guide to using serverless functions with Fauna, Netlify, and VueJS

Most of the big streaming companies we see today, like Netflix, Hulu, or Crunchyroll, use some kind of serverless database. If you want to build a product that scales, then a serverless database like Fauna is the right choice. Serverless functions play a big role in this scalability, so in this tutorial, we'll look at how we can use serverless functions in Netlify.

Here's a simple definition to get us started:
👉 Serverless functions are single-purpose functions deployed and hosted on infrastructure managed by cloud computing companies like AWS. Tasks that would easily require a fully-fledged backend hosted on a server — like communicating with APIs or a database like Fauna — can easily be done with serverless functions without having to manage backend servers for your application.
The main goal of this tutorial is to give you a definitive guide on running your serverless functions locally using Netlify CLI and the Netlify dev command.

Goal

It’s never been easier to create and deploy serverless functions with Netlify!
We’ll also explore how we can integrate Fauna, a serverless database which gives us the simplicity of a NoSQL and the ability to model complex relationships for applications. It also supports multi-tenency, which allows a database to have multiple collections or even child databases.

What you need

  • Netlify account
  • Fauna account and database
  • A project you want to use serverless functions in
  • Some JavaScript knowledge

Set up a Netlify Account

To get started with Netlify serverless functions, we need to have a Netlify account. Go to https://app.netlify.com/signup and choose a sign-up option to create an account if you haven't already. Keep in mind that you might have to answer a few questions if you’re creating a Netlify account for the first time.
https://lh6.googleusercontent.com/4Si4vuf2hPxwh18qqw_vGleogPr5xdoPy9UyG-adtznuMKSyCy8FnQ5LQd5GNs69bOSOJTtuwW_D8fKK3ITeeTPFOz60XkufEYZLhSPBZ1N4UW2RMOK6ESlQTTzXiUSmijWjcTVI
We’ll be using the Netlify dashboard to manage our sites and functions, so it’ll make sense if we knew our way around the interface.
Once signed in, you'll be taken to your Netlify dashboard.
Here, you’ll see an overview of your Netlify account on the Team Overview page, which includes some stats about your usage.
The Sites section displays the sites you have deployed to Netlify.
You can manually upload the static (HTML, CSS, and JS) files to your static site by simply dragging and dropping from your computer to your dashboard.
You also have the option to connect your Netlify account to a GitHub repo of your project and Netlify will run the build process and generate static files and for your app and deploy it.

Creating a functions folder

To get started with Netlify functions on your project, create a /functions folder in your project directory. This folder is where all our Netlify functions will live, we will later make some configurations for Netlify to access the functions in this folder.
For this article, we’ll be using a simple Vue project to get us started. It's nothing to get too excited over; it just gets random images from Unsplash and display them to users.
If you want to follow along, you can go ahead and clone the repo on your computer, navigate to the newly created folder, and install the dependencies.

git clone <https://github.com/Maxiggle/randomImages.git>
cd random-images
npm install
Enter fullscreen mode Exit fullscreen mode

This is a Vue3 project created with Vue CLI. The basic structure should look like this:

random-images
 ┣ public
 ┃ ┣ favicon.ico
 ┃ ┗ index.html
 ┣ functions
 ┃ ┣ getAPIUrl.js
 ┃ ┗ hello.js
 ┃ main.js
 ┣ src
 ┃ ┣ assets
 ┃ ┃ ┗ logo.png
 ┃ ┣ components
 ┃ ┃ ┗ ImgCont.vue
 ┃ ┣ App.vue
 ┃ ┗ main.js
 ┣ .env
 ┣ .gitignore
 ┣ README.md
 ┣ babel.config.js
 ┣ package-lock.json
 â”— package.json
Enter fullscreen mode Exit fullscreen mode

For now, we'll just have a simple function to show how functions work. Create a new file, /functions/hello.js. This is the basic structure of a Netlify function.

// /functions/hello.js

exports.handler = async function(event, context){
        return {
                statusCode: 200,
                body: JSON.stringify({message: "Hello World!"})
        };
};
Enter fullscreen mode Exit fullscreen mode

In our function, exports.handler is assigned an async function that accepts two arguments, event and context. Inside, we return an object with two basic properties:

  • statusCode, which in this case is 200 OK and,
  • body. The value of body is an object: {message: "Hello World!"}. That's converted to valid JSON using the JSON.stringify() method.

So, that's our simple function right there. Next, we'll add some configurations to be able to run this function.

Setting up our Netlify configuration

In order to run our Netlify function, we need to designate a folder in a project where Netlify would look for our functions that will be deployed. To do this, create a netlify.toml file at the root of the project and add the following:

# netlify.toml

[functions]
        directory = "./functions"
Enter fullscreen mode Exit fullscreen mode

This tells Netlify to look for our functions in the ./functions folder, which is a relative path from the root of the project. Netlify will locate and deploy the functions in this folder at build time.

Installing the Netlify CLI

Netlify's CLI lets us deploy our project locally on our own computers. To install it, make sure you have at least Node.js version 10 or later installed on your computer. Install it with:

npm install netlify-cli -g
Enter fullscreen mode Exit fullscreen mode

This gives you a global installation of Netlify CLI, so you can run it from any directory. cd into the root folder of the project and run:

netlify dev
Enter fullscreen mode Exit fullscreen mode

You should see a few things happening here:
https://lh3.googleusercontent.com/jqJ-0GypwHBTSu836AcmiDz3900ziW6FU4m4aKrZQrHc84NNOZo8AasOyq-BwmY4RnM9ZqwW62hwxM25xhcWsE5X8rxRlKYgu_Qihh_BHr234Xeco-vYUOPkW6fJ4utvisaPFge3

A few things of note:

  1. Netlify injects environment variables from our .env files into the build process which can be accessed by our serverless functions.
  2. It loads or deploys the functions located in the specified directory. The /functions server is deployed on a different port from the main application, it's given a random port of 45661, as you’ll notice in the screenshot above.
  3. It automatically detects what framework the application was built with and runs the necessary build processes to deploy the application. In this case, it’s Vue.js, but it also supports React and other popular frameworks.

Now that our functions are deployed, let’s go ahead and test them. The route will look like /.netlify/functions/<function name>. When deployed, they can be accessed through the application's default URL and port because Netlify performs some proxying in the background so that the frontend can communicate with the functions.
If we send a GET request to https://localhost:8888/.netlify/functions/hello, we should get a response of {"message":"Hello World!"}. And sure enough:
https://lh5.googleusercontent.com/UmE0NPD_Woqr7poda32u9eooB-JreuQR7sLrl5lD7koZQ6yzD5cj0a-LWPByCZekXnkfLmL-xoWD1wihfZSuDIsP53GPHiaqYaX0wNbWsLsi9jnPNtdtQTp5OVUNLxCkzn6T25I2
It works!
Now, let’s do something useful with the functions. Create a new function at
/functions/getImg.js.

// /functions/getImg.js

const fetch = require('node-fetch')
const unsplashURL = `https://api.unsplash.com/photos/random/?client_id=j3uex5UW8H34vNuI_2e6h76CNYYkFWSfTQmsiMxs2qQ&?query=lucky&orientation=landscape`;
const getImgData = async () => {
    const res = await fetch(unsplashURL);
    const data = await res.json()
    return data
}
exports.handler = async function () {
    const imgData = await getImgData();
    return {
        statusCode: 200,
        body: JSON.stringify({ data: imgData }),
        headers: {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Headers": "Content-Type",
            "Content-Type": "application/json"
            // "Access-Control-Allow-Methods": "GET, POST, OPTION",
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we first require node-fetch, a Node.js implementation of the window.fetch API. To install the version used for this project, run:

npm i node-fetch@2
Enter fullscreen mode Exit fullscreen mode

This will install version ^2.x.x of node-fetch. Once that’s installed (if it wasn't already), you can now require it in your function code.
Now, we can ping the unsplashURL from our API endpoint. It’s just an Unsplash API URL with some hard-coded parameters:

  • Client ID - client_id=redacted-redacted-redacted
  • Query - query=lucky
  • Orientation - orientation=landscape

Later on, we’ll use a .env variable to store our Client ID.
After that, we have a function getImgURL() which simply fetches and returns the image data from Unsplash.
Finally, we have our exports.handler function. Here, imgURL is assigned the value image data received from the getImgData() function. The function then returns that same object from before, with the statusCode, body with the imgData, and headers.
To use this function in our app, we have our Vue component:

<!-- src/components/ImgCont.vue -->

    <template>
      <div v-if="img.urls" class="cont">
        <div class="img-cont">
          <img :src="img.urls.regular" :alt="img.alt_description" />
          <span>{{ img.description }}</span>
        </div>
        <button class="like">💖</button>
      </div>
      <button @click="getImg()">Get a lucky picture</button>
    </template>

    <script>
            import { ref } from "@vue/reactivity";
            export default {
              setup() {
                const clicked = ref(false);
                const img = ref({});
                const getImgData = async () => {
                  const API_URL = `/.netlify/functions/getImg`;
                  let imgData;
                  try {
                    const res = await fetch(API_URL);
                    imgData = await res.json();
                  } catch (err) {
                    console.log(err);
                  }
                  return imgData.data;
                };
                const getImg = async () => {
                  img.value = await getImgData();
                };

                return { getImg, img, clicked };
                    },
            };
    </script>
    <style scoped> ... </style>
Enter fullscreen mode Exit fullscreen mode

In this component, we declared API_URL as our Netlify function route.
To get the data from the function, we have getImgData() which just fetches and returns the data from the route.
Another function — getImg() — runs when the button is clicked. It runs the getImgData() function that sets the value of img to the data returned data. We can now use the image data in the app.
We also have a button near the end of our markup which will run a function savePictureToFauna() that’ll add the current image to Fauna. We'll get back to all of this later on.

Adding environment variables

In our Netlify function /functions/getImg.js, the Client ID is hard-coded into the URL in the file. We need to save this as an environment variable instead. To do that locally, create a new .env file in the root folder:

UNSPLASH_CLIENT_ID=j3uex5UW8H34vNuI_2e6h76CNYYkFWSfTQmsiMxs2qQ
Enter fullscreen mode Exit fullscreen mode

Now, go to /functions/getImg.js and add the environment variable:

// /functions/getImg.js
...

const CLIENT_ID = process.env.UNSPLASH_CLIENT_ID;
const unsplashURL = `https://api.unsplash.com/photos/random/?client_id=${CLIENT_ID}&?query=lucky&orientation=landscape`;
Enter fullscreen mode Exit fullscreen mode

Another great thing about Netlify CLI is its hot-reload feature. Saving this file will automatically load the function again.

Deploying the project

We can deploy our project to Netlify from the CLI. In order to do this, we have to build our project locally and then deploy.

npm run build
netlify deploy
Enter fullscreen mode Exit fullscreen mode

This creates a dist/ folder where our site lives.
Then, follow the prompts. Netlify asks for authorization on your first deploy:
https://lh4.googleusercontent.com/btrMlvuyLEQ8gwQcugHsOuS3EOw5W5LWABhctvFv03fo59p0LT5vp39t3R_8fMYxAa5MJA-Ndrx3vNu2uHwwuARbwyTo6-cN09E-p4Nc0pJiOrOoqB3ZJQoG69f0ekeblFZu2XEE
https://lh3.googleusercontent.com/1oX-hGq8RrYhKmEw6l_VuNX14TtCy7OjyqTyQreydDcbNxxmrG1SFdSVLbqkhLcMveJA7GP5-FHuBodz-KIl2fpqNOFCjeL-bNedMe9CfNbeznjXXRArxd3Ferpz6HvVr7rcPOR2
It'll ask for a few other things too:

  • Whether to link to an existing site or create and configure a new site — we’ll create a new site
  • A unique site name/URL — you can use whatever you want
  • The publish directory — Vue put our build files in dist/, so we'll use that

We now have a draft deployment of the project. To deploy the production build on the main site URL, we just have to run:

netlify deploy --prod
Enter fullscreen mode Exit fullscreen mode

Pulling environment variables from Netlify dashboard

To add our .env variables to our deploy, go to your Netlify dashboard, select your site, then go to Site settings > Build & Deploy > Environment and add your environment variables.
https://lh6.googleusercontent.com/bxMh5-4lpA12RziPe3VTdlJ7qhbK7G3XPVid7WtB1kMYSbzDPWAdELPyLT5IvCLAGMBuIo_vwBDKL1Jsm2kJqlBlMeDFa1MZY5jwpwK7ez-PkbJlZoUx0vdil9hjkordCTExuetL
We can also use the env variables we add to our Netlify dashboard for our local deployment when running netlify dev again.
https://lh4.googleusercontent.com/RBsCL8CtrI8KXahXGORQ3ZiXbnZEZMblGuy4992NSx18RdqS7j_7gw-7s0lB0CbJg6E6FUH_OfGm5YlHhpRCtztZL898fb_tznaCMT2zIl3NNge4Mnn0zo_5pUJU7n_rlbGtmgI3
You can see that it ignores the environment variable from the build settings we defined on our dashboard and injects the one in the .env file. If we delete the .env file, Netlify still injects the variable from the build settings of the site.

👉 Quick note: this is only possible because the local project has been linked to the site. To link a local project to an already existing site on Netlify, simply run `netlify link`.

View functions on your dashboard:

To see your functions and their logs on your dashboard, select your site and go to Functions:
https://lh5.googleusercontent.com/w7K8CyXo7Oh1NisUhYJZiKykxwxzMUxx6glfjTO5GV4Mei5Fujfn7YJ7MT1uzj56xMbrR3aUtGHZeML4j6ABjsmHyg1Tin1jO1LP1cR2xqtbnx1uXjyp9mfC5cCUaRn4T3BvR1X4
Select a function to view its details and logs:
https://lh6.googleusercontent.com/QX2NtwCHIfkQ7cB5Q37ZnJvHBF-0CHcY7UGB9pbwu0BzlDDvCd2wfC5fHYFffBIEu-7disxrI_W9qZd8NjenH_Txl2nK8AENx6BnYpYl0aEWjC-IVzQXoyOZSlBs97Fp9GRoyA77
Awesome!
Now that we’ve seen how to work with Netlify serverless functions, let’s explore how we can add more functionality, like connecting to a database. This would normally require setting up a complete backend, but with Netlify functions we can easily pull this off without managing a backend server.

Setting up Fauna

To get up and running with Fauna, create an account and follow the steps on their quick start guide . Once you’re all set up, go to your dashboard and create a new database.
https://lh3.googleusercontent.com/OpbsbEzjMBRyurul_UqNobCpkYuESCTRBzow-zUSRCvxihTOUUbiBUTt4-as0Yh7b4e9DgDKNZN4D7L5gDfGyRUu-xV6EmiJnq_DZfP6I-lsH36iPk4cMv7u83fBhdCNR-H31SUh
Then, create a new collection.
https://lh5.googleusercontent.com/q-d5zKLxwZF3A-ugupC9fYOZ1WovIlri-mGQ4Y0xx8YJ5PxLFaynkJpXbCNS1XmUyxrknRJLCSZ7TU3hrxJ0vb2zoP75EidwiDNS99k4XxdMzmuas12rkVBI8K825XtcCH_6qhNi
Great! Now that you’ve created your collection, you can now create your own API key that you can use to access your database from your function.
https://lh3.googleusercontent.com/xNWyrVMbC6EvJc_G-7BxDfvpbk2KQoYhSt98eaKeW5286oviWlSOvrd8xHDINqsbK0d3RhMAM2ksg8cZjROuN1KQIrKyJCSe0kz5Amn6DDv1leFWwTMrgCzyLduTwA-_Mndca6wP
Once you’ve generated the key, you can add it to your .env file.

FAUNADB_SERVER_SECRET=<secret>
Enter fullscreen mode Exit fullscreen mode

To use Fauna in your project, install the driver for Javascript. You can simply install the Fauna npm project if you prefer:

npm install faunadb
Enter fullscreen mode Exit fullscreen mode

After successfully installing the package, create a new Netlify function functions/savePictureToFauna.js that’ll save liked pics to the database.

// ./functions/savePictureToFauna.js

const faunadb = require('faunadb');
// initialize faunaDB client with our secret
const client = new faunadb.Client({
    secret: process.env.FAUNADB_SERVER_SECRET,
    domain: 'db.fauna.com',
    scheme: 'https'
});
// the query object provides us with functions to create a new document in the collection
const q = faunadb.query;

exports.handler = async (event, context) => {
    // get the data from the body of the request
    const data = JSON.parse(event.body);
    try {
        // create document in existing collection
        const response = await client.query(
            q.Create(q.Collection('fav_piks'), {
                data
            })
        );
        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'Successfully created document',
                data: response
            })
        };
    } catch (error) {
        console.log(error);
        return {
            statusCode: 400,
            body: JSON.stringify({
                error
            })
        };
    }
};
Enter fullscreen mode Exit fullscreen mode

Then to run this, in the imgCont component of this example project, create a new function savePictureToFauna() that’ll send the img data when clicked.

<!-- src/components/ImgCont.vue -->

<template>
  <div v-if="img.urls" class="cont">
    <div class="img-cont">
      ...
    </div>
    <!-- run savePictureToFauna() with the img as an argument -->
    <button ref="likeBtn" class="like" :disabled="!canLike" @click="savePictureToFauna(img)"> 💖</button>
  </div>
  <button @click="getImg()">Get a lucky picture</button>
</template>
<script>
        import { ref } from "@vue/reactivity";
        export default {
          emits: ["savedPik"],
          setup(props, { emit }) {
            const img = ref({});
            const canLike = ref(true);

            const getImgData = async () => { ... };
            const getImg = async () => { ... };

            // function to POST img data to our function
            const savePictureToFauna = async (img) => {
              try {
                fetch(`/.netlify/functions/savePictureToFauna`, {
                  method: "POST",
                  headers: {
                    "Content-Type": "application/json",
                  },
                  body: JSON.stringify({
                    img,
                  }),
                });

                alert("Pik saved!");
                // emit custom savedPik event
                emit("savedPik");
                canLike.value = false;
              } catch (err) {
                console.log(err);
              }
            };

            return { getImg, img, savePictureToFauna, canLike };
          },
        };
</script>
<style scoped> ... </style>
Enter fullscreen mode Exit fullscreen mode

In the code above, the savePictureToFauna function which takes the img data as an argument, performs a POST request to the savePictureToFauna.js Netlify function, which in turn sends the data to Fauna, saving our favorite pictures in the database!
We can also get back pics sent to Fauna. In order to retrieve data from the collection, you can use the [Paginate](<https://docs.fauna.com/fauna/current/api/fql/functions/paginate?lang=javascript#examples>) function. This function allows us to fetch documents in a collection from Fauna. Paginate takes in a Set or Reference and returns a page of results.
The problem with this is that it simply returns a list of references to each document in the collection without the actual data. You can get the content of the documents by running Paginate within the [Map](<https://docs.fauna.com/fauna/current/api/fql/functions/map?lang=javascript>) function. It'll iterate on the array of documents from our collection and call the provided lambda function on each one, returning the results.
To do this, create a new file at functions/getPicsFromFauna.js, similar to the others.

  // ./functions/getPicsFromFauna.js

    const faunadb = require('faunadb');
    const client = new faunadb.Client({ ... });

    // the query object provides us with functions to create a new document in the collection
    const q = faunadb.query;

    exports.handler = async (event, context) => {
        console.log('Function `savePictureToFauna` invoked');
        try {
            const res = await client.query(
                // map through each document returned by paginate
                q.Map(
                    // get documents from collection
                    q.Paginate(
                        q.Documents(
                            q.Collection('fav_piks')
                        )
                    ),
                    // get content of each document
                    q.Lambda('doc', q.Get(q.Var('doc')))
                )
            );
            return {
                statusCode: 200,
                body: JSON.stringify({
                    message: 'Successfully fetched documents',
                    data: res.data
                })
            };
                    } catch (error) {
            console.log(error);
            return {
                statusCode: 400,
                body: JSON.stringify({
                    error
                })
            };
        }
    }
Enter fullscreen mode Exit fullscreen mode

The App.vue file has been modified to fetch and display the list of saved pics fetched from the functions/getPicsFromFauna.js function.

  <!-- src/App.vue -->

    <template>
      <main>
        ...
          <!-- run getPicsFromFauna() each time the "saved-pik" event is fired from the child component -->
          <img-cont @saved-pik="getPicsFromFauna()" />
          <section>
            <h2>Liked Pics</h2>
            <ul v-if="favPiks.length > 0" class="favPiks">
              <li v-for="favPik in favPiks" :key="favPik.ref['@ref'].id">
                <div class="img-cont">
                  <img :src="favPik.data.img.urls.small" :alt="favPik.data.img.description" class="favPik" />
                </div>
              </li>
            </ul>
            <span v-else> No fav piks</span>
          </section>
        ...
      </main>
    </template>
    <script>
    import { ref } from "@vue/reactivity";
    import ImgCont from "./components/ImgCont.vue";
    export default {
      components: { ImgCont },
      setup() {
        const favPiks = ref([]);
        // get pics function
        const getPicsFromFauna = async () => {
          try {
            // get pics from Netlify function
            const res = await fetch("/.netlify/functions/getPicsFromFauna");
            const data = await res.json();
            favPiks.value = data.data.reverse()
          } catch (error) {
            favPiks.value = null
          }
        };
        return { favPiks, getPicsFromFauna };
      },
      async mounted() {
        this.getPicsFromFauna()
      },
    };
Enter fullscreen mode Exit fullscreen mode

Sweet! That's it!

Wrapping up

You’ve seen how you can build modern applications on the JAMstack with serverless functions. This makes development a whole lot easier since we no longer need to create a backend to interact with our database.
Serverless functions are the bread and butter of the Jamstack, and Netlify makes them easy. We’ve covered only a few of their capabilities, but if you want to learn more, check out some of these useful links:

Written in connection with the Write with Fauna Program

Top comments (1)

Collapse
 
xixianykus profile image
xixianykus

Looks interesting but the netlify dev build fails because of the UNSPLASH_CLIENT_ID in the env file. Anyway round this?