Go client driver

Version: 2.0.2 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/v2

Basic usage

The following application:

  • Initializes a client instance to connect to Fauna

  • Composes a basic FQL query using an FQL template

  • Runs the query using Query()

package main

import (
	"fmt"

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

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 Query HTTP API 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. In the Dashboard, create a database and navigate to it.

  3. In the upper left pane of Dashboard’s Explorer page, click the demo database, and click the Keys tab.

  4. Click Create Key.

  5. Choose a Role of Server.

  6. Click Save.

  7. 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.

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 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.

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)

Passed variables are encoded to an appropriate type and passed to Fauna’s HTTP API. This helps prevent injection attacks.

Subqueries

You can use native variables to pass an FQL query to another FQL query. This lets you create reusable subqueries:

// Create a reusable FQL subquery
func getProduct(name string) *fauna.Query {
	subquery, _ := fauna.FQL(`
		Product.byName(${name}).first()
	`, map[string]any{"name": name})
	return subquery
}

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

	// Use the subquery in another FQL query
	query, _ := fauna.FQL(`
		let product = ${getProduct}
		product?.update({
    	name: "pizza pie"
  	})
	`, map[string]any{"getProduct": getProduct("pizza")})

	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 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 statistics

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

  • 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 fauna.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 fauna.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 fauna.MaxBackoff configuration function to set the maximum time between retries. Similarly, use fauna.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 streaming

The driver supports Event streams.

Start a stream

To get a stream token, append set.toStream() or set.changeOn() to a set from a supported source.

To start and subscribe to the stream, pass a query that produces a stream token to Stream():

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().toStream()", 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)
		}
	}
}

In query results, the driver represents stream tokens as fauna.Stream values.

To start a stream from a query result, call Subscribe() on a fauna.Stream value. 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(),
			Stream: products.toStream()
		}
	`, nil)

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

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

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

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

	events, err := client.Subscribe(queryResult.Stream)
	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)
		}
	}
}

Stream options

The Client configuration sets default query options for Stream(). To override these options, see Query options.

The Subscribe() method accepts a fauna.StartTime function. You can use fauna.StartTime to restart a stream after disconnection.

streamQuery, _ := fauna.FQL(`Product.all().toStream()`, nil)
client.Subscribe(streamQuery, fauna.StartTime(1710968002310000))

For supported functions, see StreamOptFn in the API reference.

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!