Firestore

gRPC · port 8088

Configuration

Set the emulator host environment variable:

$ export FIRESTORE_EMULATOR_HOST=localhost:8088
# or use: eval $(localgcp env)

The Go, Python, Java, and Node.js Firestore client libraries all respect this env var.

Go SDK example

Create a document, read it back, and run a query:

package main

import (
    "context"
    "fmt"
    "log"

    "cloud.google.com/go/firestore"
)

func main() {
    ctx := context.Background()

    // FIRESTORE_EMULATOR_HOST=localhost:8088 must be set
    client, err := firestore.NewClient(ctx, "my-project")
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // Create a document
    _, err = client.Collection("users").Doc("alice").Set(ctx, map[string]interface{}{
        "name":  "Alice",
        "age":   30,
        "tags":  []string{"admin", "dev"},
    })
    if err != nil {
        log.Fatal(err)
    }

    // Read it back
    doc, err := client.Collection("users").Doc("alice").Get(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Name: %s\n", doc.Data()["name"])

    // Query: find users older than 25
    iter := client.Collection("users").
        Where("age", ">", 25).
        OrderBy("age", firestore.Asc).
        Documents(ctx)
    defer iter.Stop()

    for {
        doc, err := iter.Next()
        if err != nil {
            break
        }
        fmt.Printf("  %s (age %v)\n", doc.Data()["name"], doc.Data()["age"])
    }
}

Real-time listeners

localgcp supports the Listen streaming RPC, which powers onSnapshot in the client libraries. Changes to documents are pushed to listeners in real time:

// Listen for changes to a document
snapIter := client.Collection("users").Doc("alice").Snapshots(ctx)
defer snapIter.Stop()

for {
    snap, err := snapIter.Next()
    if err != nil {
        break
    }
    fmt.Printf("Document changed: %v\n", snap.Data())
}

// Listen for changes to a collection query
queryIter := client.Collection("users").
    Where("age", ">", 25).
    Snapshots(ctx)
defer queryIter.Stop()

for {
    snap, err := queryIter.Next()
    if err != nil {
        break
    }
    for _, change := range snap.Changes {
        fmt.Printf("  %s: %v\n", change.Doc.Ref.ID, change.Doc.Data())
    }
}

Query operators

Supported query operators in the Where clause:

// IN query
client.Collection("users").Where("name", "in", []string{"Alice", "Bob"})

// ARRAY_CONTAINS
client.Collection("users").Where("tags", "array-contains", "admin")

// ARRAY_CONTAINS_ANY
client.Collection("users").Where("tags", "array-contains-any", []string{"admin", "ops"})

// NOT_IN
client.Collection("users").Where("name", "not-in", []string{"Charlie"})

Transactions

localgcp supports Firestore transactions with begin, commit, and rollback:

err = client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
    doc, err := tx.Get(client.Collection("accounts").Doc("alice"))
    if err != nil {
        return err
    }

    balance := doc.Data()["balance"].(int64)
    return tx.Set(client.Collection("accounts").Doc("alice"), map[string]interface{}{
        "balance": balance + 100,
    })
})

Not yet supported