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
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
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!"})
};
};
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 is200 OK
and, -
body
. The value of body is an object:{message: "Hello World!"}
. That's converted to valid JSON using theJSON.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"
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
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
You should see a few things happening here:
https://lh3.googleusercontent.com/jqJ-0GypwHBTSu836AcmiDz3900ziW6FU4m4aKrZQrHc84NNOZo8AasOyq-BwmY4RnM9ZqwW62hwxM25xhcWsE5X8rxRlKYgu_Qihh_BHr234Xeco-vYUOPkW6fJ4utvisaPFge3
A few things of note:
- Netlify injects environment variables from our .env files into the build process which can be accessed by our serverless functions.
- 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. - 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",
}
}
}
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
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>
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
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`;
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
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
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.
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>
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
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
})
};
}
};
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>
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
})
};
}
}
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()
},
};
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:
- GitHub Code
- Get started with Netlify CLI | Netlify Docs
- Create Your First Serverless Function - Up and Running with Serverless Functions - Jamstack Explorers (netlify.com)
- Functions overview | Netlify Docs
Written in connection with the Write with Fauna Program
Top comments (1)
Looks interesting but the netlify dev build fails because of the
UNSPLASH_CLIENT_ID
in theenv
file. Anyway round this?