Create, retrieve, update, and delete documents in Fauna
Fauna allows you to store documents and query them in a relational fashion. This section walks you through a basic example of creating, retrieving, updating, and deleting (CRUD) documents in Fauna, including working with collections. If you are new to Fauna, make sure to check out our Glossary for definitions.
Introduction
To demonstrate how to perform CRUD operations in Fauna, we are going to use the example of blog posts: creating blog posts, updating them with additional attributes, and querying for specific posts.
The steps are:
-
Make sure that the Requirements are met.
We have set up this example so you can follow along from start to finish. Feel free to skip straight to Create a post if you are just looking for examples of the create, retrieve, update, and delete process.
Requirements
This section walks you through setting up your environment, installing a driver, importing the driver, obtaining an admin key, and instantiating the client.
Supported runtimes
Before you install the driver, it’s important to ensure you’re running a compatible version of the language runtime and have satisfied other dependencies.
The JavaScript driver supports and is tested on:
-
Node.js
-
LTS (v4)
-
Stable (v6)
-
v0.12.x
-
-
Chrome
-
Firefox
-
Safari
-
Internet Explorer 11
Install the driver
To install the JavaScript driver, run this in the terminal:
npm install --save faunadb
See faunadb
on NPM for more
information.
The browser release can be found in the
fauna/faunadb-js-release
repository.
This release can be installed via bower:
bower install faunadb
Or via CDN:
<script src="//cdn.jsdelivr.net/gh/fauna/faunadb-js-release@latest/faunadb.js"></script>
The minified version of the driver can also be used via CDN:
<script src="//cdn.jsdelivr.net/gh/fauna/faunadb-js-release@latest/faunadb-min.js"></script>
Import the driver
var faunadb = require('faunadb'),
q = faunadb.query;
This is the recommended require stanza. The faunadb.query
module
contains all of the functions to create Fauna Query expressions.
Similarly with es6 modules:
import faunadb, { query as q } from "faunadb"
The CDN package exposes a global faunadb
variable.
Create a database
Create a database called my_app
in Fauna:
adminClient.query(
q.CreateDatabase({ name: 'my_app' })
)
.then((ret) => console.log(ret))
{ ref: Ref(id=my_app, collection=Ref(id=databases)),
ts: 1528127545620042,
name: 'my_app' }
Create a server key to access the my_app
database
Create a server key. The server key has unrestricted access to a single
database; in this case, the server key allows access only to the
my_app
database that we just created.
adminClient.query(
q.CreateKey({
database: q.Database('my_app'),
role: 'server',
})
)
.then((ret) => console.log(ret))
{ ref: Ref(id=201188951594107392, collection=Ref(id=keys)),
ts: 1528127585813144,
database: Ref(id=my_app, collection=Ref(id=databases)),
role: 'server',
secret: 'fnACysRJGIACAHiL_5f0UxHlPFIZgq876ptMNJ72',
hashed_secret: '$2a$05$wm6Zwl8dhlY3hFbJj0JrHuSt4YszzsDRd9KdxVbxg98yVOMJiEp0i' }
Save this key’s secret; it is only displayed once, and if you lose it, you would have to generate a new one. The key is used to perform all of the remaining database setup steps.
Your key will be different than the key you see displayed in the command output in this documentation. The key you saved just above will be different. |
Instantiate a client that has server key privileges
Instantiate a client that uses the server key that we just set up, to
perform the rest of the tasks in this tutorial. Be sure to copy the
secret returned in the previous step and replace
YOUR_FAUNADB_SERVER_SECRET
with that value.
var serverClient = new faunadb.Client({ secret: 'YOUR_FAUNADB_SERVER_SECRET' });
Set up a collection
Fauna stores data in the form of nested containers. A database contains collections, and collections contain documents. Each document belongs to a specific collection. So in order to create a document for a post, we need to first create a collection for posts.
Create a collection using the CreateCollection
function with a
param_object
containing the name
of the collection. We shall name
our collection "posts":
serverClient.query(
q.CreateCollection({ name: 'posts' })
)
.then((ret) => console.log(ret))
{ ref: Ref(id=posts, collection=Ref(id=collections)),
ts: 1527349751848648,
history_days: 30,
name: 'posts' }
Create an index
Before we create any document, let’s ensure that we can easily access
them. We do this by creating an index using the CreateIndex
function.
We create this index now to help make the examples clear; in production,
you can create an index at any time.
The customary way to access documents within a collection is by specifying a criteria for one of the fields. To enable criteria-based searches, we need to first create an index using the path of the field within the document.
Create an index on the post’s title:
serverClient.query(
q.CreateIndex({
name: 'posts_by_title',
source: q.Collection('posts'),
terms: [{ field: ['data', 'title'] }],
})
)
.then((ret) => console.log(ret))
{ ref: Ref(id=posts_by_title, collection=Ref(id=indexes)),
ts: 1527350163658503,
active: false,
partitions: 1,
name: 'posts_by_title',
source: Ref(id=posts, collection=Ref(id=collections)),
terms: [ { field: [Array] } ] }
It is also possible to specify the values
of the document that should
be returned when querying the index. We also create an index for a
post’s tags:
serverClient.query(
q.CreateIndex({
name: 'posts_by_tags_with_title',
source: q.Collection('posts'),
terms: [{ field: ['data', 'tags'] }],
values: [{ field: ['data', 'title'] }],
})
)
.then((ret) => console.log(ret))
{ ref: Ref(id=posts_by_tags_with_title, collection=Ref(id=indexes)),
ts: 1527350198235760,
active: false,
partitions: 1,
name: 'posts_by_tags_with_title',
source: Ref(id=posts, collection=Ref(id=collections)),
terms: [ { field: [Array] } ],
values: [ { field: [Array] } ] }
Create a post
Posts are created by calling the Create
function with the ref
of the
posts collection and a param_object
that specifies the structure of
the document to be created, as well as permissions for the document. The
post’s data is included in the param_object
's data
field.
serverClient.query(
q.Create(
q.Collection('posts'),
{ data: { title: 'What I had for breakfast ..' } },
)
)
.then((ret) => console.log(ret))
{ ref:
Ref(id=200372984285757952, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527349418742172,
data: { title: 'What I had for breakfast ..' } }
The Create
function returns the post document just created. As you can
see in the output, the ref
of the document is an
automatically-generated identifier that is unique to the document within
its database.
Using the Ref
function, you can
specify an id to use instead of the auto-generated id, but it must be
unique. For example:
serverClient.query(
q.Create(
q.Ref(q.Collection('posts'), '1'),
{ data: { title: 'The first post' } },
)
)
.then((ret) => console.log(ret))
{
ref: Ref(Collection("posts"), "1"),
ts: 1587595447990000,
data: { title: 'The first post' }
}
Create several posts
It can quickly become tedious to repeat the Create
function for
multiple posts.
This is where Fauna’s transaction language really shines. Let’s use a
Map
function to create several posts at once:
serverClient.query(
q.Map(
[
'My cat and other marvels',
'Pondering during a commute',
'Deep meanings in a latte',
],
q.Lambda(
'post_title',
q.Create(
q.Collection('posts'),
{ data: { title: q.Var('post_title') } },
)
),
)
)
.then((ret) => console.log(ret))
[ { ref:
Ref(id=200373080062689792, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527349510087794,
data: { title: 'My cat and other marvels' } },
{ ref:
Ref(id=200373080062690816, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527349510087794,
data: { title: 'Pondering during a commute' } },
{ ref:
Ref(id=200373080062691840, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527349510087794,
data: { title: 'Deep meanings in a latte' } } ]
Using the Map
function, we can restructure the data as an array and
wrap the Create
in a lambda function, which then runs over each
document in the collection. The anonymous lambda function specifies a
variable post_title
which is used as a placeholder in the parameters
sent to the create
function. This way, multiple documents in a
collection can be created using a single query.
Retrieve posts
The easiest way to retrieve a document is by using its reference value:
serverClient.query(
q.Get(q.Ref(q.Collection('posts'), '192903209792046592'))
)
.then((ret) => console.log(ret))
{ ref:
Ref(id=192903209792046592, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527350638301882,
data: { title: 'My cat and other marvels' } }
You can query for posts with a specific title using the match
function
and the index we created earlier:
serverClient.query(
q.Get(
q.Match(q.Index('posts_by_title'), 'My cat and other marvels')
)
)
.then((ret) => console.log(ret))
{ ref:
Ref(id=192903209792046592, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527088583491065,
data: { title: 'My cat and other marvels' } }
The match
function returns a logical set of elements, which can be
combined with other sets with set-operations like join
, intersect
,
subtract
, etc.
Update posts
You can easily modify documents by supplying the new data along with the reference to the document. For example, we want to add tags to each of our blog posts:
serverClient.query(
q.Update(
q.Ref(q.Collection('posts'), '192903209792046592'),
{ data: { tags: ['pet', 'cute'] } },
)
)
.then((ret) => console.log(ret))
{ ref:
Ref(id=192903209792046592, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527350534706104,
data:
{ title: 'My cat and other marvels', tags: [ 'pet', 'cute' ] } }
The Update
function updates specific fields in a document. It
preserves the old fields if they are not specified in params
. In the
case of nested values (known as objects, due to the JSON data format),
the old and the new values are merged. If null
is specified as a value
for a field, it is removed.
Replace posts
The Replace
function replaces the document’s data with the fields
provided in params
. Old fields not mentioned in params
are removed.
serverClient.query(
q.Replace(
q.Ref(q.Collection('posts'), '192903209792046592'),
{ data: { title: 'My dog and other marvels' } },
)
)
.then((ret) => console.log(ret))
{ ref:
Ref(id=192903209792046592, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527350638301882,
data: { title: 'My dog and other marvels' } }
Note that the title
has been updated, but tags
has been deleted.
Delete a post
Lastly, a post can be removed using the Delete
function:
serverClient.query(
q.Delete(
q.Ref(q.Collection('posts'), '192903209792045568')
)
)
.then((ret) => console.log(ret))
{ ref:
Ref(id=192903209792045568, collection=Ref(id=posts, collection=Ref(id=collections))),
ts: 1527349510087794,
data: { title: 'Deep meanings in a latte' } }
serverClient.query(
q.Get(q.Ref(q.Collection('posts'), '192903209792045568'))
)
.then((ret) => console.log(ret))
.catch((ret) => console.log(ret))
[NotFound: document not found]
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!