Fauna and FQL for Firestore users
AsyncBanana
Posted on October 30, 2021
Fauna and Firestore are very similar databases, both offering instant scalability and fairly easy ways of querying, but there are still some key differences. In this article we will look at those differences and how to migrate from Firestore to Fauna and Fauna’s query language (FQL). You might want to migrate because of Fauna’s querying language, flexibility, or multi region capabilities. Even if you don’t know whether you want to migrate yet, this article will provide some information on the differences.
Background
Firestore is a database created by Google in 2017. It is the successor to the two previous databases, Firebase Realtime DB and Cloud Datastore. It is grouped within the Firebase category group, offering integration with different Firebase services, although it is also offered through Google Cloud. Firestore’s primary selling points are that it is easy to use, even without a server, and has real-time capabilities. It also offers simple pricing based on reads, data storage, and writes.
Fauna is a database developed in 2012 by a few ex-Twitter engineers to solve scalability problems with existing NoSQL databases. Fauna offers a simple query language called FQL, along with a GraphQL service if you do not want to learn another query language. Fauna also can be used easily without a backend and is focused on doing that while remaining as scalable as possible.
Firestore and Fauna have a number of similar features and a few key differences. They both abstract over cloud machines to provide a simple way of using them and even abstract over cross region replication. They also both offer client side SDK’s and real-time capabilities. Their query languages are a bit different, as Fauna has a more comprehensive setup that allows you to do certain computations on the database through their query language. This can increase speeds by mitigating the need for round trip queries. Firestore’s query language primarily consists of CRUD functions. Another key difference is that Fauna is more flexible with querying capabilities and scalability. Now that you know the background of each database and how they differ, let's go more in depth.
Comparison
Query Language
Now that we’ve highlighted a few differences in the query languages, let’s look more closely at those differences. With Firestore, there are two different styles of querying data, function chaining and data passing via parameters. Before the V9 update, Firestore’s SDK primarily used function chaining. For example, a basic read query might look like this:
const data = (await db.collection("cities").doc("SF").get()).data()
The example above accesses the main database object, then the collection and document by chaining the method functions. That is still how it works if you use the server side SDK. However, in a more recent version of the client-side web SDK, there is a way to query by passing options as parameters instead.
const data = (await getDoc(doc(db, "collectionName", "documentName"))).data()
Fauna only has one universal SDK and one querying style, which resembles a more powerful version of the Firestore V9 SDK. To perform operations you nest different functions within other functions. For example, to read a document you can do this:
const data = (await db.query(
q.Get(q.Ref(q.Collection('collectionName'), 'documentName'))
)).data
Each function is namespaced under q
. There are many more than just the ones used in that example. For a full list, you can look at Fauna’s cheat sheet. Fauna also provides a GraphQL API as mentioned before, but I will not go over that for brevity.
Both query languages are pretty easy to use and you can do most things you need with both, although Fauna’s offers more ways of running things on the database to prevent roundtrips.
Indexes
Indexes are one place where Firestore and Fauna differ quite a bit. Firestore opts for an approach where it automatically creates indexes for all fields in a document, and allows for querying using those indexes under the hood without worrying about them. For indexes that include multiple fields, you have to manually create a compound query. This approach makes it easy to query but can end up costing the user more, as Firestore creates indexes even when they are not needed, which takes more storage space.
In contrast, Fauna makes you manually create indexes, although it offers an easy to use dashboard and FQL function for creating indexes. Fauna also offers more capabilities for indexes, like cross collection indexes.
Transactions & Consistency
Both Firestore and Fauna offer strongly consistent database operations. Fauna uses Calvin, which allows it to replicate data globally with strong consistency. Firestore does not use as advanced of a replication system, although it still does offer strong consistency. The biggest difference for consistent transactions is that as mentioned before, Fauna allows you to do more things on the database directly. This makes it so that you can reduce time when data could change in the process of your server responding to data. So Fauna is definitely better in this case in terms of reliably running consistent transactions out of the box, but both work fairly well in this case.
Pricing
Pricing for Firestore and Fauna are similar in some ways, but very different in others. They both price by reads, writes, and storage, although Fauna also prices compute operations, which are operations done on their servers. Both offer free tiers, so you can try either without paying anything.
Firestore costs pricing varies based on the region you are using. For a typical single region configuration, Firestore costs $0.36 per million documents read, $1.08 per million documents written to, and $0.108 per gigabyte of storage. For a multi region configuration, Firestore costs $0.6 per million documents read, $1.80 per million documents written to, and $0.18 per gigabyte of storage.
Because Fauna is global by default, it has more consistent pricing, although Fauna multiplies all costs by 1.2x if you are storing all data in Europe and 1.1x if you are storing some in Europe. Fauna costs $0.45 per million documents read, $2.25 per million documents written to, $2.03 per million compute operations, and $0.23 per gigabyte of storage.
As you can see from above, their costs are fairly close. Fauna can be more expensive on the surface level, but it is only sometimes more expensive. This is because Fauna offers features like built in compression and often cheaper querying. Ultimately you will need to do more research based on your own needs to figure out which is cheaper.
Limits
Both databases have some limits for throughput and size. For Firestore, there are multiple limits on documents. One is that documents must be 1 megabyte or less, and can not be changed more than once per second. Another is that no more than 10,000 write operations can be executed on the database per second, and those write operations cannot collectively contain more than 10 megabytes of data. Also, nested data in document maps have a max depth of 20, although you can get around that by turning it into a JSON string. There are many other limits shown on their Limits page, but those are the most relevant for most people. Fauna also has limits, albeit less. Document sizes are capped at 8 megabytes, transaction sizes are capped at 16 megabytes, and index entries have to stay below 64,000 bytes. For more information, you can look at their Limits page.
Comparison Conclusion
Fauna and Firestore are very related in many ways, but differ importantly in others. Both are easy to use, although Fauna can be more powerful. If you have previously used Firestore and are interested in Fauna, read on for an intro to FQL (Fauna Query Language) designed for previous Firestore users.
Intro to FQL from Firestore
As you saw in the query language section of the comparison, there can be quite a few differences between the two database's way of querying, especially if you are used to the used to the function chaining Firestore SDK. Luckily, FQL is fairly intuitive, so you should not have much trouble learning it. A basic FQL query is just db.query()
with nothing passed to it. To do things with queries, you pass different functions to the query function, like q.Get
or q.Create
, and to pass the right information to those operations like what document, you nest functions like q.Ref
and q.Collection
. This might sound verbose for someone used to someone used to Firestore’s way of just running .get()
on a document, but it can be more elegant, and you can do more.
Creating Documents
Fauna offers a function q.Create
that allows for creating documents like Firestore’s add
function. You simple pass a collection and data and it will return a reference to the new document as well as the new document’s data.
const result = await db.query(
q.Create(
q.Collection("collectionName"),
{
data: {
field: "fieldValue",
fruit: "bananas"
},
},
)
)
// Get reference to result
console.log(result.ref)
// Get new result data
console.log(result.data)
The equivalent with Firestore would be
const result = await addDoc(collection(db, "collectionName"), {
field: "fieldValue",
fruit: "bananas"
});
// Get reference to result
console.log(result);
This works for creating a document without specifying an ID, but you can also update an existing document by using q.Ref
with a collection and id instead of q.Collection
.
Getting Documents
This is pretty simple to implement if you just are getting a document by ID and collection name.
const data = (await db.query(
q.Get(q.Ref(q.Collection("collectionName"), "documentName"))
)).data
As you can see, there is a query function that contains a get query with a ref passed to it. This is equivalent to
const data = (await db.collection("collectionName").doc("documentName").get()).data()
or
const data = (await getDoc(doc(db, "collectionName", "documentName"))).data()
Setting/Updating Documents
Fauna offers two built in methods for this, q.Replace
for destructive replacing and q.Update
for updating specific fields without changing the whole document. This equates to the Firestore set
and update
functions.
await db.query(
q.Replace(
q.Ref(q.Collection("collectionName"), "documentName"),
{
data: {
field: "fieldValue", fruit: "still bananas"
}
}
)
)
await db.query(
q.Update(
q.Ref(q.Collection("collectionName"), "documentName"),
{
data: {
fruit: "still bananas"
}
}
)
)
This is equivalent to
// v8 web or Node
await db.collection("collectionName").doc("documentName").set({
field: "fieldValue",
fruit: "still bananas"
});
await db.collection("collectionName").doc("documentName").update({
fruit: "still bananas"
});
// v9 web
await setDoc(doc(db, "collectionName", "documentName"), {
field: "fieldValue",
fruit: "still bananas"
});
await updateDoc(doc(db, "collectionName", "documentName"), {
fruit: "still bananas"
});
Currently if you want to emulate the behavior of set with merge: true which creates a document if it does not exist yet, you can use run a query that checks if a document exists and creates it if it does not using q.If
.
Deleting Documents
Deleting a document with Fauna is just like getting a document, but with q.Delete
instead of q.Get
.
await client.query(
q.Delete(q.Ref(q.Collection("collectionName"), "documentName"))
);
This equates to
// v8 web or node
await db.collection("collectionName").doc("documentName").delete()
// v9 web
await updateDoc(doc(db, "collectionName", "documentName"))
Conclusion
Fauna is a great alternative to Firestore with a more powerful query API and other advantages. They are very similar databases, and it is easy to migrate to Fauna from Firestore. I hope this article has enlightened you on the differences between Fauna and Firestore, as well as how FQL code translates to Firestore code, and thanks for reading.
Posted on October 30, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.