Go client driver for Fauna

Version: 2.0.1 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. The examples use Fauna’s demo data.

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

  • AWS Lambda

  • Netlify Functions

  • Vercel Functions

Installation

The driver is available on pkg.go.dev and the fauna/fauna-go GitHub repository. To install it, 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 Client.Query()

package main

import (
	"fmt"

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

func main() {
	// Initialize the client to connect to Fauna
	client := fauna.NewClient(
		"YOUR_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

To connect to Fauna, initialize a Client instance.

The NewDefaultClient() method initializes a client using:

  • A Fauna key, access token, or JWT in the FAUNA_SECRET environment variable

  • A Fauna endpoint 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(
	"YOUR_FAUNA_SECRET", // Your key, access token, or JWT
	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.

Run FQL queries

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

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

By default, Client.Query() uses query options from the Client configuration. You can pass options to Client.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(
		"YOUR_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 float64 `fauna:"price"`
}

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

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

	client.Query(query)
}

Pagination

Use Client.Paginate() to iterate a set that contains more than one page of results. Client.Paginate() accepts the same Query options as Client.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 := "YOUR_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{
		"name": "hello world query",
	}),
	fauna.QueryTimeout(time.Second*60),
)

The following table outlines supported NewClient() parameters.

Parameter Type Required Description

secret

string

Yes

Fauna key, access token, or JWT used to authorize requests.

timeouts

Yes

Struct containing timeouts for the client. See Timeouts.

configFns

Configuration functions for the client. See Configuration functions.

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(
	"YOUR_FAUNA_SECRET",
	timeouts,
)

For default timeouts, use fauna.DefaultTimeouts():

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

The following table outlines supported fields for a fauna.Timeouts struct and their defaults.

Field name Type Required Description

QueryTimeout

time.Duration

Maximum amount of time Fauna runs a query before marking it as failed. Defaults to 5 seconds.

ClientBufferTimeout

time.Duration

Additional time beyond QueryTimeout for the driver to abort if no response is received. Used to account for network latency. Defaults to 5 seconds.

ConnectionTimeout

time.Duration

Time to wait for the connection to Fauna to complete. Defaults to 5 seconds.

IdleConnectionTimeout

time.Duration

Maximum amount of time an idle (keep-alive) connection remains open before closing. Defaults to 5 seconds.

Configuration functions

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

client := fauna.NewClient(
	"YOUR_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{
		"name": "hello world query",
	}),
	fauna.QueryTimeout(time.Second*60),
)

The following table outlines supported ClientConfigFn functions. It includes the default used if a function isn’t provided.

Function Description Default

fauna.URL

Sets the URL for the Fauna endpoint. Accepts a string.

fauna.AdditionalHeaders

Sets additional HTTP headers to include in requests. Accepts a map[string]string of string-encoded headers.

{} (none)

fauna.Linearized

Accepts a bool. If true queries from the client are linearized, ensuring strict serialization of reads and writes.

false

fauna.MaxAttempts

Sets the maximum number of retry attempts for a query. Accepts an int.

3

fauna.MaxBackoff

Sets the maximum time to wait before retrying a query if the request returns a 429 HTTP status code. Accepts a time.Duration.

20 seconds

fauna.MaxContentionRetries

Sets the maximum number of times Fauna retries a query after a contention error. These are server-side retries. Accepts an int.

0 (no retries)

fauna.DefaultTypecheck

Accepts a bool. If true, enables type checking for queries.

If true, type checking must be enabled on the database.

The database’s type checking setting

fauna.QueryTags

Key-value tags used to identify the query. Accepts a map[string]string.

Query tags are included in query logs and the response body for successful queries. The tags are typically used for monitoring.

{} (none)

fauna.QueryTimeout

Sets the maximum amount of time Fauna runs a query before marking it as failed. Accepts a time.Duration.

The QueryTimeout value in the client’s timeouts argument. See Timeouts.

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:

  • Client.Query()

  • Client.Paginate()

  • Client.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...)

The following table outlines supported QueryOptFn functions.

Function Description

fauna.Tags

Key-value tags used to identify the query. Accepts a map[string]string.

Query tags are included in query logs and the response body for successful queries. The tags are typically used for monitoring.

fauna.Timeout

Sets the maximum amount of time Fauna runs a query before marking it as failed. Accepts a time.Duration.

fauna.Traceparent

Sets a W3C-compliant traceparent ID for the request. Accepts a string. If you provide an invalid traceparent ID, Fauna generates a valid one.

The traceparent ID is included in query logs. Traceparent IDs are typically used for monitoring.

fauna.Typecheck

Accepts a bool. If true, enables type checking for queries.

If true, type checking must be enabled on the database.

Event Streaming

The driver supports Event Streaming.

Start a stream

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

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

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

func main() {
	client := fauna.NewClient(
		"YOUR_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 Client.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			float64	`fauna:"price"`
}

func main() {
	client := fauna.NewClient(
		"YOUR_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 Client.Stream(). To override these options, see Query options.

The Client.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))
Function Description

fauna.StartTime

Sets the stream start time. Accepts an int64 representing the start time in microseconds since the Unix epoch.

The start time is typically the time the stream disconnected.

The start time must be later than the creation time of the stream token. The period between the stream restart and the start time argument can’t exceed the history_days value for source set’s collection. If a collection’s history_days is 0 or unset, the period can’t exceed 15 minutes.

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!