Firebase Emulator Suite: Running golang/python/java firestore event triggers locally with the emulator
Tags: go, firestore, firebase, googlecloud
Published: 2020-09-08 20:35:20

---
title: Firebase Emulator Suite: Running golang/python/java firestore event triggers locally with the emulator
published: true
description: Awesome local firestore development with other google cloud function supported languages
tags: golang,firestore,firebase,googlecloud
//cover_image: https://direct_url_to_image.jpg
---

# Purpose

The firebase team has done a really awesome job at providing a rich and feature complete environment for running and testing out various products all locally (firestore emulator, real time database emulator, pub/sub emulator).

Most of the support/docs/guides out there mainly cover using the firebase-tools cli and using Node.Js.

This post is mainly to show that it's possible to recreate a nice local development setup with the other languages that GCP Cloud Functions supports, such as Java, Python, and Go.

> for the purpose of this write up, we will be using golang

## Setting up the emulator

Grab the latest firestore emulator with using [firebase-tools](https://www.npmjs.com/package/firebase-tools) cli by running `firebase setup:emulators:firestore`.

Invoke the help menu of the jar with `java -jar ~/.cache/firebase/emulators/cloud-firestore-emulator-v1.11.7.jar --help`, as of the time i am writing this the current version is v1.11.7 so thats what this sample will be with.

Run the emulator with `java -jar ~/.cache/firebase/emulators/cloud-firestore-emulator-v1.11.7.jar --functions_emulator localhost:6000` this will set the callback url for functions events to `localhost:6000` and thats where we will be running our local function at.

Register our function resource with the firestore emulator. We want to watch for all document writes to the root collection of `skills/{id}` and the name of our Function is called "WriteSkills" .

    curl --location --request PUT 'http://localhost:8080/emulator/v1/projects/dummy/triggers/WriteSkills' \
    --header 'Content-Type: application/json' \
    --data-raw '{
       "eventTrigger": {
           "resource": "projects/dummy/databases/(default)/documents/skills/{id}",
           "eventType": "providers/cloud.firestore/eventTypes/document.write",
           "service": "firestore.googleapis.com"
       }
    }'


As a quick schema reference you can use the following.

    curl --location --request PUT 'http://[HOST:PORT]/emulator/v1/projects/[PROJECT_ID]/triggers/[FUNCTION_NAME]' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "eventTrigger": {
            "resource": "projects/[PROJECT_ID]/databases/(default)/documents/[RESOURCE_PATH]",
            "eventType": [EVENT_TYPE],
            "service": "firestore.googleapis.com"
        }
    }'

the event types for firestore can be

    "providers/cloud.firestore/eventTypes/document.create"
    "providers/cloud.firestore/eventTypes/document.update"
    "providers/cloud.firestore/eventTypes/document.delete"
    "providers/cloud.firestore/eventTypes/document.write"

Our function code for golang will be

```go

package main

import (
    "cloud.google.com/go/firestore"
    "cloud.google.com/go/functions/metadata"
    "context"
    "fmt"
    "github.com/GoogleCloudPlatform/functions-framework-go/funcframework"
    "log"
    "time"
)

type FirestoreEvent struct {
    OldValue   FirestoreValue `json:"oldValue"`
    Value      FirestoreValue `json:"value"`
    UpdateMask struct {
        FieldPaths []string `json:"fieldPaths"`
    } `json:"updateMask"`
}

type FirestoreValue struct {
    CreateTime time.Time   `json:"createTime"`
    Fields     interface{} `json:"fields"`
    Name       string      `json:"name"`
    UpdateTime time.Time   `json:"updateTime"`
}

func main() {
    // create a firestore write event in a go routine that will be ran by the time local dev server is spun up
    go func() {
        ctx := context.Background()
        firestoreClient, err := firestore.NewClient(ctx, "dummy")
        if err != nil {
            log.Fatalf("firestore.NewClient: %v", err)
        }
        defer firestoreClient.Close()

        if _, err := firestoreClient.Collection("skills").NewDoc().Create(ctx, map[string]interface{}{
            "skill":     "Running",
            "timestamp": firestore.ServerTimestamp,
        }); err != nil {
            log.Fatalf("firestoreClient.Collection.NewDoc().Set: %v", err)
        }
    }()
    funcframework.RegisterEventFunction("/functions/projects/dummy/triggers/WriteSkills", WriteSkills)
    if err := funcframework.Start("6000"); err != nil {
        panic(err)
    }
}

func WriteSkills(ctx context.Context, e FirestoreEvent) error {
    meta, err := metadata.FromContext(ctx)
    if err != nil {
        return fmt.Errorf("metadata.FromContext: %v", err)
    }
    log.Printf("Function triggered by change to: %v", meta.Resource)
    log.Printf("Old value: %+v", e.OldValue)
    log.Printf("New value: %+v", e.Value)
    return nil
}
```

now we will run it by invoking

`export FIRESTORE_EMULATOR_HOST=localhost:8080 && go run .`

and we shall see an output of

```
Serving function...
2020/09/08 16:16:58 Function triggered by change to: &{firestore.googleapis.com projects/dummy/databases/(default)/documents/skills/3wUTxvMAawFlJPQmxwYv  }
2020/09/08 16:16:58 Old value: {CreateTime:0001-01-01 00:00:00 +0000 UTC Fields:<nil> Name: UpdateTime:0001-01-01 00:00:00 +0000 UTC}
2020/09/08 16:16:58 New value: {CreateTime:2020-09-08 20:16:58.461011 +0000 UTC Fields:map[skill:map[stringValue:Running] timestamp:map[timestampValue:2020-09-08T20:16:58.459Z]] Name:projects/dummy/databases/(default)/documents/skills/3wUTxvMAawFlJPQmxwYv UpdateTime:2020-09-08 20:16:58.461011 +0000 UTC}
```


## Util Time!

Finally i wrote a little util https://github.com/amammay/firebase-emu that will register your go function against the firestore emulator automatically and then add a entry into the funcframework for you. 

```go
package main

import (
	"cloud.google.com/go/firestore"
	"cloud.google.com/go/functions/metadata"
	"context"
	"fmt"
	"github.com/GoogleCloudPlatform/functions-framework-go/funcframework"
	"github.com/amammay/firebase-emu/fsemu"
	"log"
	"time"
)

func main() {

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

		firestoreClient, err := firestore.NewClient(ctx, "dummy")
		if err != nil {
			log.Fatalf("firestore.NewClient: %v", err)
		}
		defer firestoreClient.Close()

		if _, err := firestoreClient.Collection("skills").NewDoc().Create(ctx, map[string]interface{}{
			"skill":     "Running",
			"timestamp": firestore.ServerTimestamp,
		}); err != nil {
			log.Fatalf("firestoreClient.Collection.NewDoc().Set: %v", err)
		}
	}()
	event := fsemu.EmuResource{ProjectId: "dummy", Address: "http://localhost:8080"}
	emuRegisters := []fsemu.EmuRegister{{
		TriggerFn:    WriteSkills,
		TriggerType:  fsemu.FirestoreOnWrite,
		ResourcePath: "skills/{id}",
	}}

        //register firestore emulator resources
	if err := event.RegisterToEmu(emuRegisters); err != nil {
		panic(err)
	}
	if err := funcframework.Start("6000"); err != nil {
		panic(err)
	}
}

type FirestoreEvent struct {
	OldValue   FirestoreValue `json:"oldValue"`
	Value      FirestoreValue `json:"value"`
	UpdateMask struct {
		FieldPaths []string `json:"fieldPaths"`
	} `json:"updateMask"`
}

type FirestoreValue struct {
	CreateTime time.Time   `json:"createTime"`
	Fields     interface{} `json:"fields"`
	Name       string      `json:"name"`
	UpdateTime time.Time   `json:"updateTime"`
}

func WriteSkills(ctx context.Context, e FirestoreEvent) error {

	meta, err := metadata.FromContext(ctx)
	if err != nil {
		return fmt.Errorf("metadata.FromContext: %v", err)
	}
	log.Printf("Function triggered by change to: %v", meta.Resource)
	log.Printf("Old value: %+v", e.OldValue)
	log.Printf("New value: %+v", e.Value)
	return nil
}


```