Fauna quick start

Fauna is a serverless document-relational database you access through a cloud API.

In this tutorial, you’ll:

  • Create a Fauna database with demo data.

  • Run Fauna Query Language (FQL) queries to read and write data.

  • Build an application that integrates with Fauna using a client driver.

Create a database

To start, create a database in the Fauna Dashboard and populate it with demo data.

  1. Go to the Fauna Dashboard and sign in.

    You can sign up for a free account at https://dashboard.fauna.com/register.

  2. Click CREATE DATABASE.

  3. Enter a database Name. For this tutorial, use Demo.

  4. Fill out the remaining fields as follows:

    • Select the Region Group nearest you.

    • Toggle Use demo data on.

    • Toggle Enable backups off.

  5. Click CREATE.

Run an FQL query

You use FQL queries to read and write data in a Fauna database.

You send queries to Fauna using an HTTP API. The API lets you interact with Fauna using Fauna’s client drivers or any other client that sends HTTP requests.

You can also run FQL queries in the Dashboard Shell:

  1. In the Dashboard’s left navigation, click Explorer.

  2. Expand your region group and click the Demo database.

    This opens the Dashboard Shell.

    dashboard shell

  3. Paste the following FQL query into the shell:

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

    Don’t worry about the query syntax for now. We’ll cover that later in the tutorial.

  4. Press Ctrl + Enter to run the query.

    The query returns:

    {
      data: [
        {
          name: "limes",
          description: "Conventional, 1 ct",
          price: 0.35
        },
        ...
        {
          name: "pinata",
          description: "Original Classic Donkey Pinata",
          price: 24.99
        }
      ]
    }

Compose a query

You add data to Fauna as JSON objects called documents. The database stores documents in collections.

You can filter and fetch documents from a collection as a set, and iterate through each document.

You typically compose a query by calling one or more methods on a document, collection, or set using a TypeScript-like syntax. Expressions are evaluated sequentially from left to right.

For example, the following query calls the all() method on the Product collection.

// Get all documents from the `Product` collection
Product.all()

While useful for prototyping, all() isn’t performant on large datasets. Use an index instead. We’ll cover indexes later in the tutorial.

The query returns a set of Product documents:

{
  data: [
    {
      id: "394260846385037376",
      coll: Product,
      ts: Time("2099-04-04T18:28:13.250Z"),
      name: "cups",
      description: "Translucent 9 Oz, 100 ct",
      price: 6.98,
      ...
    },
    ...
    {
      id: "394260846402863168",
      coll: Product,
      ts: Time("2099-04-04T18:28:13.250Z"),
      name: "pinata",
      description: "Giant Taco Pinata",
      price: 23.99,
      ...
    }
  ]
}

Static typing

FQL is statically typed. Every expression has a data type that’s checked for correctness before evaluation.

For example, you can call firstWhere() on a collection, but not a document.

// Get the first product named "cups"
Product.firstWhere(.name == "cups")

Like all(), firstWhere() isn’t performant on large datasets. Use an index instead.

Static typing helps catch errors early and more consistently, saving you time. You can also turn off type checking for any query.

Method chaining

Method chaining lets you use dot notation to call a method on the output of the previous method.

The following query calls update() on the document returned by firstWhere().

// Rename the "cups" product to "clear cups" if it exists
Product.firstWhere(.name == "cups")
  ?.update({ name: "clear cups" })

?. safely calls update() only if firstWhere() returns a non-null value.

Write data

Each document has a string-encoded, 64-bit id. This id is unique for the document within its collection.

Use create() to create, or insert, a document in a collection. If wanted, you can specify an id for the document.

// Create a document with a specific `id`
Product.create({
  id: "392886847463751746",
  name: "limes",
  description: "Organic, 1 ct",
  price: 0.65
})

If you don’t specify an id, Fauna auto-generates one. Once assigned, the document id is immutable and can’t be changed.

CRUD operations

FQL also supports methods for updating, replacing, and deleting documents.

The following queries use byId() to fetch the document you created. The queries then call another method to modify the document.

// Update a document
Product.byId("392886847463751746")
  ?.update({ price: 0.75 })

// Replace a document
// Product.byId("392886847463751746")
//   ?.replace({
//     name: "key limes",
//     description: "Organic, 2 ct",
//     price: 0.95
// })

// Delete a document
// Product.byId("392886847463751746")
//   ?.delete()

Read data

Earlier queries used byId() to fetch a single document. Other methods fetch a set of documents from a collection.

Filter documents in collections

Use where() to filter documents from a collection.

// Get products named "limes"
Product.where(.name == "limes")

// Equivalent to:
// Product.all()
//   .where(.name == "limes")

where() supports several comparison operators. For example:

// Get products not named "limes"
Product.where(.name != "limes")

// Get products with a price greater than or equal to 20
// Product.where(.price >= 20)

Indexes

While useful for one-off queries, all() and where() aren’t performant on large collections. These methods require a scan of each document in the collection. This results in slower, more expensive queries as the collection grows.

Instead, create Indexes for common data access patterns. An index stores, or covers, specific field values for quicker retrieval without scanning each document.

FQL and FSL

Fauna has two languages:

  • FQL is for creating queries that write or read data. If you’re familiar with SQL, you can think of FQL as Fauna’s data query language (DQL), data manipulation language (DML), and procedural language all in one.

  • Fauna Schema Language (FSL) is for defining and creating collections, indexes, security roles, and similar entities. FSL is Fauna’s data definition language (DDL).

Collection schemas

You define indexes in FSL as part of a collection schema. You typically store a collection schema as a .fsl file in a schema directory of a project. You can then push and pull the files from Fauna using the Fauna CLI.

You can also manage collection schemas and other FSL entities using the Fauna Dashboard.

dashboard collection schema

Index definitions

An index definition can include:

  • Terms: Fields for exact match searches

  • Values: Fields for sorting and range searches

For example, the Product collection schema has definitions for the byName and sortedByPriceLowToHigh indexes:

collection Product {
  ...
  index byName {
    terms [.name]
    values [desc(.quantity)]
  }

  ...
  index sortedByPriceLowToHigh {
    values [.price, .name, .description]
  }
}

Run queries with indexes

You call an index as a method on the collection. You must pass an argument for each term in the index definition. All indexes return a set.

// Exact match search
Product.byName("limes")

// Sort by values
// Product.sortedByPriceLowToHigh()

// Range search
// Product.sortedByPriceLowToHigh({ from: 20, to: 30 })

// Combine terms and values
// Product.byName("limes", { from: 50, to: 10 })

Projection

By default, queries that return sets or documents include every document field. Use projection to return only the specific fields you want. This creates smaller response payloads and decreases egress costs.

The following query returns only the name, description, and price fields for Product documents.

// Get product names, descriptions, and prices
Product.sortedByPriceLowToHigh() {
  name,
  description,
  price
}

Response:

{
  data: [
    {
      name: "limes",
      description: "Conventional, 1 ct",
      price: 0.35
    },
    ...
    {
      name: "pinata",
      description: "Original Classic Donkey Pinata",
      price: 24.99
    }
  ]
}

Covered queries

If you use projection on an index’s covered fields, Fauna gets the field values from the index. This is a covered query.

If the projection contains an uncovered field, Fauna must retrieve the field values from the documents. This is an uncovered query.

// This is a covered query.
// `name`, `description`, and `prices` are `values`
// in the `sortedByPriceLowToHigh` index definition.
Product.sortedByPriceLowToHigh() {
  name,
  description,
  price
}

// This is an uncovered query.
// `quantity` is not one of the `terms` or `values`
// in the `sortedByPriceLowToHigh` index definition.
Product.sortedByPriceLowToHigh() {
  quantity,
  name
}

Covered queries are typically faster and less expensive than uncovered queries, which require document scans. If you frequently run uncovered queries, consider adding the uncovered fields to the index definition.

Use projections to resolve references

In Fauna, you create relationships between documents by referencing one document in another. For example:

// Create a store
let newStore = Store.create({
  id: "393607951810560064",
  name: "Acme Supply"
})

// Create a product with a store reference
Product.create({
  id: "393607951812657216",
  name: "key limes",
  description: "Organic, 3 ct",
  price: 1.00,
  store: newStore
})

The response includes the reference in the store field. If you get the document or a set without projection, the reference is unresolved.

{
  id: "393607951812657216",
  coll: Product,
  ...
  store: Store("393607951810560064")
}

Use projection to resolve the reference and return the store document. This is similar to a join in SQL.

Product.byId("393607951812657216") {
  name,
  description,
  store {
    id,
    name
  }
}

Response:

{
  name: "key limes",
  description: "Organic, 3 ct",
  store: {
    id: "393607951810560064",
    name: "Acme Supply"
  }
}

Create an API key

Next, you’ll connect to Fauna using a client. To do this, you need an API key. Create a key in the Dashboard:

  1. In the upper left pane of Dashboard’s Explorer page, hover over the Demo database, and click the Key icon.

    key icon

  2. Click CREATE KEY.

  3. Choose a Role of Admin or Server and enter an optional Key Name.

    Admin and Server are built-in roles. Fauna also supports user-defined roles.

  4. Click SAVE.

  5. Copy the Secret Key. This is an API key for the Demo database.

Build an application

Next, create a basic application that integrates with Fauna using a Fauna client driver. A client driver sends FQL queries and receives responses using your preferred programming language. The client deserializes query responses into the language’s corresponding data types.

JavaScriptPythonGoC#

 

  1. Set the FAUNA_SECRET environment variable to your Fauna API key. Fauna’s client drivers can access the key from this variable.

    export FAUNA_SECRET=<apiKey>
  2. Create a directory for the app and install the driver.

    mkdir app
    cd app
    go mod init app
    go get github.com/fauna/fauna-go
    mkdir app
    cd app
    npm install fauna
    mkdir app
    cd app
    pip install fauna

    The C# client library is currently in beta.

    Navigate to your .NET or C# project directory and add the Fauna package to your project. Include the Json.NET package to serialize query responses to JSON.

    mkdir app
    cd app
    dotnet new console
    dotnet add package Fauna --prerelease
    dotnet add package Newtonsoft.Json
  3. Create an app.go file and add the following code:

    Create an app.mjs file and add the following code:

    Create an app.py file and add the following code:

    Edit the Program.cs file and replace the code with the following:

    import { Client, fql } from "fauna";
    
    const client = new Client();
    
    // Build a query using the `fql` function
    const storeQuery = fql`
      Product.sortedByPriceLowToHigh() {
        name,
        description,
        price
      }`;
    // Run the query
    const queryResult = await client.query(storeQuery);
    const documents = queryResult.data.data;
    
    console.log(documents);
    client.close();
    from fauna import fql
    from fauna.client import Client
    from fauna.errors import FaunaException
    
    client = Client()
    
    try:
      # Build a query using the `fql` function
      store_query = fql("""
        Product.sortedByPriceLowToHigh() {
          name,
          description,
          price
      }""")
      # Run the query
      query_result = client.query(store_query)
      documents = query_result.data.data
    
      print(documents)
    except FaunaException as e:
        print(e)
    finally:
        client.close();
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    
    	"github.com/fauna/fauna-go"
    )
    
    func main() {
    	client, clientErr := fauna.NewDefaultClient()
    	if clientErr != nil {
    		panic(clientErr)
    	}
    
    	// Build a query using the `fauna.FQL` function
    	storeQuery, _ := fauna.FQL(`
    		Product.sortedByPriceLowToHigh() {
    			name,
    			description,
    			price
    		}`, nil)
    	// Run the query
    	res, err := client.Query(storeQuery)
    	if err != nil {
    		panic(err)
    	}
    
    	jsonData, err := json.Marshal(res.Data)
    	fmt.Println(string(jsonData))
    }
    namespace HelloFauna;
    using Fauna;
    using static Fauna.Query;
    using Newtonsoft.Json;
    
    class Program
    {
      static async Task Main(string[] args)
      {
        var secret = Environment.GetEnvironmentVariable("FAUNA_SECRET");
        if (string.IsNullOrEmpty(secret))
        {
          Console.WriteLine("Fauna secret is not valid");
          return;
        }
    
        var cfg = new Configuration(secret);
        var client = new Client(cfg);
    
        // Build a query using the `FQL` function
        var storeQuery = FQL($@"
          Product.sortedByPriceLowToHigh() {{
            name,
            description,
            price
        }}");
        // Run the query
        var result = await client.QueryAsync(storeQuery);
        var documents = result.Data;
    
        var jsonResult = JsonConvert.SerializeObject(documents, Formatting.Indented);
        Console.WriteLine(jsonResult);
      }
    }

    The application prints product data from the demo database.

  4. Run the application:

    go run app.go
    node app.mjs
    python app.py
    dotnet run

    The application prints the following:

    [
      { name: 'limes',
        description: 'Conventional, 1 ct',
        price: 0.35
      },
      ...
      {
        name: 'pinata',
        description: 'Original Classic Donkey Pinata',
        price: 24.99
      }
    ]
    [
        {"name": "clear cups", "description": "Translucent 9 Oz, 100 ct", "price": 6.98},
        ...
        {"name": "pinata", "description": "Giant Taco Pinata", "price": 23.99},
    ]
    {
      "Data": [
        {
          "description": "Conventional, 1 ct",
          "name": "limes",
          "price": 0.35
        },
        ...
        {
          "description": "Original Classic Donkey Pinata",
          "name": "pinata",
          "price": 24.99
        }
      ],
      "After": ""
    }
    {
      "Data": [
        {
          "name": "limes",
          "description": "Conventional, 1 ct",
          "price": 0.35
        },
        ...
        {
          "name": "pinata",
          "description": "Original Classic Donkey Pinata",
          "price": 24.99
        }
      ],
      "After": null
    }

Next steps

Congratulations! You’ve created a database, run some queries, and integrated Fauna with an application.

To get more out of Fauna, try some of the following next steps:

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!