Chapter 2: Let's get into Fauna: a guide to understand Fauna while creating a social media database
Tags: fauna, nextjs, javascript, serverless
Published: 2020-10-21 02:30:47

In the [first chapter of this series] (https://dev.to/sertge/let-s-get-into-fauna-a-guide-to-understand-fauna-while-creating-a-social-media-database-23db), we had the chance to walk through the [Fauna](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_SocialMediaDatabase_SJamie) and Next.js tutorials and started a simple website with a basic login mechanic. In this chapter, we are going to create the functions within [Fauna](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_SocialMediaDatabase_SJamie) to sign up new users, create _following_ relationships between users, create posts, like them and comment them, we have our first approach to Fauna’s permissions system by granting users permissions to execute the functions mentioned.

#Create new users
Last time, we did register our user using the [Fauna](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_SocialMediaDatabase_SJamie) dashboard. This is an impractical approach because we want our site to be autonomous and that transaction should be done from the user interface in our website. This is what the API called signup does.

##What does this API do?
This API imports the query commands from fauna
```javascript
1 import { query as q } from 'faunadb'
```

And then, uses the Create command to create a new document on the _Users_ collection, 

```javascript
16	user = await serverClient.query(
17	  q.Create(q.Collection('User'), { //errata: our collection is called Users 
18	    credentials: { password },
19	    data: { email },
20	  })
21	)
```

As you can see, it’s very similar to the way we created our user with the Fauna’s dashboard. The main difference here is that we need to prepend every command with `q.` so it’s recognized as a Fauna’s method.

After creating the user, the API logs the user and returns the login token

```javascript
31	const loginRes = await serverClient.query(
32	  q.Login(user.ref, {
33	  password,
34	})
35	)
```

The secret returned is very similar to the Server Key we created on the first chapter. We need to save it on a cookie so the user keeps it when navigating through the website.

```javascript
41    const cookieSerialized = serializeFaunaCookie(loginRes.secret)
42
43    res.setHeader('Set-Cookie', cookieSerialized)
```

As you can see, we needed to call `serverClient` twice, the first time to create the user and the second time to log into the account. Even if two queries doesn’t look like a lot of calls, the user creation API is a good point to start using User Defined Functions (UDF), so, why don’t we try to make it a single database query to reduce latency on these requests? After this, we can understand how to do the same on larger functions that require an important amount of reads/writes. 

#Start using User Defined Functions (UDF) to improve your site's performance

[Fauna](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_SocialMediaDatabase_SJamie) allows you to create your own functions using the FQL methods described [here] (https://docs.fauna.com/fauna/current/api/fql/cheat_sheet), don’t panic, you don’t have to learn them all just yet. Most of the time we will be using `Match`, `Paginate`, `Select` and `Get` methods to get all the documents we require. Let’s now navigate to [Fauna](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_SocialMediaDatabase_SJamie) and create some simple functions.

![create new function on Fauna, part 1](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/fauna-new-function_TpAKX9HLt6.png)

Let’s open our database from the Fauna’s dashboard, open the functions menu and click on _New Function_. Usually Fauna gives you a short example function’s body by default, it goes:

```javascript
Query(
  Lambda(
    "x", Add(Var("x"), Var("x"))
  )
)
```

Let’s explain it by parts:
- Query: it’s only parameter is a lambda function and its purpose is to prevent the lambda function for immediate execution. It encases the function definition.
- Lambda: this method has two parameters, the first one is the set of arguments the function can get (in this case, x), and the second one is the lambda function, which means the commands we will execute. All argument names should be strings, also, if you need to use more than one parameter, you should put all names in an array (e.g [“x”,”y”]).
- Add: In the example code provided by Fauna, they use `Add` as the only method used, this returns the arithmetic sum of all the arguments. However, we will change this part to login the user.
- Var: Everytime we make a reference to an existing variable, we need to call the method `Var` and put as argument the name of the variable as a **string**.

Ok, the functional part is `Lambda`, so let’s transform the default function’s body and make it a function for creating users.

```javascript
Query(
  Lambda(
    ["email","password"],
    Let(
      {
        user:Create( //create the user
          Collection("Users"),
          { 
            credentials: { password: Var("password") },
            data: {
              email: Var("email"),
              posts: 0,
              activeSince: Now()
            }
        }),
        userRef: Select(
          "ref",
          Var("user")
        ),
      },
      Login(Var("userRef"), {
        password: Var("password"),
        data: {
          message: "first Login"
        }
      })
    )
  )
)
```

This time, I changed the parameters in the Lambda function to show you how to put more than one variable. In this case, _email_ is the user’s email and _password_ is the user’s password.
The method `Let` allows you to create an object with temporal variables (represented as the object’s fields) and use them in the second argument by calling the method `Var`. We create a field named _user_ and define it as the response for creating a new user on the Users collection with the data provided and some additional fields (for reference). The response of `Create` is the created document.

We also create a field called _userRef_ in which we Select the field _ref_ of our newly created user (this is equivalent to using `user.ref` in javascript). After defining our binding variables, we set the second parameter of `Let` to `Login` the user, this means,  the `Let` method will return the result of Login.
When you login a user, you can provide additional data, we did put a field called _message_ and put the string _first login_. You can be more creative and include relevant information for metrics like user’s IP, language, web browser, etc.

Let's name our function as `signupUser` and save it.

Next step: call this function from the signup API on our local repository.
If you don't have this repository yet, you can create it with the _create-next-app. Just run this from your command shell

`npx create-next-app --example with-cookie-auth-fauna with-cookie-auth-fauna-app`
or
`yarn create next-app --example with-cookie-auth-fauna with-cookie-auth-fauna-app`

![Update Signup API on local app to call new function instead of executing methods](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Visual-update-API_abOZ6Yfib.png)

Replace the method in the first `serverClient`query to 
```javascript
q.Call(q.Function('signupUser'), [ password , email ])
```

This function will return the result from `Login` the user, thus, the second query is unnecessary and you can delete it.

![Remove the section using the Query method for Login](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Visual-remove-login_AOAQ0Ar0q.png)

When we test it, we should have the same behaviour we had before adding the UDF.

What did we achieve with this? On the API, we reduced the queries to Fauna from 2 to 1, reducing some lines of code. On the database, we reduced the data sent and received by performing both processes on the same query, we didn’t need to receive any information to perform the `Login` as we used the `Let` method.
If we have additional processes like adding tasks, roles, historic data, etc. we will have even better performance when we use UDFs.

In this case it doesn’t seem as much, but when we start expanding our database, we will have more efficiency by having UDFs vs performing many database queries.

You can make these functions available in your repository by adding them to a setup file. This way, when you are setting up a similar database for another server, you can recreate the same structure with just a single command. If you already have some experience with Next.js, you can adapt [this example](https://github.com/fauna-brecht/fwitter/blob/master/src/fauna/setup) from Fauna’s developer team. Otherwise, wait until Chapter 3 when we will summarize all our progress in a setup script for Javascript that we will be able to track on your repository of choice.

#Use UDF to follow users and post contents with less connection requests

##Setting-up: Following relationships

We have the methods for creating new users, but there’s not a lot we can do with that. It’s time to add _following_ relationships between our users. In this series, we will use a _follower/followee_ relationship in which a user can follow another, this is not necessarily reciprocal.

Use the [Fauna's dasboard](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_SocialMediaDatabase_SJamie) to create a new collection and name it _Followers_, leave the other fields with their default values. Create two new indexes for your  new collection, name them `followers_by_followee` and `followees_by_follower`. We will make the first index return all the followers of an user and the later index will return the people a user is following (followees). Let’s also make a third index called `is_followee_of_user`, with this, we can find if a user is already following another one and make unique the document related to the following condition.

1. Create _Followers_ collection:
![Fauna Dashboard: Create a new collection named Followers](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Fauna-followers-collection_8cE2ZlXjL.png)
Also, you can do it from the Fauna’s shell using the method [CreateCollection] (https://docs.fauna.com/fauna/current/api/fql/functions/createcollection?lang=javascript):
`CreateCollection({name:”Followers”}) `

2. Create Index _followers_by_followee_:
![Fauna Dashboard: create new index named followers_by_followee](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/fauna-index-followers-by-followee_lQusBDtoC.png)
Create it from the Fauna’s shell using [CreateIndex] (https://docs.fauna.com/fauna/current/api/fql/functions/createindex?lang=javascript):
```javascript
CreateIndex({
  name: "followers_by_followee",
  unique: false,
  serialized: true,
  source: Collection("Followers"),
  terms: [
    {
      field: ["data", "followee"]
    }
  ],
  values: [
    {
      field: ["data", "follower"]
    }
  ]
})
```

3. Create Index _followees_by_follower_:
![Fauna Dashboard: Create new index named followees_by_follower](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/fauna-index-followees-by-follower_1Bog9IVqk.png)

And with Fauna Shell: 
```javascript
CreateIndex({
  name: "followees_by_follower",
  unique: false,
  serialized: true,
  source: Collection("Followers"),
  terms: [
    {
      field: ["data", "follower"]
    }
  ],
  values: [
    {
      field: ["data", "followee"]
    }
  ]
})
```

4. Create the index _is_followee_of_user_:
![Fauna Dashboard: Create new index named is_followee_of_user](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Fauna-index-is-following-user_x1eGFrWh5.png)

With this, we prepared our database to handle follower/followee relationships between all users, now, let’s prepare to post content.

##Setting-up: User’s posts

Create a new collection and name it _Posts_. At this point, I think you’ve already got the hang of it.
Create an index for this collection and name it _posts_by_owner_. This index will have the field `owner` as term and the value field will be empty.

![Create index named posts_by_owner](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/fauna-index-post-by-owner_fpLE6O_WC.png)

Now, our database can contain something more than users. Let’s create some functions to follow users and to create posts.

##Follow users

```javascript
CreateFunction({
  name: ‘followUsers’
  role: null,
  body: Query(
    Lambda(
      "followee",
      If(
        IsEmpty(
          Match(Index("is_followee_of_user"), [Identity(), Var("followee")])
        ),
        Do(
          Create(Collection("Followers"), {
            data: { follower: Identity(), followee: Var("followee") }
          }),
          { isFollowing: true }
        ),
        Do(
          Delete(
            Select(
              ["data", 0],
              Paginate(
                Match(Index("is_followee_of_user"), [
                  Identity(),
                  Var("followee")
                ])
              )
            )
          ),
          { isFollowing: false }
        )
      )
    )
  )
})
```

This function toggles the follow/unfollow state of the users. If you already follow a user, you’ll stop following it, if you are not a follower, you’ll become one. Also, this function returns the new following status as `true` or `false`.

##Create Post
```javascript
CreateFunction({
  name: "createPost",
  role: null,
  body: Query(
    Lambda(
      "description",
      Create(Collection("Posts"), {
        data: {
          description: Var("description"),
          date: Now(),
          owner: Identity(),
          likes: 0,
          comments: 0
        }
      })
    )
  )
})
```

With this function, you can create a new post and put initial values like the date it was posted as well as set the amount of likes and comments to 0.

##List Users

```javascript
CreateFunction({
  name: "listUsers",
  role: null,
  body: Query(
    Lambda(
      "cursor",
      Map(
        Paginate(Reverse(Documents(Collection("Users"))), {
          after: Var("cursor")
        }),
        Lambda("ref", {
          userId: Select("id", Var("ref")),
          isFollowee: IsNonEmpty(
            Match(Index("is_followee_of_user"), [Identity(), Var("ref")])
          ),
        isSelf: Equals(Identity(), Var("ref"))
        })
      )
    )
  )
})
```

This function brings all the users, due to the nature of Paginate, every function call will return a page of 64 documents by default, in case we need the next page of 64 users, we can send a _cursor_ variable containing the ref of the last user from the last result. Also, we can change the size of every page as we need. The response will contain a field called _data_ which is an array of objects containing the fields _userId_ (a string with the reference of the user), _isFollowee_ (a boolean stating if you are following this user), and _isSelf_ (a boolean indicating whether this user is you).

We’ve got several functions and indexes, but our users have permissions to none of them, all they can do is get their own user id. Let’s use the Fauna dashboard and the hints they provide to help us set the permissions for everyone.

First, let’s get to the **manage roles** section:
![Fauna Dashboard: Create a new role under the Security / manage roles option](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Fauna-new-role_4XXsEWhkE.png)

Click on **__new custom role__** and name it _basicUser_, then start adding the collections and functions, add everything except the index called _users_by_email_ and the function called _signupUser_.

![Fauna Dashboard: Selecting a collection to add permits](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Fauna-new-role-2_wTuql__Bq.png)

Fauna’s ABAC (Atribute-Based Access Control) will grant the documents of a collection all the permits that you grant. An authenticated document (in this case user) can have one or more roles, if any role grants permission to perform a certain action, the action will be performed when required.

After you finish adding your collections and indexes, you should see this:

![Fauna Dashboard: permissions view after adding all required indexes and collections](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Fauna-new-role-3_6fRu5wJEK.png)

Each row represents a collection, index or function. Each column stands for an action. 
+Over the Collections your actions are _Read_ / _Write_(update) / _Create_ / _Delete_ / _Read History_ / _Write on History_ / _Unrestricted_ (do all)
+Over the indexes, you can _Read_ / _Unrestricted_ access (read all index's records, even for documents you can’t directly access)
+You can _Call_ functions

Now, let’s grant the permissions for these items, click on the red X to turn it into a green checkmark on the next items:
+*Read* permissions for collections *Users, Followers, Posts*.
+*Create* permissions on *Followers* and *Posts* (we will change that later).
+*Delete* permissions on *Followers*.
+*Read* permissions on all indexes.
+*Call* permissions on all functions.

Finally, let’s click on the **Membership** tab on the upper side of the window to define who will have the permissions we’ve just set.

Select the *Users* collection as the users will be the ones with these permissions, now you can click on the new Row with the name Users to set a predicate function. In this case, all users will have permissions, so let’s just make the function return `true` all the time.

![Fauna Dashboard: Predicate function for the Users collection, make it return true](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Fauna-new-role-4_cWaCbEUQ7.png)

It’s done. Click save and we are ready to test our new functions using Next.js

For this, let’s clone this repository
[https://github.com/Sertge/fauna-example](https://github.com/Sertge/fauna-example)

In this repository, we updated the example from Fauna in this address
[https://github.com/vercel/next.js/tree/canary/examples/with-cookie-auth-fauna](https://github.com/vercel/next.js/tree/canary/examples/with-cookie-auth-fauna)

To include the APIs that will call the functions we’ve just created. When you’re done cloning, add your Fauna’s server key to the environment variable, input the command `npm install` or `yarn`, and then `npm run dev` or `yarn dev` to run your local server.

When you clone this repository and run it, you should see some additional options in your header

![New navigation bar on project, it contains new options: Users, New Post, Feed](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/chrome-new-header_Dsq3alsvy.png)

From _Signup_, create some new users and click on the _Users_ option from the header.
Here, you can see a list of all users on the database and will have the option to follow/unfollow them, also, you can follow yourself.

![Navigation: Users tab shows the users](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/page-list-users_8Xo2NnL77.png)

And finally, you can add some posts and see them on the database from the tab called _New post_

![Navigation: Add a post](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/page-new-post_3myvJeczA.png)

![Fauna Dashboard: See the new post from the Posts collection](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/fauna-new-post_9vdejZqyJ.png)

Great, now we can create some follower/followee relationships, see which users we are following as well as the ones we aren’t and we can create some posts, all of this by using Fauna’s UDF. In the next section, we will harvest even more power from Fauna.

#Interact with your followers, have them like and comment your posts

On a social network, we want to have our wall/feed populated with the users we follow and keep up to date with them. Also, we want to let them know we admire their work by liking or commenting on their posts. It’s time to add a collection called _Likes_ and another one called _Comments_ as well as create the functions to post comments, toggle the like/unlike status on a post, and finally, grant permissions to our users so they can do all of this.

+Use the dashboard to create a new collection and name it _Likes_ or use `CreateCollection({name:’Likes’})` on the Shell.
+Create a new collection and name it _Comments_.
+Create a new Index for the collection _Likes_ and name it _likes_by_post_id_, use the field **postId** as search term.

![Fauna Dashboard: Creating the index likes_by_post_id](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Fauna-new-index-likes-by-post-id_vYBmWZuid.png)

You can also run the command on the shell
```javascript
CreateIndex({
  name: "likes_by_post_id",
  unique: false,
  serialized: true,
  source: Collection("Likes"),
  terms: [
    {
      field: ["data", "postId"]
    }
  ]
})
```
+Create another index for the _Comments_ collection and name it _comments_by_post_id_, it's the same as the previous step, just change the collection and the name.
+Create an index for the _Likes_ collection called _is_liked_by_user_ and use the _owner_ and the _postId_ as terms. Make it **unique** by marking the _Unique_ field, so a user can only like a post once.

![Fauna Dashboard: a new index called is_liked_by_user](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/Fauna-new-index-is-liked-by-user_YjwfxZJMq.png)

Let’s also add a function to post comments and name it _postComment_
```javascript
CreateFunction({
  "name": "postComment",
  "role": null,
  "body": Query(
    Lambda(
      ["postId", "description"],
      Create(Collection("Comments"), {
        data: {
          owner: Identity(),
          date: Now(),
          likes: 0,
          description: Var("description"),
          postId: Var("postId")
        }
      })
    )
  )
})
```

A function to toggle the like/unlike status on the post
```javascript
CreateFunction({
  name: "toggleLike",
  role: null,
  body: Query(
    Lambda(
      "postId",
      If(
        IsEmpty(Match(Index("is_liked_by_user"), [Identity(), Var("postId")])),
        Do(
          Create(Collection("Likes"), {
            data: { owner: Identity(), postId: Var("postId"), date: Now() }
          }),
          { isFollowing: true }
        ),
        Do(
          Delete(
            Select(
              ["data", 0],
              Paginate(
                Match(Index("is_liked_by_user"), [Identity(), Var("postId")])
              )
            )
          ),
          { isFollowing: false }
        )
      )
    )
  )
})
```

A function to get the posts of all the users you follow (feed):
```javascript
CreateFunction({
  name: "getFeed",
  role: null,
  body: Query(
    Lambda(
      "cursor",
      Map(
        Paginate(
          Reverse(
            Join(
              Match(Index("followees_by_follower"), Identity()),
              Index("posts_by_owner")
            )
          )
        ),
        Lambda(
          "post",
          Merge(Select("data", Get(Var("post"))), {
            isLiked: IsNonEmpty(
              Match(Index("is_liked_by_user"), [Identity(), Var("post")])
            ),
            postId: Var("post"),
            userIsOwner: Equals(
              Identity(),
              Select(["data", "owner"], Get(Var("post")))
            )
          })
        )
      )
    )
  )
})
```

And finally, a function to get the comments from a post:
```javascript
CreateFunction({
  name: "getComments",
  role: null,
  body: Query(
    Lambda(
      "postId",
      Map(
        Paginate(Match(Index("comments_by_post_id"), Var("postId"))),
        Lambda(
          "comment",
          Merge(Select("data", Var("comment")), {
            isLiked: IsNonEmpty(
              Match(Index("is_liked_by_user"), [Identity(), Var("comment")])
            ),
            commentId: Var("comment"),
            userIsOwner: Equals(
              Identity(),
              Select(["data", "owner"], Get(Var("comment")))
            )
          })
        )
      )
    )
  )
})
```

We’re almost there, it’s time to add the permissions so our regular users are able to use these collections, indexes and functions. Add the following permissions:

+Likes: Read, Create, Delete.
+Comments: Read, Create
+All new indexes: Read
+All new functions: Call

Now, you can head to the feed and get all the posts your followees have posted:

![Navigation: Feed page with some posts](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/page-feed_p96gLrmvI.png)

Click on any post to open it and see all the comments:
![Navigation: A post with its comments, it includes the user's Id](https://ik.imagekit.io/z9wrsfvqnb/FaunaForum/page-post-variable_pStabQlnW.png)

Now, we have some of the functions a small social network requires to work. In the next chapter, we will display more information, add the stories, chats, and use [Fauna's](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_SocialMediaDatabase_SJamie) ABAC to limit the user’s actions, giving us some control within our application. Also, we will learn how to create ABAC predicate functions and debug them.