Documentation
¶
Overview ¶
Package backend allows a Go program to import a standard Go package instead of self-hosting the backend API in a separate web server.
You need to call the Setup function to initialize all services passing a github.com/staticbackendhq/core/config.AppConfig. You may create environment variables and load the config directly by confing.Load function.
// You may load the configuration from environment variables // config.Current = config.LoadConfig() // this sample uses the in-memory database provider built-in // you can use PostgreSQL or MongoDB config.Current = config.AppConfig{ AppEnv: "dev", DataStore: "mem", DatabaseURL: "mem", LocalStorageURL: "http://localhost:8099", } backend.Setup(config.Current)
The building blocks of StaticBackend are exported as variables and can be used directly accessing their interface's functions. For instance to use the github.com/staticbackendhq/core/cache.Volatilizer (cache and pub/sub) you'd use the Cache variable:
if err := backend.Cache.Set("key", "value"); err != nil { return err } val, err := backend.Cache.Get("key")
The available services are as follow:
- Cache: caching and pub/sub
- DB: a raw github.com/staticbackendhq/core/database.Persister instance (see below for when to use it)
- Filestore: raw blob storage
- Emailer: to send emails
- Config: the config that was passed to Setup
- Log: logger
You may see those services as raw building blocks that give you the most flexibility to build on top.
For easy of use, this package wraps important / commonly used functionalities into more developer friendly implementations.
For instance, the Membership function wants a github.com/staticbackendhq/core/model.DatabaseConfig and allows the caller to create account and user as well as reseting password etc.
usr := backend.Membership(base) sessionToken, user, err := usr.CreateAccountAndUser("me@test.com", "passwd", 100)
To contrast, all of those can be done from your program by using the DB (github.com/staticbackendhq/core/database.Persister) data store, but for convenience this package offers easier / ready-made functions for common use-cases. Example for database CRUD and querying:
tasks := backend.Collection[Task](auth, base, "tasks") newTask, err := tasks.Create(Task{Name: "testing"})
The Collection returns a strongly-typed structure where functions input/output are properly typed, it's a generic type.
StaticBackend makes your Go web application multi-tenant by default. For this reason you must supply a github.com/staticbackendhq/core/model.DatabaseConfig and (database) and sometimes a github.com/staticbackendhq/core/model.Auth (user performing the actions) to the different parts of the system so the data and security are applied to the right tenant, account and user.
You'd design your application around one or more tenants. Each tenant has their own database. It's fine to have one tenant/database. In that case you might create the tenant and its database and use the database ID in an environment variable. From a middleware you might load the database from this ID.
// if you'd want to use SB's middleware (it's not required) // you use whatever you like for your web handlers and middleware. // SB is a library not a framework. func DetectTenant() middleware.Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check for presence of a public DB ID // this can come from cookie, URL query param key := r.Header.Get("DB-ID") // for multi-tenant, DB ID can be from an env var if len(key) == 0 { key = os.Getenv("SINGLE_TENANT_DBID") } var curDB model.DatabaseConfig if err := backend.Cache.GetTyped(key, &curDB); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } curDB, err := backend.DB.FindDatabase(key) // err != nil return HTTP 400 Bad request err = backend.Cache.SetTyped(key, curDB) // add the tenant's DB in context for the rest of // your pipeline to have the proper DB. ctx := r.Context() ctx = context.WithValue(ctx, ContextBase, curDB) next.ServeHTTP(w, r.WithContext(ctx))) }) } }
You'd create a similar middleware for adding the current user into the request context.
If you ever decide to switch to a multi-tenant design, you'd already be all set with this middleware, instead of getting the ID from the env variable, you'd define how the user should provide their database ID.
Example ¶
package main import ( "fmt" "log" "time" "github.com/staticbackendhq/core/backend" "github.com/staticbackendhq/core/config" "github.com/staticbackendhq/core/model" ) type EntityDemo struct { ID string `json:"id"` AccountID string `json:"accountId"` Name string `json:"name"` Status string `json:"status"` } func (x EntityDemo) String() string { return fmt.Sprintf("%s | %s", x.Name, x.Status) } func main() { // we initiate config.Current as type config.AppConfig // using the in-memory database engine. // You'd use PostgreSQL or Mongo in your real configuration // Also note that the config package has a LoadConfig() function that loads // config from environment variables i.e.: // config.Current = LoadConfig() config.Current = config.AppConfig{ AppEnv: "dev", Port: "8099", DatabaseURL: "mem", DataStore: "mem", LocalStorageURL: "http://localhost:8099", NoFullTextSearch: true, } // the Setup function will initialize all services based on config backend.Setup(config.Current) // StaticBackend is multi-tenant by default, so you'll minimaly need // at least one Tenant with their Database for your app // // In a real application you need to decide if your customers will // have their own Database (multi-tenant) or not. cus := model.Tenant{ Email: "new@tenant.com", IsActive: true, Created: time.Now(), } cus, err := backend.DB.CreateTenant(cus) if err != nil { fmt.Println(err) return } base := model.DatabaseConfig{ TenantID: cus.ID, Name: "random-name-here", IsActive: true, Created: time.Now(), } base, err = backend.DB.CreateDatabase(base) if err != nil { fmt.Println(err) return } // let's create a user in this new Database // You'll need to create an model.Account and model.User for each of // your users. They'll need a session token to authenticate. usr := backend.Membership(base) // Role 100 is for root user, root user is your app's super user. // As the builder of your application you have a special user which can // execute things on behalf of other users. This is very useful on // background tasks were your app does not have the user's session token. sessionToken, user, err := usr.CreateAccountAndUser("user1@mail.com", "passwd123456", 100) if err != nil { log.Fatal(err) } fmt.Println(len(sessionToken) > 10) // In a real application, you'd store the session token for this user // inside local storage and/or a cookie etc. On each request you'd // request this session token and authenticate this user via a middleware. // we simulate having authenticated this user (from middleware normally) auth := model.Auth{ AccountID: user.AccountID, UserID: user.ID, Email: user.Email, Role: user.Role, Token: user.Token, } // this is what you'd normally do in your web handlers to execute a request // we create a ready to use CRUD and Query collection that's typed with // our EntityDemo. In your application you'd get a Collection for your own // type, for instance: Product, Order, Customer, Blog, etc. // // Notice how we're passing the auth: current user and base: current database // so the operations are made from the proper user and in the proper DB/Tenant. entities := backend.Collection[EntityDemo](auth, base, "entities") // once we have this collection, we can perform database operations newEntity := EntityDemo{Name: "Go example code", Status: "new"} newEntity, err = entities.Create(newEntity) if err != nil { fmt.Println(err) return } fmt.Println(newEntity) // the Create function returned our EntityDemo with the ID and AccountID // filled, we can now update this record. newEntity.Status = "updated" newEntity, err = entities.Update(newEntity.ID, newEntity) if err != nil { fmt.Println(err) return } // let's fetch this entity via its ID to make sure our changes have // been persisted. check, err := entities.GetByID(newEntity.ID) if err != nil { fmt.Print(err) return } fmt.Println(check) }
Output: true Go example code | new Go example code | updated
Index ¶
- Variables
- func BuildQueryFilters(p ...any) (q [][]any, err error)
- func GetJWT(token string) ([]byte, error)
- func Setup(cfg config.AppConfig)
- type Database
- func (d Database[T]) BulkCreate(entities []T) error
- func (d Database[T]) Create(data T) (inserted T, err error)
- func (d Database[T]) Delete(id string) (int64, error)
- func (d Database[T]) GetByID(id string) (entity T, err error)
- func (d Database[T]) IncrementValue(id, field string, n int) error
- func (d Database[T]) List(lp model.ListParams) (res PagedResult[T], err error)
- func (d Database[T]) Query(filters [][]any, lp model.ListParams) (res PagedResult[T], err error)
- func (d Database[T]) Update(id string, v any) (entity T, err error)
- func (d Database[T]) UpdateMany(filters [][]any, v any) (int64, error)
- type FileStore
- type MagicLinkData
- type PagedResult
- type SavedFile
- type User
- func (u User) Authenticate(email, password string) (string, error)
- func (u User) CreateAccountAndUser(email, password string, role int) ([]byte, model.User, error)
- func (u User) CreateUser(accountID, email, password string, role int) ([]byte, model.User, error)
- func (u User) GetAuthToken(tok model.User) (jwtBytes []byte, err error)
- func (u User) Register(email, password string) (string, error)
- func (u User) ResetPassword(email, code, password string) error
- func (u User) SetPasswordResetCode(email, code string) error
- func (u User) SetUserRole(email string, role int) error
- func (u User) SetupMagicLink(data MagicLinkData) error
- func (u User) UserSetPassword(email, oldpw, newpw string) error
- func (u User) ValidateMagicLink(email, code string) (string, error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // Config reflect the configuration received on Setup Config config.AppConfig // DB initialized Persister data store DB database.Persister // Emailer initialized Mailer for sending emails Emailer email.Mailer // Filestore initialized Storer for raw save/delete blob file Filestore storage.Storer // Cache initialized Volatilizer for cache and pub/sub Cache cache.Volatilizer Search *search.Search // Log initialized Logger for all logging Log *logger.Logger // Membership exposes Account and User functionalities like register, login, etc // account and user functionalities. Membership func(model.DatabaseConfig) User // Storage exposes file storage functionalities. It wraps the blob // storage as well as the database storage. Storage func(model.Auth, model.DatabaseConfig) FileStore // Scheduler to execute schedule jobs (only on PrimaryInstance) Scheduler *function.TaskScheduler )
All StaticBackend services (need to call Setup before using them).
Functions ¶
func BuildQueryFilters ¶
BuildQueryFilters helps building the proper slice of filters.
The arguments must be divided by 3 and has the following order:
field name | operator | value
backend.BuildQueryFilters("done", "=", false)
This would filter for the false value in the "done" field.
Supported operators: =, !=, >, <, >=, <=, in, !in
Example ¶
package main import ( "fmt" "github.com/staticbackendhq/core/backend" ) func main() { filters, err := backend.BuildQueryFilters( "done", "=", true, "effort", ">=", 15, ) if err != nil { fmt.Println(err) return } fmt.Println(filters) }
Output: [[done = true] [effort >= 15]]
Types ¶
type Database ¶
type Database[T any] struct { // contains filtered or unexported fields }
Database enables all CRUD and querying operations on a specific type
func Collection ¶
Collection returns a ready to use Database to perform DB operations on a specific type. You must pass auth which is the user performing the action and the tenant's database in which this action will be executed. The col is the name of the collection.
Collection name only accept alpha-numberic values and cannot start with a digit.
func (Database[T]) BulkCreate ¶
BulkCreate creates multiple records in the collection/repository
func (Database[T]) IncrementValue ¶
IncrementValue increments or decrements a specifc field from a collection/repository
func (Database[T]) List ¶
func (d Database[T]) List(lp model.ListParams) (res PagedResult[T], err error)
List returns records from a collection/repository using paging/sorting params
func (Database[T]) Query ¶
func (d Database[T]) Query(filters [][]any, lp model.ListParams) (res PagedResult[T], err error)
Query returns records that match with the provided filters.
type FileStore ¶
type FileStore struct {
// contains filtered or unexported fields
}
FileStore exposes file functions
type MagicLinkData ¶
type MagicLinkData struct { FromEmail string `json:"fromEmail"` FromName string `json:"fromName"` Email string `json:"email"` Subject string `json:"subject"` Body string `json:"body"` MagicLink string `json:"link"` }
MagicLinkData magic links for no-password sign-in
type PagedResult ¶
PageResult wraps a slice of type T with paging information
type User ¶
type User struct {
// contains filtered or unexported fields
}
User handles everything related to accounts and users inside a database
func (User) Authenticate ¶
Authenticate tries to authenticate an email/password and return a session token
func (User) CreateAccountAndUser ¶
CreateAccountAndUser creates an account with a user
func (User) CreateUser ¶
CreateUser creates a user for an Account
func (User) GetAuthToken ¶
GetAuthToken returns a session token for a user
func (User) ResetPassword ¶
ResetPassword resets the password of a matching email/code for a user
func (User) SetPasswordResetCode ¶
SetPasswordResetCode sets the password forget code for a user
func (User) SetUserRole ¶
SetUserRole changes the role of a user
func (User) SetupMagicLink ¶
func (u User) SetupMagicLink(data MagicLinkData) error
SetupMagicLink initialize a magic link and send the email to the user
func (User) UserSetPassword ¶
UserSetPassword password changes initiated by the user