Fauna tour

Fauna is a true serverless database that combines document flexibility with native relational capabilities, offering auto-scaling, multi-active replication, and HTTPS connectivity.

This tour guides you through the process of building a basic JavaScript app with Fauna, highlighting Fauna’s unique features and developer experience.

Prefer to learn using a sample application? Check out the driver sample apps on GitHub:

Distributed, serverless architecture

Fauna databases are easy to set up and fully managed — no provisioning, capacity planning, or operational overhead required.

Create a database

To kick things off, create a database for your app:

  1. Log in to the Fauna Dashboard. You can sign up for a free account at https://dashboard.fauna.com/register.

  2. Create a database with the following configuration:

    • Region Group: Choose your preferred region group. The Region Group determines the geographic regions in which your data resides.

    • Use demo data: Enabled. This option populates the database with sample e-commerce data and schema we’ll use later in the tour.

    • Enable backups: Disabled.

      For a production database, use this feature to automatically create daily backups that you can restore in case of operator errors, such as accidental deletion.

    Leave the other options as is.

That’s it! Your database is instantly allocated and ready for queries.

Your database’s data is automatically replicated across regions in its region group, ensuring high availability and resilience to outages.

Virtual Private Fauna (VPF)

For enhanced security and customization, Fauna offers Virtual Private Fauna (VPF). This option provides single-tenant deployments on dedicated infrastructure with a customizable regional footprint across multiple cloud providers.

VPF is designed to meet specific security and compliance requirements for organizations with more stringent needs.

API delivery model

In Fauna, every query is a transaction, ensuring ACID compliance across all operations, even in globally distributed Region Groups.

Fauna uses stateless, token-based authentication. Each query is an independently authenticated request to the global Query HTTP API endpoint.

Fauna routes and authorizes queries using an authentication secret, scoped to a specific database. Fauna uses replicas and intelligent routing to ensure queries have low latency.

Create an authentication secret

Fauna supports several types of authentication secrets. For this tour, create a key, which is a type of secret:

  1. In the Dashboard’s Explorer page, select your demo database.

  2. In the database’s Keys tab, click Create Key.

  3. Choose a Role of admin.

  4. Click Save.

    Create a key

  5. Copy the Key Secret. You’ll use the secret later in the tour.

Client drivers

To run queries, your app can work directly with the Fauna Core HTTP API or use a Fauna client driver for your preferred programming language:

 

 

Each driver is a lightweight, open-source wrapper for the HTTP API. They’re maintained by Fauna and lightweight enough for use in serverless functions, edge networks, or other resource-constrained environments.

 

The Fauna client driver model

 

Add Fauna to a Node.js app

Next, create a basic Node.js app using the Fauna JavaScript driver:

  1. Create a directory for the app and install the driver:

    mkdir app
    cd app
    npm install fauna
  2. Set the FAUNA_SECRET environment variable to your key’s secret. Fauna’s client drivers can access the secret from this variable:

    export FAUNA_SECRET=<KEY_SECRET>
  3. Create an app.mjs file and add the following code:

    import { Client, fql } from "fauna";
    
    // Initialize the Fauna client.
    const client = new Client();
    
    // Compose a query using an `fql` template string.
    const query = fql`Product.all() { name, description, price }`;
    
    // Run the query.
    const pages = client.paginate(query);
    // Iterate through the results
    const products = [];
    for await (const product of pages.flatten()) {
      products.push(product)
    }
    console.log(products)
    // Close the client.
    client.close();
  4. Run the app:

    node app.mjs

    The app prints the following data from your demo database:

    [
      {
        name: 'cups',
        description: 'Translucent 9 Oz, 100 ct',
        price: 698
      },
      {
        name: 'donkey pinata',
        description: 'Original Classic Donkey Pinata',
        price: 2499
      },
      ...
    ]

Fauna Query Language

You use Fauna Query Language (FQL) queries to read and write data in a Fauna database.

Fauna stores data as JSON-like documents, organized into collections. Queries can filter and fetch documents from a collection as a set, and iterate through each document.

Run a query in the Dashboard Shell

In addition to your sample app, you can use the Dashboard Shell to run FQL queries for testing or one-off queries.

To test it out, re-run the query from your sample app:

Product.all() { name, description, price }

The query fetches all documents from the Product collection as a Set.

Run an FQL query in the Dashboard Shell

Static typing

FQL is a TypeScript-inspired language and includes optional static typing. You can use typing to catch errors early in development, improving code quality and reducing runtime issues.

Relational queries

FQL also offers a concise, expressive syntax for relational queries, supporting complex joins and data transformations:

// Gets the first customer with
// an email of "alice.appleseed@example.com".
let customer: Any = Customer
                .where(.email == "alice.appleseed@example.com")
                .first()

// Gets the first order, sorted by descending creation time,
// for the customer.
let order: Any = Order
                .where(.customer == customer)
                .order(desc(.createdAt))
                .first()

// Project fields from the order.
// The order contains fields with document references.
// Projecting the fields traverses references,
// similar to a SQL join.
order {
  // `Customer` document reference
  customer {
    name,
    email
  },
  status,
  createdAt,
  items {
    // Nested `Product` document reference
    product {
      name,
      price,
      stock,
      // `Category` document reference
      category {
        name
      }
    },
    quantity
  },
  total
}

User-defined functions (UDFs)

You can save and reuse FQL statements as user-defined functions (UDFs), which works similarly to stored procedures in SQL. UDFs let you encapsulate and store business logic in your database, promoting code reuse and maintaining a clear separation of concerns.

Schema as code

Fauna Schema Language (FSL) lets you define and manage database schema as code. You can manage your database’s FSL schema using the Dashboard or as .fsl files using the Fauna CLI.

Using the CLI lets you:

Manage schema using the Fauna CLI

Fauna’s demo dataset includes FSL schema for several resources. Install the Fauna CLI and pull your demo databases’s schema into your app’s project directory.

  1. Install the Fauna CLI:

    npm install -g fauna-shell
  2. Log in to Fauna using the Fauna CLI:

    fauna cloud-login

    When prompted, enter:

    • Endpoint name: cloud (Press Enter)

    • Email address: The email address for your Fauna account.

    • Password: The password for your Fauna account.

    • Which endpoint would you like to set as default? The cloud-* endpoint for your Region Group. For example, to use the US Region Group, use cloud-us.

    fauna cloud-login requires an email and password login. If you log in to Fauna using GitHub or Netlify, you can enable email and password login using the Forgot Password workflow.

  3. In your project’s root directory, run:

    fauna project init

    When prompted, use:

    • schema as the schema directory. A schema directory stores schema files for the project’s databases. You’ll use this directory later in the tutorial. If the directory doesn’t exist, the command creates it.

    • dev as the environment name.

    • The default endpoint.

    • Your demo database as the database.

  4. Pull your database’s schema into the project directory:

    fauna schema pull

    When prompted, accept and pull the changes.

    The command saves the schema as .fsl files in the schema directory. For example, collections.fsl contains collection schema for the database:

    collection Customer {
      name: String
      email: String
      address: {
        street: String,
        city: String,
        state: String,
        postalCode: String,
        country: String
      }
    
      compute cart: Order? = (customer => Order.byCustomerAndStatus(customer, 'cart').first())
    
      // Use a computed field to get the Set of Orders for a customer.
      compute orders: Set<Order> = ( customer => Order.byCustomer(customer))
    
      // Use a unique constraint to ensure no two customers have the same email.
      unique [.email]
    
      index byEmail {
        terms [.email]
      }
    }
    ...

Change data capture (CDC) and real-time events

Fauna event sources let you build change data capture (CDC) and real-time app features for your app.

An event source tracks changes to a specific Set of documents or document fields, defined by an FQL query. The event source emits an event whenever a tracked change occurs.

An app can consume an event source in two ways:

Event Feeds : Asynchronous requests that poll the event source for paginated events.

Event Streams: Real-time subscriptions that push events from the event source to your application using an open connection to Fauna.

Subscribe to an event stream

Update your app to subscribe to a real-time event stream. As a test, track changes to the demo database’s Customer collection.

  1. Open your app’s app.mjs file and replace the app’s code with the following:

    import { Client, fql } from "fauna";
    
    const client = new Client();
    
    // Create an event source by appending `eventSource()`
    // to a query that returns a supported Set of documents.
    const query = fql`Customer.all().eventSource() { name, email }`;
    
    // Start an Event Stream using the query.
    const stream = client.stream(query);
    
    // Print each event from the stream.
    for await (const event of stream) {
      console.log("Stream event:", event);
    }
    
    client.close();
  2. Run the app:

    node app.mjs

    Event streams require an open connection so leave the app running.

  3. In the Dashboard Shell, run the following FQL query to update a tracked Customer collection document:

    // Creates a `Customer` collection document.
    Customer.create({
      name: "John Doe",
      email: "john.doe@example.com",
      address: {
        street: "87856 Mendota Court",
        city: "Washington",
        state: "District of Columbia",
        postalCode: "20220",
        country: "US"
      }
    })

    When you run the query, the app data from the related event:

    Stream event: {
      type: 'add',
      data: { name: 'John Doe', email: 'john.doe@example.com' },
      txn_ts: 1727127627405000,
      cursor: 'gsGCGmbx4EsaGCPPQAA=',
      stats: {
        read_ops: 1,
        storage_bytes_read: 207,
        compute_ops: 1,
        processing_time_ms: 8,
        rate_limits_hit: []
      }
    }
  4. Press Ctrl+C to stop the app.

Security and access control

Fauna supports both role-based access control (RBAC) for coarse-grained permissions and attribute-based access control (ABAC) for fine-grained, dynamic access rules.

Built-in user authentication

Fauna includes a built-in end-user authentication system called tokens.

Each token is associated with an identity document representing an end user, system, or other identity. A credential associates an end user’s password with the identity document.

With ABAC, you can use FQL predicates to dynamically grant roles and privileges based on the attributes of identity documents, the query, and other context.

Create an end-user authentication token with ABAC

Next, you’ll extend the demo dataset with a role for customer end users. Use the existing Customer collection to store these end user’s identity documents.

Then create a token for a customer and use it to authenticate a query.

  1. In your project’s schema directory, create a roles.fsl file and add the following FSL role schema to it:

    // Defines the customer role.
    role customer {
    
      // Assign the customer role to tokens with
      // an identity document in the Customer collection.
      membership Customer
    
      // Grant read access to the Product collection.
      privileges Product {
        read
      }
    
      // Grant read access to the Category collection.
      privileges Category {
        read
      }
    
      // If the predicate is true,
      // grant `read` access to the Customer collection.
      privileges Customer {
        read {
          // The customer can only access their own
          // Customer identity document.
          // Check that the Customer document
          // is the token's identity document.
          predicate (doc => doc == Query.identity())
        }
      }
    }
  2. Save roles.fsl. Then push the schema to Fauna:

    fauna schema push

    When prompted, accept and stage the schema.

  3. Check the status of the staged schema:

    fauna schema status
  4. When the status is ready, commit the staged schema to the database:

    fauna schema commit

    The commit applies the staged schema to the database. This creates the role.

  5. In the Dashboard Shell, run the following FQL query to:

    • Create a Customer collection that serves as an identity document.

    • Create a credential that associates the identity document with a password.

    • Create a token using the credential.

    let customer = Customer.create({
      name: "Jane Doe",
      email: "jane.doe@example.com",
      address: {
        street: "87856 Mendota Court",
        city: "Washington",
        state: "District of Columbia",
        postalCode: "20220",
        country: "US"
      }
    })
    
    let credentials = Credential.create({
      document: customer,
      password: "SAMPLE_PASSWORD"
    })
    
    credentials.login("SAMPLE_PASSWORD")

    The query returns a token document with a secret. You can use the authentication secret to authenticate requests on behalf of the user.

    {
      id: "409855969129922637", // Auto-generated ID for the token document.
      coll: Token,
      ts: Time("2099-09-23T21:46:01.200Z"),
      document: Customer("409855969112096845"), // Reference for the token's identity document.
      secret: "fn..."
    }
  6. To test out the secret and role, paste it into the Dashboard Shell’s authentication drop-down, selecting Secret.

    Create a key

  7. Then use the secret to run an FQL query that accesses a collection with limited privileges, such as the Customer collection:

    Customer.all() { name, email }

    Based on its role and privileges, the secret can only access the token’s Customer identity document. Other Customer documents are omitted from the results:

    {
      data: [
        {
          name: "Jane Doe",
          email: "jane.doe@example.com"
        }
      ]
    }

Third-party authentication

Fauna also supports integrations with third-party identity providers.

 

Database model and multi-tenancy

Fauna’s database model makes it easy to create databases for isolated environments, such as staging and production, and multi-tenant applications:

  • Each Fauna database can have many child databases. You can use child databases as tenants for your application.

  • All databases, including child databases, are instantly allocated without provisioning or warmup.

  • Each database is logically isolated from its peers with separate access controls.

  • All Fauna resources, excluding top-level keys, exist as documents within a specific database. This includes collections, user-defined functions, and child databases.

  • Queries run in the context of a single database and can’t access data outside the database. Databases aren’t aware of their parents or peers.

Create a child database

You can use FQL queries to programmatically create child databases. Child databases are represented as Database collection documents in the parent database.

To create a child database, run the following query as an Admin in the Dashboard Shell:

// Create a child database named `childDB`
Database.create({
  name: "childDB",
  typechecked: true,
  priority: 10
})

You can populate and access the database’s data using a secret that’s scoped to the database.

You can use a scoped key to manage a child database’s data from its parent database.

Next steps

Congratulations — You’ve completed the tour! 🏆

This introduction is just the tip of the iceberg. There’s much more of Fauna to explore. To help you on your journey, we’ve curated a selection of resources below.

 

A quick reference for FQL methods, grouped by functionality.

Learn how to use Fauna through a sample application.

Build solutions with Fauna following best practices.

Create a project for an app using the Fauna CLI and FSL.

Set up access control in Fauna using RBAC and ABAC.

Learn about common FQL query patterns.

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!