Skip to content
This repository was archived by the owner on Sep 2, 2024. It is now read-only.

Commit 53e3021

Browse files
committed
fixed pkg go doc and added code examples + README improvements
1 parent 0cb870b commit 53e3021

File tree

4 files changed

+132
-92
lines changed

4 files changed

+132
-92
lines changed

README.md

Lines changed: 29 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ applications.
2323
You can think of it as a lightweight Firebase replacement you may self-host. Less
2424
vendor lock-in, and your data stays in your control.
2525

26+
You may use its building blocks from one or a combination of:
27+
28+
* Client-side JavaScript
29+
* Server-side client libraries (Node, Go, Python)
30+
* Import a Go package directly in your Go programs
31+
2632
### Table of content
2733

2834
* [Import as Go package](#import-as-go-package)
@@ -40,97 +46,45 @@ vendor lock-in, and your data stays in your control.
4046
## Import as Go package
4147

4248
As of v1.4.1 StaticBackend offers an importable Go package removing the need
43-
to self-host the backend API while keeping all functionalities from your Go
44-
program.
49+
to self-host the backend API separately while keeping all functionalities from
50+
your Go program.
51+
52+
### Installing
53+
54+
```sh
55+
$ go get github.com/staticbackendhq/core/backend
56+
```
4557

4658
### Example usage
4759

4860
```go
49-
package main
50-
51-
import (
52-
"fmt"
53-
"log"
54-
"time"
61+
// using the cache & pub/sub
62+
backend.Cache.Set("key", "value")
5563

56-
"github.com/staticbackendhq/core/backend"
57-
"github.com/staticbackendhq/core/config"
58-
"github.com/staticbackendhq/core/model"
59-
)
64+
msg := model.Command{Type: "chan_out", Channel: "#lobby", Data: "hello world"}
65+
backend.Cache.Publish(msg)
6066

67+
// use the generic Collection for strongly-typed CRUD and querying
6168
type Task struct {
62-
ID string `json:"id"`
63-
AccountID string `json:"accountId"`
64-
Title string `json:"title"`
65-
Done bool `json:"done"`
66-
}
67-
68-
func main() {
69-
cfg := config.AppConfig{
70-
AppEnv: "dev",
71-
Port: "8099",
72-
DatabaseURL: "mem",
73-
DataStore: "mem",
74-
LocalStorageURL: "http://localhost:8099",
75-
}
76-
// this initialized the backend from config
77-
backend.Setup(cfg)
78-
79-
// You can access most of the raw building blocks directly from the
80-
// package exported variables (which are interface) initialized via the
81-
// config you pass
82-
83-
// Set a value in the cache
84-
backend.Cache.Set("key", "value")
85-
86-
// For strongly-typed database functionalities
87-
88-
// in a real app you'd have a middleware handling identifying and fetching a
89-
// model.DatabaseConfig for the current request. For this README sample
90-
// we're faking a real call which would fail if ran.
91-
base, _ := backend.DB.FindDatabase("current-web-request-db-id")
92-
93-
// let's create a user in this new Database
94-
usr := backend.Membership(base)
95-
sessionToken, user, err := usr.CreateAccountAndUser("[email protected]", "passwd123456", 100)
96-
if err != nil {
97-
log.Fatal(err)
98-
}
99-
100-
// we simulate having authenticating this user (from middleware normally)
101-
// in a real app you'd store the sessionToken in your user's
102-
// app (cookie, local storage, etc)
103-
// You'd need to have a middleware responsible of validating this token
104-
// and have a model.Auth for the rest of your pipeline.
105-
auth := model.Auth{
106-
AccountID: user.AccountID,
107-
UserID: user.ID,
108-
Email: user.Email,
109-
Role: user.Role,
110-
Token: user.Token,
111-
}
112-
113-
// we create a strongly-typed instance of the "tasks" collection
114-
// so we have full CRUD / Queries functions for your Task type
115-
tasks := backend.Collection[Task](auth, base, "tasks")
116-
117-
newTask := Task{Title: "my task", Done: false}
118-
119-
newTask, err = tasks.Create(newTask)
120-
if err != nil {
121-
log.Fatal(err)
122-
}
69+
ID string `json:"id"`
70+
Title string `json:"title"`
12371
}
72+
// auth is the currently authenticated user performing the action.
73+
// base is the current tenant's database to execute action
74+
// "tasks" is the collection name
75+
tasks := backend.Collection(auth, base, "tasks")
76+
newTask, err := tasks.Create(Task{Title: "testing"})
77+
// newTask.ID is filled with the unique ID of the created task in DB
12478
```
12579

80+
View a [full example in the doc](https://pkg.go.dev/github.com/staticbackendhq/core/backend#example-package).
81+
12682
### Documentation for the `backend` Go package
12783

12884
Refer to the
12985
[Go documentation](https://pkg.go.dev/github.com/staticbackendhq/core/backend)
13086
to know about all functions and examples.
13187

132-
133-
13488
## What can you build
13589

13690
I built StaticBackend with the mindset of someone tired of writing the same code

backend/backend.go

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,55 @@
11
// Package backend allows a Go program to import a standard Go package
2-
// instead of self-hosting the backend API.
2+
// instead of self-hosting the backend API in a separate web server.
33
//
44
// You need to call the [Setup] function to initialize all services passing
55
// a [github.com/staticbackendhq/core/config.AppConfig]. You may create
66
// environment variables and load the config directly by confing.Load function.
77
//
8+
// // this sample uses the in-memory database provider built-in
9+
// // you can use PostgreSQL or MongoDB
10+
// cfg := config.AppConfig{
11+
// AppEnv: "dev",
12+
// DataStore: "mem",
13+
// DatabaseURL: "mem",
14+
// LocalStorageURL: "http://localhost:8099",
15+
// }
16+
// backend.Setup(cfg)
17+
//
818
// The building blocks of [StaticBackend] are exported as variables and can be
919
// used directly accessing their interface's functions. For instance
10-
// to use the Volatilizer (cache and pub/sub) you'd use the [Cache] variable:
20+
// to use the [github.com/staticbackendhq/core/cache.Volatilizer] (cache and
21+
// pub/sub) you'd use the [Cache] variable:
1122
//
1223
// if err := backend.Cache.Set("key", "value"); err != nil {
1324
// return err
1425
// }
1526
// val, err := backend.Cache.Get("key")
1627
//
1728
// The available services are as follow:
18-
// 1. Cache: caching and pub/sub
19-
// 2. DB: a raw Persister instance (see below for when to use it)
20-
// 3. Filestore: raw blob storage
21-
// 4. Emailer: to send emails
22-
// 5. Config: the config that was passed to Setup
23-
// 6. Log: logger
29+
// - [Cache]: caching and pub/sub
30+
// - [DB]: a raw [github.com/staticbackendhq/core/database.Persister] instance (see below for when to use it)
31+
// - [Filestore]: raw blob storage
32+
// - [Emailer]: to send emails
33+
// - [Config]: the config that was passed to [Setup]
34+
// - [Log]: logger
2435
//
2536
// You may see those services as raw building blocks that give you the most
26-
// flexibility. For easy of use, this package wraps important / commonly used
37+
// flexibility to build on top.
38+
//
39+
// For easy of use, this package wraps important / commonly used
2740
// functionalities into more developer friendly implementations.
2841
//
29-
// For instance, the [Membership] function wants a model.DatabaseConfig and allows
30-
// the caller to create account and user as well as reseting password etc.
42+
// For instance, the [Membership] function wants a
43+
// [github.com/staticbackendhq/core/model.DatabaseConfig] and allows the caller
44+
// to create account and user as well as reseting password etc.
3145
//
3246
// usr := backend.Membership(base)
3347
// sessionToken, user, err := usr.CreateAccountAndUser("[email protected]", "passwd", 100)
3448
//
3549
// To contrast, all of those can be done from your program by using the [DB]
3650
// ([github.com/staticbackendhq/core/database.Persister]) data store, but for
3751
// convenience this package offers easier / ready-made functions for common
38-
// use-cases. Example:
52+
// use-cases. Example for database CRUD and querying:
3953
//
4054
// tasks := backend.Collection[Task](auth, base, "tasks")
4155
// newTask, err := tasks.Create(Task{Name: "testing"})
@@ -44,16 +58,51 @@
4458
// input/output are properly typed, it's a generic type.
4559
//
4660
// [StaticBackend] makes your Go web application multi-tenant by default.
47-
// For this reason you must supply a model.DatabaseConfig and sometimes a
48-
// model.Auth (user performing the actions) to the different parts of the system
49-
// so the data and security are applied to the right tenant, account and user.
61+
// For this reason you must supply a
62+
// [github.com/staticbackendhq/core/model.DatabaseConfig] and (database) and
63+
// sometimes a [github.com/staticbackendhq/core/model.Auth] (user performing
64+
// the actions) to the different parts of the system so the data and security
65+
// are applied to the right tenant, account and user.
5066
//
5167
// You'd design your application around one or more tenants. Each tenant has
5268
// their own database. It's fine to have one tenant/database. In that case
5369
// you might create the tenant and its database and use the database ID in
5470
// an environment variable. From a middleware you might load the database from
5571
// this ID.
5672
//
73+
// // if you'd want to use SB's middleware (it's not required)
74+
// // you use whatever you like for your web handlers and middleware.
75+
// // SB is a library not a framework.
76+
// func DetectTenant() middleware.Middleware {
77+
// return func(next http.Handler) http.Handler {
78+
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
79+
// // check for presence of a public DB ID
80+
// // this can come from cookie, URL query param
81+
// key := r.Header.Get("DB-ID")
82+
// // for multi-tenant, DB ID can be from an env var
83+
// if len(key) == 0 {
84+
// key = os.Getenv("SINGLE_TENANT_DBID")
85+
// }
86+
// var curDB model.DatabaseConfig
87+
// if err := backend.Cache.GetTyped(key, &curDB); err != nil {
88+
// http.Error(w, err.Error(), http.StatusBadRequest)
89+
// return
90+
// }
91+
// curDB, err := backend.DB.FindDatabase(key)
92+
// // err != nil return HTTP 400 Bad request
93+
// err = backend.Cache.SetTyped(key, curDB)
94+
// // add the tenant's DB in context for the rest of
95+
// // your pipeline to have the proper DB.
96+
// ctx := r.Context()
97+
// ctx = context.WithValue(ctx, ContextBase, curDB)
98+
// next.ServeHTTP(w, r.WithContext(ctx)))
99+
// })
100+
// }
101+
// }
102+
//
103+
// You'd create a similar middleware for adding the current user into the
104+
// request context.
105+
//
57106
// If you ever decide to switch to a multi-tenant design, you'd already be all
58107
// set with this middleware, instead of getting the ID from the env variable,
59108
// you'd define how the user should provide their database ID.

backend/database.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ type Database[T any] struct {
1414
col string
1515
}
1616

17-
// Collection returns a ready to use Database to perform operations on a specific type
17+
// Collection returns a ready to use Database to perform DB operations on a
18+
// specific type. You must pass auth which is the user performing the action and
19+
// the tenant's database in which this action will be executed. The col is the
20+
// name of the collection.
21+
//
22+
// Collection name only accept alpha-numberic values and cannot start with a digit.
1823
func Collection[T any](auth model.Auth, base model.DatabaseConfig, col string) Database[T] {
1924
return Database[T]{
2025
auth: auth,
@@ -184,7 +189,17 @@ func fromDoc(doc map[string]any, v interface{}) error {
184189
return json.Unmarshal(b, v)
185190
}
186191

187-
// BuildQueryFilters helps building the proper slice of filters
192+
// BuildQueryFilters helps building the proper slice of filters.
193+
//
194+
// The arguments must be divided by 3 and has the following order:
195+
//
196+
// field name | operator | value
197+
//
198+
// backend.BuildQueryFilters("done", "=", false)
199+
//
200+
// This would filter for the false value in the "done" field.
201+
//
202+
// Supported operators: =, !=, >, <, >=, <=, in, !in
188203
func BuildQueryFilters(p ...any) (q [][]any, err error) {
189204
if len(p)%3 != 0 {
190205
err = errors.New("parameters should all have 3 values for each criteria")

backend/exmaples_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package backend_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/staticbackendhq/core/backend"
7+
)
8+
9+
func ExampleBuildQueryFilters() {
10+
filters, err := backend.BuildQueryFilters(
11+
"done", "=", true,
12+
"effort", ">=", 15,
13+
)
14+
if err != nil {
15+
fmt.Println(err)
16+
return
17+
}
18+
19+
fmt.Println(filters)
20+
21+
// Output: [[done = true] [effort >= 15]]
22+
}

0 commit comments

Comments
 (0)