Using Next.js with FaunaDB: How to Query the Database from Your App
Tags: jamstack, javascript, webdev
Published: 2021-03-26 16:17:39

---
title: Using Next.js with FaunaDB: How to Query the Database from Your App
published: true
description: This article will explore how to get started using FaunaDB and Next.js to connect a database to a Jamstack site built in the framework.
tags: jamstack, javascript, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6of2b8i8hm43a2ts87cb.png
canonical_url: https://snipcart.com/blog/nextjs-faunadb
---

One of the things distinguishing [Jamstack](https://snipcart.com/blog/jamstack) sites from plain static sites is their use of data from [APIs](https://snipcart.com/blog/integrating-apis-introduction). 

While a traditional static site might use file-based data like Markdown and YAML, a Jamstack site frequently uses things like a [headless CMS](https://jamstack.org/headless-cms/), [headless ecommerce](https://snipcart.com/blog/headless-ecommerce-guide) or third-party APIs. Headless CMS and API-driven services like Algolia fill many needs but are built for particular purposes. Your Jamstack site might need to store and access data that doesn't fit into the CMS archetype, for instance. In those cases, you might need a... database!

[Fauna](https://fauna.com/) is one solid option. It is a cloud-based transactional serverless database that makes that data available via a data API. This makes it ideal for use in a Jamstack application.

> A great piece about FaunaDB with its CEO [here](https://www.ibm.com/cloud/blog/database-deep-dives-faunadb).

This article will explore how to get started using FaunaDB and [Next.js](https://www.youtube.com/watch?v=H4snDqITlmc) to connect a database to a Jamstack site built in the React framework.

![Next js and FaunaDB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kn5d78py74wbko19qgvo.png)

We'll cover:

1. [Setting up FaunaDB](#setup)
2. [Using Fauna Query Language](#fql)
3. [Setting up Next.js and Fauna DB](#nextjs)

> More about serverless architectures [here](https://snipcart.com/blog/why-serverless-example)

<h2 id="setup">Setting up FaunaDB</h2>

Fauna databases provide many ways to get started. You can use the web-based admin to create and manage new databases. However, you can also do most actions via the [Fauna Shell](https://github.com/fauna/fauna-shell), a CLI for interacting with Fauna, which is what we'll use for this tutorial.

```bash
npm install -g fauna-shell
```

Now we can log in.

```bash
fauna cloud-login
```

You'll need to enter your email and password. If you signed up using third-party authentication like Netlify or GitHub, you need to create the database via the web admin and get a security key in the security tab of the database.

<span style="font-size:.8em;">Fauna documentation about cloud-login [here](https://docs.fauna.com/fauna/current/integrations/shell/cloud-login.html)</span>

We'll create a simple application using Next.js that will be a list of shows that I want to watch. Let's create a new database to store this data.

```bash
fauna create-database my_shows
```

At this point, we can use the shell to interact with the database and create new collections, which are Fauna's equivalent of a [table](https://en.wikipedia.org/wiki/Table_(database)).

```bash
fauna shell my_shows
```

You should see something like the following:

```bash
Starting shell for database my_shows
Connected to https://db.fauna.com
Type Ctrl + D or .exit to exit the shell
my_shows> 
```

<h2 id="fql">Using FQL (Fauna Query Language) to create and query data</h2>

Once inside the shell, you can interact with your new database using [FQL (Fauna Query Language)](https://docs.fauna.com/fauna/current/api/fql/). FQL is essentially Fauna's API language for creating, updating, and querying data. However, it isn't an API in the way you're probably used to using one. It includes things like [data types](https://docs.fauna.com/fauna/current/api/fql/types?lang=javascript), [built in functions](https://docs.fauna.com/fauna/current/api/fql/cheat_sheet) and even [user defined functions](https://docs.fauna.com/fauna/current/api/fql/functions) that make it feel more like a programming language than a typical API. There's a lot you can do with FQL, more than we can cover in-depth here. Be sure to refer to [the documentation](https://docs.fauna.com/fauna/current/api/fql/) for a full overview.

Let's start by creating a collection called "shows."

```javascript
CreateCollection({ name: "shows" })
```

A collection in Fauna stores documents. If you are more comfortable with a traditional relational database model, you can think of these as table rows. We could create a single document using the `Create()` method, but instead, populate multiple documents using the `Map()` method. We'll map over a nested array of values. Each of the nested arrays represents the values of one document. We'll use these to populate the two properties in our document, `title` and `watched`. For now, we'll set `watched` on all these dummy items to `false` to indicate we have not yet watched them.

```javascript
Map(
  [
    ["Kim's Convenience",false],
    ["I'm Sorry",false],
    ["The Good Place",false]
  ],
  Lambda(["title","watched"],
    Create(
      Collection("shows"), { data: { title: Var("title"), watched: Var("watched")} }
    )
  )
)
```

Lastly, let's query for all the documents in our "shows" collection. In this case, we'll use `Collection()` to define which collection we are pulling from, `Documents()` to say that we want all the references to each document in our shows collection, and then `Paginate()` to convert these references to [Page objects](https://docs.fauna.com/fauna/current/api/fql/functions/paginate?lang=javascript#page). Each page will be passed to the `Lambda()` function, where they will be used to `Get()` the full record.

```javascript
Map(
  Paginate(Documents(Collection("shows"))),
  Lambda(show => Get(show))
)
```

You should see a result like:

```json
{
  data: [
    {
      ref: Ref(Collection("shows"), "293065998672593408"),
      ts: 1615748366168000,
      data: { title: "I'm Sorry", watched: false }
    },
    {
      ref: Ref(Collection("shows"), "293065998672594432"),
      ts: 1615748366168000,
      data: { title: 'The Good Place', watched: false }
    },
    {
      ref: Ref(Collection("shows"), "293065998672595456"),
      ts: 1615748366168000,
      data: { title: "Kim's Convenience", watched: false }
    }
  ]
}
```

Finally, before we move on, we should create an index for this collection. Among other things, the index will make it easier to locate a document, updating the records easier.

```javascript
CreateIndex({
  name: "shows_by_title",
  source: Collection("shows"),
  terms: [{ field: ["data", "title"] }]
})
```

Now that we have our database created and populated let's move to use it within a Next.js app.

<h2 id="nextjs">Getting data in Next.js with FaunaDB</h2>

We're going to walk through creating a simple web app using Next.js that uses our Fauna table to allow us to add shows we want to watch and mark the shows we've watched as done. This will demonstrate how to read data from Fauna and display it in Next.js, create new records in Fauna, and update an existing record.

The code for this sample is [available in GitHub](https://github.com/remotesynth/fauna-next). It borrows the layout from [this CodePen](https://codepen.io/alphardex/pen/rNNPQwa). You can see what the app looks like below.

![The sample of Shows to Watch app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/27x5cjpwfc69bc2o0wf0.gif)

To use the sample yourself, you'll need to provide a `.env` file with a value for `FAUNADB_SECRET` that contains a key from Fauna to connect to your `shows` collection. To obtain a key, go to the "Security" tab within your collection on the Fauna dashboard and create a new key.

We won't cover every detail of building a Next.js app here as it is just a single page. We'll explore some of the basic pieces that you need to understand to use Fauna.

> You can follow [this tutorial](https://nextjs.org/learn/basics/create-nextjs-app) from Vercel to get started with Next.js 

### Fauna JavaScript driver

To query Fauna within our app, we're going to use the [Fauna JavaScript driver]( https://docs.fauna.com/fauna/current/drivers/javascript). This is a Node.js library for integrating with Fauna. It allows you to run the same FQL queries that we ran within the Fauna Shell from your Node application. To add this to a new Next.js application, you need to run:

```bash
npm install faunadb
```

Within Node, you need to instantiate the client with your Fauna key. We can do this from within a `/lib/fauna.js` file that we will include wherever we need to access data in Fauna. It gets the key from an environment variable called `FAUNADB_SECRET` that is within a `.env.local` file.

```javascript
import faunadb from 'faunadb';

export const faunaClient = new faunadb.Client({
  secret: process.env.FAUNADB_SECRET,
});
```

### Protecting your API key

Before we begin getting data, there's one area of concern. Since our application data is all user-generated, it is getting all of its Fauna data client-side rather than at build time. This means that anyone inspecting the API call would have access to the Fauna key.

There are two ways to handle this:

1. Create a key that has very restricted permissions set within the Fauna dashboard to limit misuse.

This still exposes the key but limits the potential for misuse. It's handy if you are reading data and limit the key to read-only.

2. Create a serverless function that is an intermediary for calling the Fauna API, thereby hiding your key entirely. 

This is the more secure option because it never exposes the key at all. Users can still call the endpoint if they inspect how, but the API limits what they can do.

Luckily, within Next.js, there is an easy way to accomplish the second option by using [Nextjs API routes](https://nextjs.org/docs/api-routes/introduction). 

All of the interaction with Fauna within this sample app will go through one of three API routes: `getShows`; `addShows`; or `updateShows`.

### Getting data from Fauna database

From a Fauna Query Language standpoint, reading data from Fauna is pretty straightforward. We'll use the same `Map()` function we used within the Fauna Shell earlier. We need to do it in the context of the client that we instantiated earlier. 

The methods are all derived from an instance of the `query` object from the Fauna JavaScript driver. Otherwise, the query itself is the same.

```javascript
import { query as q } from 'faunadb';
import { faunaClient } from '../../lib/fauna';

export default async (req, res) => {
  if (req.method == 'GET') {
    let query = await faunaClient.query(
      q.Map(
        q.Paginate(q.Documents(q.Collection('shows'))),
        q.Lambda((show) => q.Get(show))
      )
    );
    res.status(200).json({ data: query.data });
  }
};
```

To call this from within our app when the page loads, we'll first instantiate a new state variable with our shows array:

```javascript
let [shows, setShows] = useState([]);
```

Then from within the `useEffect` React hook, we can call the API endpoint and populate the results with the data returned from Fauna.

```javascript
useEffect(async () => {
  let showData = await fetcher('/api/getShows');
  setShows(showData.data);
}, []);
```

### Adding data to FaunaDB

Next, let's look at the `createShows` API endpoint. To add a single row to our `shows` collection, we'll use the `Create()` method within FQL. We provide it an instance of the collection we are writing to and a structure containing the data we want to write.

```javascript
export default async (req, res) => {
  if (req.method == 'POST') {
    const body = JSON.parse(req.body);
    let query = await faunaClient.query(
      q.Create(q.Collection('shows'), {
        data: { title: body.title, watched: false },
      })
    );
    res.status(200).json({ data: query });
  }
};
```

We're defaulting a new show to `watched: false` but populating the title with whatever is passed.

(Note: for the sake of simplicity, I have not added a lot of error checking to this application, meaning calling this method without a title will fail).

Now let's use this API endpoint by first instantiating another state variable to hold the new show data entered into the form.

```javascript
let [newShow, setNewShow] = useState('');
```

We also need to add a function that will be the `onChange` handler for the form input.

```javascript
function handleNewShow(e) {
  setNewShow(e.target.value);
}
```

Finally, we need a method to handle when the user clicks on the "Add" button to submit a new show. This method calls our `addShows` API endpoint. That endpoint returns the new show data that was just added to Fauna. 

We then append that to the array of shows so that our UI will update with the show we just added. 

Finally, we clear the form input by emptying the value of the `newShow` state variable.

```javascript
async function handleAddShow() {
  const res = await fetch('/api/addShows', {
    method: 'POST',
    body: JSON.stringify({
      title: newShow,
    }),
  });
  const body = await res.json();
  // add the new show to the existing list
  let newShows = shows.slice();
  newShows.push(body.data);
  setShows(newShows);
  setNewShow('');
}
```

### Updating data in Fauna

Finally, we want to be able to check and uncheck a show as having been watched. To do that, we need to update a record in Fauna. 

This is where the index we added earlier will come in handy as it allows us to easily get a reference to the record using the show title.  We use that reference to get an instance of the record and then update the record with the new data, which is watched as either true or false.

```javascript
export default async (req, res) => {
  if (req.method == 'POST') {
    const body = JSON.parse(req.body);
    let query = await faunaClient.query(
      q.Update(
        q.Select(
          ['ref'],
          q.Get(q.Match(q.Index('shows_by_title'), body.title))
        ),
        {
          data: {
            watched: body.watched,
          },
        }
      )
    );
    res.status(200).json({ data: query });
  }
};
```

Next, we'll add a click handler for the checkbox inputs that will call the `updateShow` API endpoint. 

To update the UI, we loop through the show state variable and update the `watched` value on the proper show. Once the state is updated, the show will mark as watched or unwatched accordingly.

```javascript
async function handleUpdateShow(e) {
  const res = await fetch('/api/updateShow', {
    method: 'POST',
    body: JSON.stringify({
      title: e.target.value,
      watched: e.target.checked,
    }),
  });
  let newShows = shows.slice();
  newShows = newShows.map((show) => {
    if (show.data.title == e.target.value) {
      return Object.assign({}, show, {
        data: { title: e.target.value, watched: e.target.checked },
      });
    }
    return show;
  });
  setShows(newShows);
}
```

## Where to go from here

Obviously, this is a simple example, but hopefully, it gave you a solid sense of working with Fauna. There is much more that you can do with Fauna than we've covered here. 

For example, if you prefer to work with GraphQL rather than Fauna's FQL for querying, Fauna gives you the ability to upload a GraphQL schema and then use GraphQL for querying rather than FQL.

There's even a [Next.js starter](https://github.com/vercel/next.js/tree/canary/examples/with-graphql-faunadb) providing an example of how to use Fauna with GraphQL inside a Next.js app.

Fauna offers built-in [identity and authentication](https://docs.fauna.com/fauna/current/tutorials/authentication/user) that you can use to add a login to your Jamstack site. And there's also a [streaming feature](https://docs.fauna.com/fauna/current/drivers/javascript#streaming) in the preview that allows you to subscribe to a document so that any time it is updated or deleted, the stream is notified. Basically, this lets you push changes from Fauna to the client whenever data is updated.

It's worth giving Fauna a try - there's a generous [free tier](https://fauna.com/pricing) that you can do quite a lot with. Go ahead and put some data in your Jam.