JavaScript driver

JavaScript logo

The section describes Fauna’s open source JavaScript driver, which provides the resources required to interact with Fauna.

Current stable version

4.8.0

Repository

Supported runtimes

This driver supports and is tested on:

  • Node.js

    • LTS

    • Stable

  • Chrome

  • Firefox

  • Safari

  • Internet Explorer 11

Installation

Node.js

npm install --save faunadb

or:

yarn add faunadb

See faunadb on NPM for more information.

Browsers

Via CDN:

<script src="//cdn.jsdelivr.net/npm/faunadb@latest/dist/faunadb.js"></script>

The minified version of the driver can also be used via CDN:

<script src="//cdn.jsdelivr.net/npm/faunadb@latest/dist/faunadb-min.js"></script>

Usage

Connecting from a browser

The Fauna JavaScript driver works well within a browser, or other HTML rendering context that can process JavaScript. Replace YOUR_FAUNA_SECRET with a secret for your database.

You can acquire a secret by performing the following steps:

  1. Log in to the Fauna Dashboard.

  2. Select (or create) a database.

  3. Click Security.

  4. Click New Key.

Be sure to copy the secret to a safe place. It is a password-equivalent, and is only ever displayed once.

See Security for more information on keys, tokens, and secrets.

<html>
  <head>
    <title>Connect to Fauna example</title>
  </head>
  <body>
    <h1>Connect to Fauna example</h1>

    <p>
      Open your browser's developer console to see the query results.
    </p>
  </body>
<script src="https://cdn.jsdelivr.net/npm/faunadb@latest/dist/faunadb.js"></script>
<script type="text/javascript">
  var faunadb = window.faunadb
  var q = faunadb.query
  var client = new faunadb.Client({
    secret: 'YOUR_FAUNA_SECRET',
    // Adjust the endpoint if you are using Region Groups
    endpoint: 'https://db.fauna.com/',
  })
  client.query(
    q.ToDate('2018-06-06')
  )
  .then(function (res) { console.log('Result:', res) })
  .catch(function (err) { console.log('Error:', err) })
</script>
</html>

Requiring the driver in Node.js

var faunadb = require('faunadb')
var q = faunadb.query

This is the recommended require stanza. The faunadb.query module contains all of the functions to create Fauna query expressions.

Instantiating a client

var client = new faunadb.Client({
  secret: 'YOUR_FAUNA_SECRET',
  // NOTE: Use the correct endpoint for your database's Region Group.
  endpoint: 'https://db.fauna.com/',
})

See Connections for more details on creating client connections. See Region Groups for more details on the domains to use for Region Groups.

By default, the client object executes queries using HTTP Keep-Alive requests, which means that the connection is held open longer than required to receive a query response. This behavior can reduce the connection overhead when your code needs to issue many queries.

Should you ever need to disable keep-alive connections, you can do so in the client constructor’s options:

var client = new faunadb.Client({
  secret: 'YOUR_FAUNA_SECRET',
  keepAlive: false,
})

When keepAlive is set to false, each query that your code executes results in a separate HTTP connection to Fauna.

Issuing queries

Once the client has been instantiated, it can be used to issue queries. For example, to create a document in an existing collection named test with the data: { testField: 'testValue' }:

var createP = client.query(
  q.Create(
    q.Collection('test'),
    { data: { testField: 'testValue' } }
  )
)

All methods on faunadb.Client return ES6 Promises. So, if we wanted to handle the Promise to access the Ref of the newly created document:

createP.then(function(response) {
  console.log(response.ref); // Logs the ref to the console.
})

response is a JSON object containing the Fauna response. See the JSDocs for faunadb.Client.

Query metrics

You can acquire metrics for any query by using the client.queryWithMetrics method. Its response object has a different structure:

{
  value: { ... }, // structured response body
  metrics: { // usage data
    x-compute-ops: 0,
    x-byte-read-ops: 0,
    x-byte-write-ops: 0,
    x-query-time: 0,
    x-txn-retries: 0
  }
}

All metrics values are Integer values. x-query-time is measured in milliseconds.

For example:

client.queryWithMetrics(q.Now())
.then((result) => {
  console.log('Response:', result.value)
  console.log('Metrics:', result.metrics)
})
.catch((error) => console.log("Error:" , error))
Response:
Time("2022-03-29T18:24:08.626Z")
Metrics:
{
  'x-compute-ops': 1,
  'x-byte-read-ops': 0,
  'x-byte-write-ops': 0,
  'x-query-time': 8,
  'x-txn-retries': 0
}

Pagination helpers

The driver contains helpers to provide a simpler API for consuming paged responses from Fauna. See the Paginate reference for a description of paged responses.

Using the helper to page over sets lets the driver manage cursors and pagination state. For example, client.paginate:

var helper = client.paginate(
  q.Match(
    q.Index('test_index'),
    'example-term'
  )
)

The return value, helper, is an instance of PageHelper. The each method executes a callback function on each consumed page.

helper.each(function(page) {
  // Logs the page's contents,
  // for example: [ Ref(Collection("test"), "1234"), ... ]
  console.log(page);
});

Note that each returns a Promise<void> that is fulfilled on the completion of pagination.

The pagination can be transformed server-side via the Fauna Query Language by using the map and filter functions.

For example, to retrieve the matched documents:

helper
  .map(function(ref) {
    return q.Get(ref)
  })
  .each(function(page) {
    console.log(page); // Logs the retrieved documents.
  })

See the JSDocs for more information on the pagination helper.

Timeouts

The client can be configured to handle timeouts in two different ways:

  1. Specify an HTTP timeout in seconds by adding a timeout field to the options block when instantiating the client.

    When timeout is provided, the client waits the specified number of seconds before timing out, if it has yet to receive a response. When the period has elapsed, any subsequent response cannot be handled.

  2. Specify a server query timeout:

    1. by adding queryTimeout to the options block when instantiating the client, or

    2. by passing an object containing queryTimeout as the second parameter to the .query method.

    When a query timeout is provided, the server waits for the specified number of milliseconds before timing out, if it has yet to complete the current query. When the period has elapsed, the query fails and the server responds with an error.

For example:

// Specify an HTTP timeout
const client = new faunadb.Client({
  secret: 'YOUR_FAUNA_SECRET',
  timeout: 30,
})
// Specify a query timeout during client instantiation
const client = new faunadb.Client({
  queryTimeout: 2000,
  secret: 'YOUR_FAUNA_SECRET',
})
// Specify a query timeout per query
client.query(
  q.Paginate(q.Collections()),
  { queryTimeout: 1 }
)

The queryTimeout passed to .query() take precedence over any queryTimeout specified during client instantiation.

Per-query options

Some options (currently only secret and queryTimeout) can be overridden on a per-query basis:

var createP = client.query(
  q.Create(
    q.Collection('test'),
    {
      data: { testField: 'testValue' }
    }
  ),
  { secret: 'YOUR_FAUNA_SECRET' }
)
var helper = client.paginate(
  q.Match(q.Index('test_index'), 'example-term'),
  null,
  { secret: 'YOUR_FAUNA_SECRET', }
)
var data = client.query(
  q.Paginate(q.Collections()),
  { queryTimeout: 100 }
)

Custom fetch

To use a custom fetch(), specify it in the configuration when instantiating a client. The custom fetch() must be compatible with the standard Web API Specification of the Fetch API.

For example:

const customFetch = require('./customFetch')
const client = new faunadb.Client({
  secret: 'YOUR_FAUNA_SECRET',
  fetch: customFetch,
})

Use in Cloudflare Workers

Cloudflare Workers have neither XMLHttpRequest nor fetch in the global scope. Therefore, the cross-fetch package is unable to inject its own fetch() function, and throws an error. The fetch() function is injected via a closure, so the workaround would be to pass the fetch objects when initiating the Fauna client object.

Cloudflare Workers also don’t support the use of an AbortController, which terminates requests as well as event streams. Here is a workaround:

const c = new faunadb.Client({
  secret: 'YOUR_FAUNA_SECRET',
  fetch: (url, params) => {
    const signal = params.signal
    delete params.signal
    const abortPromise = new Promise(resolve => {
      if (signal) {
        signal.onabort = resolve
      }
    })
    return Promise.race([abortPromise, fetch(url, params)])
  },
})

HTTP/2 session idle time (Node.js only)

When using Node.js, the driver uses HTTP/2 multiplexing to reuse the same connection for one or more simultaneous requests. After all open requests have been resolved, the driver keeps the session open for a period of time, 500 milliseconds by default. During this idle period, the connection can be reused for any new requests.

The http2SessionIdleTime parameter may be used to control how long the HTTP/2 connection remains open while the connection is idle. To save on the overhead of closing and re-opening the connection, set http2SessionIdleTime to a longer time (a non-negative integer representing milliseconds). The maximum value of this parameter is 5000 milliseconds.

While an HTTP/2 connection is active, the driver holds the Node.js event loop open. This prevents the Node.js process from terminating before expected responses can be received. Call client.close() to manually close the connection and allow the process to terminate. This is especially important when http2SessionIdleTime is a large value.

// sample.js (run it with "node sample.js" command)
const { Client, query: q } = require('faunadb')

async function main() {
  const client = new Client({
    secret: 'YOUR_FAUNA_SECRET',
    http2SessionIdleTime: 5000,
  })
  const output = await client.query(q.Add(1, 1))

  console.log(output)

  client.close()
  //     ^^^ If it's not called then the process won't terminate
}

main().catch(console.error)

Syntactic sugar

The JavaScript driver supports syntactic sugar for Lambda functions:

// standard FQL
q.Lambda(["x", "y"], q.Add(q.Var("x"), q.Var("y")))

// syntactic sugar
(x, y) => { q.Add(x, y) }

Using syntactic sugar, the driver translates named variables into Var expressions directly, with no attention to variable scope. If your query involves nested Lambda functions that use identically-named variables, the inner usage refers to the same variable as the outer usage.

Debugging

For debugging purposes in Node.js client applications, it is often useful to override the console.log function. The default console.log only inspects the contents of an object or array to 5 levels of depth. User-defined fields and values in Fauna documents start at level 3, so very little nesting is required to see [Object] or [Array] in response data.

You can override console.log with the following lines at the top of your client scripts:

// customize console.log to report full depth of response
const util = require('util')
const origLog = console.log
console.log = (...msgs) => {
  for (let m of msgs) {
    if (typeof m !== 'string') {
      m = util.inspect(
        m,
        {
          showHidden: false,
          depth: null, // show objects to full depth
          maxArrayLength: null, // show all array items
        }
      )
    }
    origLog(m)
  }
}

Tracing

A trace uniquely identifies a transaction flow. In distributed scenarios, a traceparent propagates a contextual identifier, which lets you associate logs from disparate services that participate in the application. The traceparent request header represents a request in a tracing system using a common format. See traceparent.

Fauna supports distributed tracing by allowing you to apply a traceparent header to individual queries. The traceparent request header format is similar to this example:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01.

Use the identifier in the query response in subsequent queries. If the Fauna query logic is not the main application, you should use the identifier provided by that application and follow the recommended processing model for mutating the identifier.

If you don’t include a traceparent identifier, one is generated for you and attached to the query response.

Tags

You can tag individual queries with identifiers that can be used to associate operations by using the x-fauna-tags header tag.

  • Tags and associated values can be alphanumeric strings and include the underscore (_) character.

  • Tag names can be up to 40 characters, and tag values can be up to 80 characters.

  • You can have up to 25 tags per query.

A query fails with a 400 status code if the tag is invalid.

Code

The JavaScript driver includes no polyfills. Support for Internet Explorer 11 requires a Promise polyfill.

Event streaming

This section demonstrates how to subscribe to document change events.

There are two kinds of event streaming:

The code required to subscribe to each type is very similar. The primary difference is the type of Reference involved in the subscription, and the kinds of events that are included in the stream.

There is a cost in compute operations to hold a stream open, or to repeatedly start a stream that fails.

Use of the document helper requires that the target document reference exists. If the document reference does not exist, the stream fails but the driver retries the stream setup once per second, until the client stops or the document reference is created. This can lead to significant stream charges.

See Billing for details.

Document streaming

The following example is an HTML page that uses the JavaScript driver and plain JavaScript to establish a stream on a document in the Scores collection, with document ID 1.

To make this example work:

  1. Replace YOUR_FAUNA_SECRET with the secret that you use to connect to Fauna.

  2. Host the page in a web server.

  3. Open the page in your browser.

<html>
  <head>
    <style>
span.scores {
  font-family: monospace;
  white-space: pre;
}
    </style>
  </head>
  <body>
    <h1>Scores stream</h1>
    <p>
      Scores: <span class="scores"></span>
    </p>
  </body>

  <script src="https://cdn.jsdelivr.net/npm/faunadb@latest/dist/faunadb-min.js"></script>
  <script type="text/javascript">

var faunadb = window.faunadb
var q = faunadb.query

var client = new faunadb.Client({
  secret: 'YOUR_FAUNA_SECRET',
  domain: 'db.fauna.com', // Adjust if you are using Region Groups
})

// Define a reference to the document that we want to stream
// Note that the Scores collection must already exist
var docRef = q.Ref(q.Collection('Scores'), '1')

function report(e) {
  console.log(e)
  var data = ('action' in e)
    ? e["document"].data
    : e.data
  document.body.innerHTML += '<p><span class="scores">' +
    JSON.stringify(data) +
    '</span></p>'
}

var stream
const startStream = () => {
  stream = client.stream.document(docRef)
  .on('snapshot', snapshot => {
    report(snapshot)
  })
  .on('version', version => {
    report(version)
  })
  .on('error', error => {
    console.log('Error:', error)
    stream.close()
    setTimeout(startStream, 1000)
  })
  .start()
}

startStream()

  </script>
</html>

If the docRef example does not exist, the page reports an error to the browser’s developer console once per second, and attempts to stream the document again.

You can use the Web Shell in the Fauna Dashboard, or fauna-shell, to run the following queries to get the collection and document created:

CreateCollection({ name: "Scores" })
Create(Ref(Collection("Scores"), "1"), { data: { scores: [1, 2, 3] }})

If you have the page open in your browser, you should see the contents of the data field from that document reported in the web page.

At this point, the stream is setup. Any time that the document is updated or deleted, the stream receives an event reflecting the change and the web page reports the new state. For example:

Update(Ref(Collection("Scores"), "1"), { data: { scores: [5, 2, 3] }})

If you need to stop the stream without closing the browser tab, your client code can call stream.close().

It is possible to stream changes to a document that does not yet exist. Instead of using:

client.stream.document(<document reference>).on('<event type>') ...)

Use:

client.stream(<document reference>).on('<event type>' ...)

Use of the document helper function means that you don’t have to listen to start events.

Set streaming

The following example is an HTML page that uses the JavaScript driver and plain JavaScript to establish a stream on the set of documents in the Scores collection.

To make this example work:

  1. Replace YOUR_FAUNA_SECRET with the secret that you use to connect to Fauna.

  2. Host the page in a web server.

  3. Open the page in your browser.

<html>
  <head>
    <style>
span.scores {
  font-family: monospace;
  white-space: pre;
}
    </style>
  </head>
  <body>
    <h1>Scores stream</h1>
    <p>
      Scores: <span class="scores"></span>
    </p>
  </body>

  <script src="https://cdn.jsdelivr.net/npm/faunadb@latest/dist/faunadb-min.js"></script>
  <script type="text/javascript">

var faunadb = window.faunadb
var q = faunadb.query

var client = new faunadb.Client({
  secret: 'YOUR_FAUNA_SECRET',
  domain: 'db.fauna.com', // Adjust if you are using Region Groups
})

// Define the reference to the target set
var setRef = q.Documents(q.Collection('Scores'))

// Define a function that adds events to the page
function report(e) {
  console.log(e)
  document.body.innerHTML += '<p><span class="scores">' +
    JSON.stringify(e) +
    '</span></p>'
}

// Define the stream fields to include
var streamOptions = { fields: ['action', 'document', 'index'] }

var stream
const startStream = () => {
  stream = client.stream(setRef, streamOptions)
  .on('start', start => { report(start) })
  .on('set', set => { report(set) })
  .on('error', error => {
    console.log('Error:', error)
    stream.close()
    setTimeout(startStream, 1000)
  })
  .start()
}

startStream()

  </script>
</html>

Before you try the example, make sure that the "Scores" collection exists:

CreateCollection({ name: "Scores" })

You can use the Web Shell in the Fauna Dashboard, or fauna-shell, to run the following queries to get the collection and document created:

Create(Ref(Collection("Scores"), "1"), { data: { scores: [1, 2, 3] }})

If you have the page open in your browser, you should see the contents of the data field from that document reported in the web page.

At this point, the stream is setup. Any time that a new "Scores" document is created, or an existing "Scores" document is deleted, the stream receives an event reflecting the change and the web page reports the new state. For example:

Create(Collection("Scores"), { data: { scores: [5, 6, 7] }})

If you need to stop the stream without closing the browser tab, your client code can call stream.close().

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!