Fauna v10 Go client driver (current)

Version: 3.0.0 Repository: fauna/fauna-go

Fauna’s Go client driver lets you run FQL queries from Go applications.

This guide shows how to set up the driver and use it to run FQL queries.

This driver can only be used with FQL v10. It’s not compatible with earlier versions of FQL. To use earlier FQL versions, use the faunadb version.

Supported Go versions

  • 1.19

  • 1.20

  • 1.21

  • 1.22

Supported cloud runtimes

API reference

API reference documentation for the driver is available on pkg.go.dev.

Installation

To install the driver, run:

go get github.com/fauna/fauna-go/v3

Basic usage

The following application:

  • Initializes a client instance to connect to Fauna

  • Composes a basic FQL query using an FQL string template

  • Runs the query using Query()

package main

import (
	"fmt"

	"github.com/fauna/fauna-go/v3"
)

func main() {
	// Initialize the client to connect to Fauna
	client := fauna.NewClient(
		"FAUNA_SECRET",
		fauna.DefaultTimeouts(),
	)

	// Compose a query
	query, _ := fauna.FQL(`
		Product.sortedByPriceLowToHigh() {
			name,
			description,
			price
			}
	`, nil)

	res, err := client.Query(query)
	if err != nil {
		panic(err)
	}

	jsonData, _ := json.Marshal(res.Data)
	fmt.Println(string(jsonData))
}

Connect to Fauna

Each Fauna query is an independently authenticated request to the Core HTTP API’s Query endpoint. You authenticate with Fauna using an authentication secret.

Get an authentication secret

Fauna supports several secret types. For testing, you can create a key, which is a type of secret:

  1. Log in to the Fauna Dashboard.

  2. On the Explorer page, create a database.

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

  4. Choose a Role of server.

  5. Click Save.

  6. Copy the Key Secret. The secret is scoped to the database.

Initialize a client

To send query requests to Fauna, initialize a Client instance.

The NewDefaultClient() method initializes a client using:

  • A Fauna authentication secret in the FAUNA_SECRET environment variable

  • A base URL used by the driver for Fauna Core HTTP API requests in the FAUNA_ENDPOINT environment variable.

  • Default client configuration options

client, clientErr := fauna.NewDefaultClient()
if clientErr != nil {
	panic(clientErr)
}

To pass configuration options, use NewClient() to initialize the client:

client := fauna.NewClient(
	"FAUNA_SECRET",
	fauna.DefaultTimeouts(),
)

NewClient() requires secret and timeouts arguments. For timeouts and more configuration options, see Client configuration.

Connect to a child database

A scoped key lets you use a parent database’s admin key to send query requests to its child databases.

For example, if you have an admin key for a parent database and want to connect to a child database named childDB, you can create a scoped key using the following format:

// Scoped key that impersonates an `admin` key for
// the `childDB` child database.
fn...:childDB:admin

You can then initialize a Client instance using the scoped key:

client := fauna.NewClient(
	"fn...:childDB:admin",
	fauna.DefaultTimeouts(),
)

Multiple connections

You can use a single client instance to run multiple asynchronous queries at once. The driver manages HTTP connections as needed. Your app doesn’t need to implement connection pools or other connection management strategies.

You can create multiple client instances to connect to Fauna using different credentials or client configurations.

AWS Lambda connections

AWS Lambda freezes, thaws, and reuses execution environments for Lambda functions. See Lambda execution environment.

When an execution environment is thawed, Lambda only runs the function’s handler code. Objects declared outside of the handler method remain initialized from before the freeze. Lambda doesn’t re-run initialization code outside the handler.

Fauna drivers keep socket connections that can time out during long freezes, causing ECONNRESET errors when thawed.

To prevent timeouts, create Fauna client connections inside function handlers. Fauna drivers use lightweight HTTP connections. You can create new connections for each request while maintaining good performance.

Run FQL queries

Use FQL string templates to compose FQL queries. Run the queries using Query():

query, _ := fauna.FQL(`Product.sortedByPriceLowToHigh()`, nil)
client.Query(query)

By default, Query() uses query options from the Client configuration. You can pass options to Query() to override these defaults. See Query options.

You can only compose FQL queries using string templates.

Variable interpolation

Use ${} to pass native Go variables as map[string]any to FQL. You can escape a variable by prepending an additional $.

// Create a native Go var
collectionName := "Product"

// Pass the var to an FQL query
query, _ := fauna.FQL(`
	let collection = Collection(${collectionName})
	collection.sortedByPriceLowToHigh()
`, map[string]any{"collectionName": collectionName})

client.Query(query)

The driver encodes interpolated variables to an appropriate FQL type and uses the wire protocol to pass the query to the Core HTTP API’s Query endpoint. This helps prevent injection attacks.

Query composition

You can use variable interpolation to pass FQL string templates as query fragments to compose an FQL query:

func main() {
	client := fauna.NewClient(
		"FAUNA_SECRET",
		fauna.DefaultTimeouts(),
	)

	// Create a reusable query fragment.
	productQuery, _ := fauna.FQL(`
		Product.byName("pizza").first()
	`, nil)

	// Use the fragment in another FQL query.
	query, _ := fauna.FQL(`
		let product = ${product}
		product {
			name,
			price
		}
	`, map[string]any{"product": productQuery})

	client.Query(query)
}

Structs

The driver supports user-defined structs:

type Product struct {
	Name string `fauna:"name"`
	Description string `fauna:"description"`
	Price int `fauna:"price"`
}

func main() {
	client := fauna.NewClient(
		"FAUNA_SECRET",
		fauna.DefaultTimeouts(),
	)

	newProduct := Product{"key limes", "Organic, 1 ct", 95}
	query, _ := fauna.FQL(`Product.create(${product})`, map[string]any{"product": newProduct})

	client.Query(query)
}

Pagination

Use Paginate() to iterate through a Set that contains more than one page of results. Paginate() accepts the same Query options as Query().

// Adjust `pageSize()` size as needed.
query, _ := fauna.FQL(`
Product
	.byName("limes")
	.pageSize(2)
`, nil)

paginator := client.Paginate(query)
for {
	page, _ := paginator.Next()

	var pageItems []any
	page.Unmarshal(&pageItems)

	for _, item := range pageItems {
		fmt.Println(item)
	}

	if !paginator.HasNext() {
		break
	}
}

Query stats

Successful query responses and the following error types include query stats:

  • ErrAbort

  • ErrAuthentication

  • ErrAuthorization

  • ErrContendedTransaction

  • ErrInvalidRequest

  • ErrQueryCheck

  • ErrQueryRuntime

  • ErrQueryRuntime

  • ErrQueryTimeout

  • ErrServiceInternal

  • ErrServiceTimeout

  • ErrThrottling

query, _ := fauna.FQL(`"Hello world"`, nil)

res, err := client.Query(query)
if err != nil {
	if faunaErr, ok := err.(*fauna.ErrQueryCheck); ok {
		jsonData, _ := json.Marshal(faunaErr.QueryInfo.Stats)
		fmt.Println(string(jsonData))
	}
	panic(err)
}

jsonData, _ := json.Marshal(res.Stats)
fmt.Println(string(jsonData))

Client configuration

The Client instance comes with reasonable configuration defaults. We recommend using the defaults in most cases.

If needed, you can use NewClient() to configure the client and override the defaults. This also lets you set default Query options.

secret := "FAUNA_SECRET"
timeouts := fauna.Timeouts{
	QueryTimeout:          time.Minute,
	ClientBufferTimeout:   time.Second * 30,
	ConnectionTimeout:     time.Second * 10,
	IdleConnectionTimeout: time.Minute * 5,
}

client := fauna.NewClient(
	// Configure the client
	secret,
	timeouts,
	fauna.URL("https://db.fauna.com"),
	fauna.AdditionalHeaders(map[string]string{
		"foo": "bar",
	}),
	fauna.Linearized(false),
	fauna.MaxAttempts(5),
	fauna.MaxBackoff(time.Minute),
	fauna.MaxContentionRetries(5),

	// Set default query options
	fauna.DefaultTypecheck(true),
	fauna.QueryTags(map[string]string{
		"tag", "value",
	}),
	fauna.QueryTimeout(time.Second*60),
)

For supported parameters, see NewClient in the API reference.

Timeouts

NewClient() requires a timeouts argument. The argument must contain a Timeouts struct:

timeouts := fauna.Timeouts{
	QueryTimeout:          time.Second * 5,
	ClientBufferTimeout:   time.Second * 5,
	ConnectionTimeout:     time.Second * 5,
	IdleConnectionTimeout: time.Second * 5,
}

client := fauna.NewClient(
	"FAUNA_SECRET",
	timeouts,
)

For default timeouts, use DefaultTimeouts():

client := fauna.NewClient(
	"FAUNA_SECRET",
	fauna.DefaultTimeouts(),
)

For supported fields, see Timeouts in the API reference.

Configuration functions

To configure the client and set default query options, pass one or more ClientConfigFn functions to NewClient():

client := fauna.NewClient(
	"FAUNA_SECRET",
	fauna.DefaultTimeouts(),

	// Start configuration functions
	fauna.URL("https://db.fauna.com"),
	fauna.AdditionalHeaders(map[string]string{
		"foo": "bar",
	}),
	fauna.Linearized(false),
	fauna.MaxAttempts(5),
	fauna.MaxBackoff(time.Minute),
	fauna.MaxContentionRetries(5),

	// Configuration functions for
	// default query options
	fauna.DefaultTypecheck(true),
	fauna.QueryTags(map[string]string{
		"tag", "value",
	}),
	fauna.QueryTimeout(time.Second*60),
)

For supported functions, see ClientConfigFn in the API reference.

Retries

By default, the client automatically retries a query if the request returns a 429 HTTP status code. Retries use an exponential backoff.

Use the MaxBackoff configuration function to set the maximum time between retries. Similarly, use MaxAttempts to set the maximum number of retry attempts.

Query options

The Client configuration sets default query options for the following methods:

  • Query()

  • Paginate()

  • Stream()

To override these defaults, pass one or more QueryOptFn functions to the method:

options := []fauna.QueryOptFn{
	fauna.Tags(map[string]string{
		"name": "hello world query",
	}),
	fauna.Timeout(time.Minute),
	fauna.Traceparent("00-750efa5fb6a131eb2cf4db39f28366cb-000000000000000b-00"),
	fauna.Typecheck(true),
}

query, _ := fauna.FQL(`"Hello world"`, nil)
client.Query(query, options...)

For supported functions, see QueryOptFn in the API reference.

Event Feeds

The driver supports Event Feeds. An Event Feed asynchronously polls an event source for events.

To use Event Feeds, you must have a Pro or Enterprise plan.

Request an Event Feed

To get an event source, append set.eventSource() or set.eventsOn() to a supported Set.

To get paginated events, pass the event source to Feed(). This lets you fetch a page of initial results followed by an Event Feed:

client := fauna.NewClient(
	"FAUNA_SECRET",
	fauna.DefaultTimeouts(),
)

query, _ := fauna.FQL(`
let set = Product.all()
{
	initialPage: set.pageSize(10),
	eventSource: set.eventSource()
}
`, nil)

res, err := client.Query(query)
if err != nil {
	log.Fatalf("Failed to query: %v", err)
}

var result struct {
	InitialPage fauna.Page        `fauna:"initialPage"`
	EventSource fauna.EventSource `fauna:"eventSource"`
}
if err := res.Unmarshal(&result); err != nil {
	log.Fatalf("Failed to unmarshal results: %v", err)
}

feed, err := client.Feed(result.EventSource)
if err != nil {
	log.Fatalf("Failed to create feed: %v", err)
}

If changes occur between the creation of the event source and the Feed() request, the feed replays and emits any related events.

You can also pass a query that produces an event source directly to FeedFromQuery():

query, _  := fauna.FQL(`Product.all().eventsOn(.price, .stock)`, nil)

feed, err := client.FeedFromQuery(query)
if err != nil {
    log.Fatalf("Failed to create feed from query: %v", err)
}

In most cases, you’ll get events after a specific start time or cursor.

Get events after a specific start time

When you first poll an event source using an Event Feed, you usually pass EventFeedStartTime() to Feed() or FeedFromQuery(). The request returns events that occurred after the specified timestamp (exclusive):

query, _ := fauna.FQL(`Product.all().eventSource()`, nil)

// Calculate timestamp for 10 minutes ago
tenMinutesAgo := time.Now().Add(-10 * time.Minute)

feed, err := client.FeedFromQuery(
    query,
    fauna.EventFeedStartTime(tenMinutesAgo),
)

The start time must be later than the creation time of the event source. The period between the request and the start time can’t exceed the history_days setting for the source Set’s collection. If history_days is 0 or unset, the period is limited to 15 minutes.

Get events after a specific cursor

After the initial request, you usually get subsequent events using the cursor for the last page or event. To get events after a cursor (exclusive), pass an EventFeedCursor() to Feed() or FeedFromQuery():

query, _ := fauna.FQL(`Product.all().eventSource()`, nil)

feed, err := client.FeedFromQuery(
    query,
    fauna.EventFeedCursor("gsGabc456"),  // Cursor for a previous page
)

Iterate on an Event Feed

Feed() and FeedFromQuery() return an EventFeed instance. You can use a for loop to iterate through the pages of events:

import (
	"fmt"
	"log"
	"time"

	"github.com/fauna/fauna-go/v3"
)

func main() {
	client := fauna.NewClient("FAUNA_SECRET", fauna.DefaultTimeouts())

	query, _ := fauna.FQL(`Product.all().eventSource()`, nil)

	// Calculate timestamp for 10 minutes ago
	tenMinutesAgo := time.Now().Add(-10 * time.Minute)

	feed, err := client.FeedFromQuery(
		query,
		fauna.EventFeedStartTime(tenMinutesAgo),
	)
	if err != nil {
		log.Fatalf("Failed to create feed: %v", err)
	}

	for {
		var page fauna.FeedPage
		err := feed.Next(&page)
		if err != nil {
			log.Fatalf("Error getting next feed page: %v", err)
		}

		fmt.Println("Page stats:", page.Stats)

		for _, event := range page.Events {
			switch event.Type {
			case "add":
				// Do something on add
				fmt.Println("Add event: ", event)
			case "update":
				// Do something on update
				fmt.Println("Update event: ", event)
			case "remove":
				// Do something on remove
				fmt.Println("Remove event: ", event)
			}
		}

		if !page.HasNext {
			break
		}
	}
}

Each page includes a top-level cursor. You can pass the cursor to Feed() or FeedFromQuery() using EventFeedCursor():

import (
	"fmt"
	"log"
	"time"

	"github.com/fauna/fauna-go/v3"
)

func processFeed(client *fauna.Client, query *fauna.Query, startTs time.Time, sleepTime time.Duration) {
	var cursor string = ""

	for {
		// Use start time only on the first request, then use cursor.
		var options []fauna.FeedOptFn
		if !startTs.IsZero() {
			options = append(options, fauna.EventFeedStartTime(startTs))
			// Null out startTs after first use.
			startTs = time.Time{}
		} else {
			options = append(options, fauna.EventFeedCursor(cursor))
		}

		feed, err := client.FeedFromQuery(query, options...)
		if err != nil {
			log.Fatalf("Failed to create feed: %v", err)
		}

		for {
			var page fauna.FeedPage
			err := feed.Next(&page)
			if err != nil {
				log.Fatalf("Error getting next feed page: %v", err)
			}

			for _, event := range page.Events {
				switch event.Type {
				case "add":
					fmt.Println("Add event:", event)
				case "update":
					fmt.Println("Update event:", event)
				case "remove":
					fmt.Println("Remove event:", event)
				}
			}

			// Store the cursor of the last page
			cursor = page.Cursor

			// If no more pages are available, break the inner loop
			if !page.HasNext {
				break
			}
		}

		// Sleep between feed requests
		fmt.Printf("Sleeping for %v seconds...\n", sleepTime.Seconds())
		time.Sleep(sleepTime)
	}
}

func main() {
	client := fauna.NewClient("FAUNA_SECRET", fauna.DefaultTimeouts())

	// Calculate timestamp for 10 minutes ago
	tenMinutesAgo := time.Now().Add(-10 * time.Minute)
	query, err := fauna.FQL(`Product.all().eventsOn(.price, .stock)`, nil)
	if err != nil {
		log.Fatalf("Failed to create FQL query: %v", err)
	}

	sleepTime := 300 * time.Second
	processFeed(client, query, tenMinutesAgo, sleepTime)
}

If needed, you can store the cursor as a collection document:

import (
	"fmt"
	"log"
	"time"

	"github.com/fauna/fauna-go/v3"
)

func processFeedWithCursor(client *fauna.Client, query *fauna.Query, startTs time.Time, sleepTime time.Duration) {
	// Ensure `Cursor` collection exists
	createCursorCollection, err := fauna.FQL(`
		if (Collection.byName("Cursor").exists() == false) {
			Collection.create({
				name: "Cursor",
				fields: {
					name: {
						signature: "String"
					},
					value: {
						signature: "String?"
					}
				},
				constraints: [
					{
						unique: [
							{ field: ".name", mva: false }
						]
					}
				],
				indexes: {
					byName: {
						terms: [
							{ field: ".name", mva: false }
						]
					}
				}
			})
		} else {
			null
		}
	`, nil)
	if err != nil {
		log.Fatalf("Failed to create Cursor collection: %v", err)
	}
	if _, err := client.Query(createCursorCollection); err != nil {
		log.Fatalf("Failed to create Cursor collection: %v", err)
	}

	// Ensure `ProductInventory` document exists in `Cursor`
	createProductInventoryCursor, err := fauna.FQL(`
		if (Collection("Cursor").byName("ProductInventory").first() == null) {
			Cursor.create({
				name: "ProductInventory",
				value: null
			})
		} else {
			null
		}
	`, nil)
	if err != nil {
		log.Fatalf("Failed to create ProductInventory cursor: %v", err)
	}
	if _, err := client.Query(createProductInventoryCursor); err != nil {
		log.Fatalf("Failed to create ProductInventory cursor: %v", err)
	}

	for {
		// Fetch existing cursor from the `Cursor` collection
		cursorQuery, err := fauna.FQL(`Cursor.byName("ProductInventory").first()`, nil)
		if err != nil {
			log.Fatalf("Failed to create cursor query: %v", err)
		}
		cursorRes, err := client.Query(cursorQuery)
		if err != nil {
			log.Fatalf("Failed to fetch cursor: %v", err)
		}

		// Unmarshal cursor data into a map
		var cursorData map[string]interface{}
		if err := cursorRes.Unmarshal(&cursorData); err != nil {
			log.Fatalf("Failed to unmarshal cursor result: %v", err)
		}

		// Extract the cursor value
		cursor, _ := cursorData["cursor"].(string)

		// Set options based on cursor existence
		var options []fauna.FeedOptFn
		if cursor == "" {
			options = append(options, fauna.EventFeedStartTime(startTs))
		} else {
			// Here we ensure that the query supports cursors
			if query == nil {
				log.Fatalf("Query is nil; unable to create feed with cursor.")
			}
			options = append(options, fauna.EventFeedCursor(cursor))
		}

		// Create the feed
		feed, err := client.FeedFromQuery(query, options...)
		if err != nil {
			log.Fatalf("Failed to create feed: %v", err)
		}

		for {
			var page fauna.FeedPage
			if err := feed.Next(&page); err != nil {
				log.Fatalf("Error getting next feed page: %v", err)
			}

			for _, event := range page.Events {
				switch event.Type {
				case "add":
					fmt.Println("Add event: ", event)
				case "update":
					fmt.Println("Update event: ", event)
				case "remove":
					fmt.Println("Remove event: ", event)
				}
			}

			// Store the cursor of the last page in the `Cursor` collection
			cursor = page.Cursor
			updateCursor, err := fauna.FQL(fmt.Sprintf(`
				Cursor.byName("ProductInventory").first()!.update({
					value: "%s"
				})
			`, cursor), nil)
			if err != nil {
				log.Fatalf("Failed to create update cursor query: %v", err)
			}
			if _, err := client.Query(updateCursor); err != nil {
				log.Fatalf("Failed to update cursor: %v", err)
			}
			fmt.Printf("Cursor updated: %s\n", cursor)

			startTs = time.Time{}
			fmt.Printf("Sleeping for %v seconds...\n", sleepTime.Seconds())
			time.Sleep(sleepTime)
		}
	}
}

func main() {
	client := fauna.NewClient("FAUNA_SECRET", fauna.DefaultTimeouts())

	// Calculate timestamp for 10 minutes ago
	tenMinutesAgo := time.Now().Add(-10 * time.Minute)
	query, err := fauna.FQL(`Product.all().eventsOn(.price, .stock)`, nil)
	if err != nil {
		log.Fatalf("Failed to create FQL query: %v", err)
	}

	sleepTime := 300 * time.Second
	processFeedWithCursor(client, query, tenMinutesAgo, sleepTime)
}

Error handling

Errors can occur in two places:

  • While fetching a page

  • While iterating a page’s events

This distinction allows for you to ignore errors originating from event processing. For example:

import (
	"fmt"
	"log"
	"time"

	"github.com/fauna/fauna-go/v3"
)

func main() {
	client := fauna.NewClient("FAUNA_SECRET", fauna.DefaultTimeouts())

	query, _ := fauna.FQL(`Product.all().eventSource()`, nil)

	// Calculate timestamp for 10 minutes ago
	tenMinutesAgo := time.Now().Add(-10 * time.Minute)

	feed, err := client.FeedFromQuery(
		query,
		fauna.EventFeedStartTime(tenMinutesAgo),
	)
	if err != nil {
		log.Fatalf("Failed to create feed: %v", err)
	}

	for {
		var page fauna.FeedPage
		err := feed.Next(&page)
		if err != nil {
			log.Fatalf("Error getting next feed page: %v", err)
		}

		fmt.Println("Page stats:", page.Stats)

		for _, event := range page.Events {
			func() {
				defer func() {
					if r := recover(); r != nil {
						log.Printf("Error processing event: %v", r)
					}
				}()

				switch event.Type {
				case "add":
					fmt.Println("Add event: ", event)
				case "update":
					fmt.Println("Update event: ", event)
				case "remove":
					fmt.Println("Remove event: ", event)
				default:
					log.Printf("Unknown event type: %s", event.Type)
				}
			}()
		}

		if !page.HasNext {
			break
		}
	}
}

Each page’s cursor contains the cursor for the page’s last successfully processed event. If you’re using a loop to poll for changes, you can use the cursor to skip any events that caused errors.

Event Feed options

Both Feed() and FeedFromQuery() accept FeedOptFn functions as arguments.

Use EventFeedStartTime() to start the feed at a specific timestamp:

tenMinutesAgo := time.Now().Add(-10 * time.Minute)

feed := client.FeedFromQuery(
	fauna.FQL(`Product.all().eventSource()`),
	fauna.EventFeedStartTime(tenMinutesAgo),
)

Use EventFeedCursor() to start the feed at a specific event or page cursor:

feed := client.FeedFromQuery(
	fauna.FQL(`Product.all().eventSource()`),
	fauna.EventFeedCursor("gsGabc456"),
)

Use EventFeedPageSize() to set the maximum number of events returned per page:

feed := client.FeedFromQuery(
	fauna.FQL(`Product.all().eventSource()`),
	fauna.EventFeedCursor("gsGabc456"),
	fauna.EventFeedPageSize(10),
)

For supported functions, see FeedOptFn in the API reference.

Event Streaming

The driver supports Event Streaming.

Start a stream

To get an event source, append set.eventSource() or set.eventsOn() to a supported Set.

The driver represents event sources as EventSource values. To stream the source’s events, pass the event source to Stream(). This lets you output a stream alongside normal query results:

type Product struct {
	Name			string	`fauna:"name"`
	Description		string	`fauna:"description"`
	Price			int	`fauna:"price"`
}

func main() {
	client := fauna.NewClient(
		"FAUNA_SECRET",
		fauna.DefaultTimeouts(),
	)

	dataLoad, _ := fauna.FQL(`
		let products = Product.all()
		{
			Products: products.toArray(),
			EventSource: products.eventSource()
		}
	`, nil)

	data, err := client.Query(dataLoad)
	if err != nil {
		panic(err)
	}

	queryResult := struct {
		Products []Product
		EventSource   fauna.EventSource
	}{}

	if err := data.Unmarshal(&queryResult); err != nil {
		panic(err)
	}

	fmt.Println("Existing products:")
	for _, product := range queryResult.Products {
		fmt.Println(product)
	}

	events, err := client.Stream(queryResult.EventSource)
	if err != nil {
		panic(err)
	}
	defer events.Close()

	fmt.Println("Products from streaming:")
	var event fauna.Event
	for {
		err := events.Next(&event)
		if err != nil {
			panic(err)
		}

		switch event.Type {
		case fauna.AddEvent, fauna.UpdateEvent, fauna.RemoveEvent:
			var product Product
			if err = event.Unmarshal(&product); err != nil {
				panic(err)
			}
			fmt.Println(product)
		}
	}
}

You can also pass a query that produces an event source directly to StreamFromQuery().

type Product struct {
	Name			string	`fauna:"name"`
	Description		string	`fauna:"description"`
	Price			int	`fauna:"price"`
}

func main() {
	client := fauna.NewClient(
		"FAUNA_SECRET",
		fauna.DefaultTimeouts(),
	)

	streamQuery, _ := fauna.FQL("Product.all().eventSource()", nil)
	events, err := client.Stream(streamQuery)
	if err != nil {
		panic(err)
	}
	defer events.Close()

	var event fauna.Event
	for {
		err := events.Next(&event)
		if err != nil {
			panic(err)
		}

		switch event.Type {
		case fauna.AddEvent, fauna.UpdateEvent, fauna.RemoveEvent:
			var product Product
			if err = event.Unmarshal(&product); err != nil {
				panic(err)
			}
			fmt.Println(product)
		}
	}
}

Stream options

Both Stream() and StreamFromQuery() accept StreamOptFn functions as arguments.

Use StreamStartTime() to restart a stream at a specific timestamp:

streamQuery, _ := fauna.FQL(`Product.all().eventSource()`, nil)
tenMinutesAgo := time.Now().Add(-10 * time.Minute)

client.StreamFromQuery(streamQuery, nil, fauna.StreamStartTime(tenMinutesAgo))

Use EventCursor() to resume a stream after a disconnect:

streamQuery, _ := fauna.FQL(`Product.all().toStream()`, nil)
client.StreamFromQuery(streamQuery, nil, fauna.EventCursor("abc2345=="))

For supported functions, see StreamOptFn in the API reference.

Debug logging

To enable debug logging, set the FAUNA_DEBUG environment variable to an integer for the value of the desired slog level:

  • slog.LevelInfo logs all HTTP responses from Fauna.

  • slog.LevelDebug includes the HTTP request body. The Authorization header is not redacted.

For Go versions before 1.21, the driver uses a log.Logger. For 1.21+, the driver uses the slog.Logger.

You can optionally define your own Logger. For an example, see CustomLogger in logging_slog_test.go`.

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!