Simple Go ORM for Google/Firebase Cloud Firestore

Overview

pipeline status coverage report Go Report Card GoDoc

go-firestorm

Go ORM (Object-relational mapping) for Google Cloud Firestore.

Goals

  1. Easy to use
  2. Non intrusive
  3. Non exclusive
  4. Fast

Features

  • Basic CRUD operations
  • Search
  • Concurrent requests
  • Transactions
  • Configurable auto load of references
  • Handles cyclic references
  • Sub collections
  • Supports embedded/anonymous structs
  • Supports unexported fields
  • Custom mappers between fields and types
  • Caching
  • Supports Google App Engine - 2. Gen (go version >= 1.11)

Getting Started

Prerequisites

go get -u github.com/jschoedt/go-firestorm

Setup

  1. Setup a Firestore client
  2. Create a firestorm client and supply the names of the id and parent fields of your model structs. Parent is optional. The id field must be a string but can be called anything.
...
client, _ := app.Firestore(ctx)
fsc := firestorm.New(client, "ID", "")
  1. Optional. For optimal caching to work consider adding the CacheHandler
http.HandleFunc("/", firestorm.CacheHandler(otherHandler))

Basic CRUD example

Note: Recursive Create/Delete is not supported and must be called on every entity. So to create an A->B relation. Create B first so the B.ID has been created and the create A.

type Car struct {
	ID         string
	Make       string
	Year       time.Time
}
car := &Car{}
car.Make = "Toyota"
car.Year, _ = time.Parse(time.RFC3339, "2001-01-01T00:00:00.000Z")

// Create the entity
fsc.NewRequest().CreateEntities(ctx, car)()

if car.ID == "" {
    t.Errorf("car should have an auto generated ID")
}

// Read the entity by ID
otherCar := &Car{ID:car.ID}
fsc.NewRequest().GetEntities(ctx, otherCar)()
if otherCar.Make != "Toyota" {
    t.Errorf("car should have name: Toyota but was: %s", otherCar.Make)
}
if otherCar.Year != car.Year {
    t.Errorf("car should have same year: %s", otherCar.Year)
}

// Update the entity
car.Make = "Jeep"
fsc.NewRequest().UpdateEntities(ctx, car)()

otherCar := &Car{ID:car.ID}
fsc.NewRequest().GetEntities(ctx, otherCar)()
if otherCar.Make != "Jeep" {
    t.Errorf("car should have name: Jeep but was: %s", otherCar.Make)
}

// Delete the entity
fsc.NewRequest().DeleteEntities(ctx, car)()

otherCar = &Car{ID:car.ID}
if err := fsc.NewRequest().GetEntities(ctx, otherCar)(); err == nil {
    t.Errorf("We expect a NotFoundError")
}

More examples

Search

Create a query using the firebase client

car := &Car{}
car.ID = "testID"
car.Make = "Toyota"

fsc.NewRequest().CreateEntities(ctx, car)()

query := fsc.Client.Collection("Car").Where("make", "==", "Toyota")

result := make([]Car, 0)
if err := fsc.NewRequest().QueryEntities(ctx, query, &result)(); err != nil {
    t.Errorf("car was not found by search: %v", car)
}

if result[0].ID != car.ID || result[0].Make != car.Make {
    t.Errorf("entity did not match original entity : %v", result)
}

More examples

Concurrent requests

All CRUD operations are asynchronous and return a future func that when called will block until the operation is done.

NOTE: the state of the entities is undefined until the future func returns.

car := &Car{Make:"Toyota"}

// Create the entity which returns a future func
future := fsc.NewRequest().CreateEntities(ctx, car)

// ID is not set
if car.ID != "" {
	t.Errorf("car ID should not have been set yet")
}

// do some more work

// blocks and waits for the database to finish
future()

// now the car has been saved and the ID has been set
if car.ID == "" {
    t.Errorf("car should have an auto generated ID now")
}

More examples

Transactions

Transactions are simply done in a function using the transaction context

car := &Car{Make: "Toyota"}

fsc.DoInTransaction(ctx, func(transCtx context.Context) error {

    // Create the entity in the transaction using the transCtx
    fsc.NewRequest().CreateEntities(transCtx, car)()

    // Using the transCtx we can load the entity as it is saved in the session context
    otherCar := &Car{ID:car.ID}
    fsc.NewRequest().GetEntities(transCtx, otherCar)()
    if otherCar.Make != car.Make {
        t.Errorf("The car should have been saved in the transaction context")
    }

    // Loading using an other context (request) will fail as the car is not created until the func returns successfully
    if err := fsc.NewRequest().GetEntities(ctx, &Car{ID:car.ID})(); err == nil {
        t.Errorf("We expect a NotFoundError")
    }
})

// Now we can load the car as the transaction has been committed
otherCar := &Car{ID:car.ID}
fsc.NewRequest().GetEntities(ctx, otherCar)()
if otherCar.Make != "Toyota" {
    t.Errorf("car should have name: Toyota but was: %s", otherCar.Make)
}

More examples

Configurable auto load of references

Use the req.SetLoadPaths("fieldName") to auto load a particular field or req.SetLoadPaths(firestorm.AllEntities) to load all fields.

Load an entity path by adding multiple paths eg.: path->to->field

fsc.NewRequest().SetLoadPaths("path", "path.to", "path.to.field").GetEntities(ctx, car)()

More examples

Help

Help is provided in the go-firestorm User Group

You might also like...
A better ORM for Go, based on non-empty interfaces and code generation.

reform A better ORM for Go and database/sql. It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as oppose

100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood.

go-queryset 100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood. Contents

A better ORM for Go, based on non-empty interfaces and code generation.
A better ORM for Go, based on non-empty interfaces and code generation.

A better ORM for Go and database/sql. It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as opposed to interface{}, type system sidestepping, and runtime reflection. It will be kept simple.

An orm library support nGQL for Golang

norm An ORM library support nGQL for Golang. Overview Build insert nGQL by struct / map (Support vertex, edge). Parse Nebula execute result to struct

golang orm

korm golang orm, 一个简单易用的orm, 支持嵌套事务 安装 go get github.com/wdaglb/korm go get github.com/go-sql-driver/mysql 支持数据库 mysql https://github.com/go-sql-driv

Golang ORM with focus on PostgreSQL features and performance

go-pg is in a maintenance mode and only critical issues are addressed. New development happens in Bun repo which offers similar functionality but works with PostgreSQL, MySQL, and SQLite.

GorosePro(Go ORM),这个版本是Gorose的专业版

GorosePro(Go ORM),这个版本是Gorose的专业版

The fantastic ORM library for Golang, aims to be developer friendly

GORM The fantastic ORM library for Golang, aims to be developer friendly. Overview Full-Featured ORM Associations (Has One, Has Many, Belongs To, Many

 SQL mapper ORM framework for Golang
SQL mapper ORM framework for Golang

SQL mapper ORM framework for Golang English 中文 Please read the documentation website carefully when using the tutorial. DOC Powerful Features High Per

Comments
  • panic: reflect: call of reflect.Value.NumField on zero Value

    panic: reflect: call of reflect.Value.NumField on zero Value

    Hello 👋

    Thank you for an excellent library! I've had some issues with panic: reflect: call of reflect.Value.NumField on zero Value.

    Guessing it's caused by passing empty/nilled fields? Any tips to how I can fix it?

    opened by cobraz 7
  • How to use cache?

    How to use cache?

    Hello,

    thanks you for this simple and straightforward package!

    I'm successfully using in one of my current projects. The DB interface works perfectly fine, however I have difficulties getting the caching layer to work.

    I have written a little middleware that injects the CacheHandler.

    func CacheMiddleware(next http.Handler) http.Handler {
    	return firestorm.CacheHandler(next.ServeHTTP)
    }
    

    and adding it to my router (go-chi):

    r.Use(db.CacheMiddleware)
    r.Get("/", indexHandler)
    

    From each of my HTTP handlers, I'm then passing the context onto the db functions:

    func countryHandler(w http.ResponseWriter, r *http.Request) {
        ...
    	countryData, err := LoadCountryData(r.Context())
    	if err != nil {
    		log.Printf("Failed to load  data: %s", err)
    		http.Error(w, "Internal Server Error", 500)
    		return
    	}
        ...
    }
    
    // var Client *firestorm.FSClient was initialized somewhere else
    func LoadCountryData(ctx context.Context) (*types.Country, error) {
    	var c = &types.Country{ID: slug}
    	_, err := Client.NewRequest().SetLoadPaths(firestorm.AllEntities).GetEntities(ctx, c)()
    	if IsNotFoundError(err) {
    		return nil, nil
    	} else if err != nil {
    		return nil, err
    	}
    
    	return c, nil
    }
    

    Nevertheless, I still get the message "Warning. Consider adding the CacheHandler middleware for the session cache to work" and subsequent database calls don't seem to be faster.

    Am I using the cache (in)correctly? Is there a way I can verify its behavior?

    opened by jacksgt 6
  • go-firestorm User Group

    go-firestorm User Group

    The link for go-firestorm User Group does not work, or rather I do not have access to it. Is it meant to be non-public or is something wrong?

    Thanks again for an awesome project, @jschoedt!

    opened by cobraz 1
Owner
Jens Kjær Schødt
Jens Kjær Schødt
Using-orm-with-db - Trying to use ORM to work with PostgreSQL

Using ORM with db This repo contains the training (rough) code, and possibly not

Pavel V 5 Jul 31, 2022
ORM for Cloud Spanner to boost your productivity

ORM for Cloud Spanner to boost your productivity ??

Kanji Hirata 18 Sep 20, 2022
A simple wrapper around sql.DB to help with structs. Not quite an ORM.

go-modeldb A simple wrapper around sql.DB to help with structs. Not quite an ORM. Philosophy: Don't make an ORM Example: // Setup require "modeldb" db

Jae Kwon 17 Nov 16, 2019
Simple and Powerful ORM for Go, support mysql,postgres,tidb,sqlite3,mssql,oracle, Moved to https://gitea.com/xorm/xorm

xorm HAS BEEN MOVED TO https://gitea.com/xorm/xorm . THIS REPOSITORY WILL NOT BE UPDATED ANY MORE. 中文 Xorm is a simple and powerful ORM for Go. Featur

null 6.6k Sep 27, 2022
Simple and performant ORM for sql.DB

Simple and performant ORM for sql.DB Main features are: Works with PostgreSQL, MySQL, SQLite. Selecting into a map, struct, slice of maps/structs/vars

Uptrace 1.5k Sep 28, 2022
beedb is a go ORM,support database/sql interface,pq/mysql/sqlite

Beedb ❗ IMPORTANT: Beedb is being deprecated in favor of Beego.orm ❗ Beedb is an ORM for Go. It lets you map Go structs to tables in a database. It's

astaxie 707 Sep 6, 2022
ORM-ish library for Go

We've moved! gorp is now officially maintained at: https://github.com/go-gorp/gorp This fork was created when the project was moved, and is provided f

James Cooper 52 Aug 23, 2022
Database agnostic ORM for Go

If you are looking for something more lightweight and flexible, have a look at jet For questions, suggestions and general topics visit the group. Inde

Erik Aigner 697 Sep 9, 2022
QBS stands for Query By Struct. A Go ORM.

Qbs Qbs stands for Query By Struct. A Go ORM. 中文版 README ChangeLog 2013.03.14: index name has changed to {table name}_{column name}. For existing appl

Evan Zhou 549 Sep 9, 2022
Generate a Go ORM tailored to your database schema.

SQLBoiler is a tool to generate a Go ORM tailored to your database schema. It is a "database-first" ORM as opposed to "code-first" (like gorm/gorp). T

Volatile Technologies Inc. 5.2k Sep 26, 2022