FaunaDB Basics
FaunaDB is a next-generation cloud database that combines the simplicity of NoSQL, without sacrificing the ability to model complex relationships. It’s completely serverless, fast, ACID-compliant, and has a generous free tier for small apps - basically everything you could possibly want in a fully-managed database.
The following lesson demonstrates how to model a basic twitter-inspired social graph with FaunaDB and access it on the web with Node.js. It will introduce you to Fauna Query Language (FQL) and common patterns for reading and writing to the database.
Initial Setup
Start by creating a node project, then install the FaunaDB JS package and Express.
npm init -y
npm install faunadb express
Initialize FaunaDB
Initialize the client with your server key, then import the FQL functions required for this demo.
const faunadb = require('faunadb');
const client = new faunadb.Client({ secret: 'YOUR-KEY' })
// FQL functions
const {
Ref,
Paginate,
Get,
Match,
Select,
Index,
Create,
Collection,
Join,
Call,
Function: Fn,
} = faunadb.query;
Initialize Express
We’ll use Express to serve the API.
const app = require('express')();
app.listen(5000, () => console.log('API on http://localhost:5000'))
At this point, I would recommend using an API client like Insomnia to make requests to your API on http://localhost:5000.
Database Structure
The database contains three collections - users
, tweets
, and relationships
. Create these collections from the Fauna dashboard.
Users and Tweets
In the following section, we will create a variety of API endpoints for reading and writing tweets to Fauna. A user has many tweets, a tweet belongs to a user.
Create a Tweet
First, go to the Fauna dashboard, and create a document with the name of fireship_dev
. Our goal is to associate many tweets to this user account.
Next, create an index to search for a user based on the username.
A one-to-many relationship can be established by retrieving the user document Ref
, then using it as the data with the Create
function.
app.post('/tweet', async (req, res) => {
const data = {
user: Select('ref', Get(Match(Index('users_by_name'), 'fireship_dev'))),
text: 'Hello world!'
}
const doc = await client.query(
Create(
Collection('tweets'),
{ data }
)
)
res.send(doc)
});
Read a Tweet by ID
Reading a document by its ID does not require an index. Use Get
to read a single document by pointing to its collection and reference ID.
app.get('/tweet/:id', async (req, res) => {
const doc = await client.query(
Get(
Ref(
Collection('tweets'),
req.params.id
)
)
)
res.send(doc)
});
Query a User’s Tweets
Reading multiple tweet documents by the user attribute requires an index. Create an index named tweets_by_user
with a search term of user
.
The Paginate
function will return all documents that match the query.
app.get('/tweet', async (req, res) => {
const docs = await client.query(
Paginate(
Match(
Index('tweets_by_user'),
Select('ref', Get(Match(Index('users_by_name'), 'fireship_dev')))
)
)
)
res.send(docs)
});
Fauna Functions
The code presented above duplicates the following line of FQL several times:
Select('ref', Get(Match(Index('users_by_name'), 'fireship_dev')))
Fauna Functions provide a way to extract this logic to the cloud to reduce duplication and improve maintainability.
Create a Function
Extract the duplicated code to a function from the Fauna dashboard. The function accepts the username string as an argument and returns the full user document reference.
Call a Function
Use Call
to execute this function in a query. For example, we can refactor the previous example like so:
const {
Call,
Function: Fn,
} = faunadb.query;
app.get('/tweet', async (req, res) => {
const docs = await client.query(
Paginate(
Match(
Index('tweets_by_user'),
Call(Fn("getUser"), 'fireship_dev')
)
)
)
res.send(docs)
});
User-to-User Relationships
The following section models a social graph where users can follow other users, then query a feed of their tweets.
Create a Relationship
A relationship document contains two user references - the follower and followee.
app.post('/relationship', async (req, res) => {
const data = {
follower: Call(Fn("getUser"), 'bob'),
followee: Call(Fn("getUser"), 'fireship_dev')
}
const doc = await client.query(
Create(
Collection('relationships'),
{ data }
)
)
res.send(doc)
});
Query a Feed of Tweets
After establishing a relationship, you likely want to query tweets from followed users - this will require a Join
.
Create an index named followees_by_follower
with a search term of followee
and a value of follower
.
The Paginate
function will return all followed users, then Join will match the user references to the tweets_by_user
index.
app.get('/feed', async (req, res) => {
const docs = await client.query(
Paginate(
Join(
Match(
Index('followees_by_follower'),
Call(Fn("getUser"), 'bob')
),
Index('tweets_by_user'),
)
)
)
res.send(docs)
});