├── LICENSE ├── README.md ├── client.go ├── client_test.go ├── go.mod ├── go.sum └── test └── remote_client.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Supabase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An isomorphic Go client for Supabase. 2 | 3 | ## Features 4 | 5 | - [ ] Integration with [Supabase.Realtime](https://github.com/supabase-community/realtime-go) 6 | - Realtime listeners for database changes 7 | - [x] Integration with [Postgrest](https://github.com/supabase-community/postgrest-go) 8 | - Access your database using a REST API generated from your schema & database functions 9 | - [x] Integration with [Gotrue](https://github.com/supabase-community/gotrue-go) 10 | - User authentication, including OAuth, ***email/password***, and native sign-in 11 | - [x] Integration with [Supabase Storage](https://github.com/supabase-community/storage-go) 12 | - Store files in S3 with additional managed metadata 13 | - [x] Integration with [Supabase Edge Functions](https://github.com/supabase-community/functions-go) 14 | - Run serverless functions on the edge 15 | 16 | ## Quickstart 17 | 18 | 1. To get started, create a new project in the [Supabase Admin Panel](https://app.supabase.io). 19 | 2. Grab your Supabase URL and Supabase Public Key from the Admin Panel (Settings -> API Keys). 20 | 3. Initialize the client! 21 | 22 | *Reminder: `supabase-go` has some APIs that require the `service_key` rather than the `public_key` (for instance: the administration of users, bypassing database roles, etc.). If you are using the `service_key` **be sure it is not exposed client side.** Additionally, if you need to use both a service account and a public/user account, please do so using a separate client instance for each.* 23 | 24 | ## Documentation 25 | 26 | ### Get Started 27 | 28 | First of all, you need to install the library: 29 | 30 | ```sh 31 | go get github.com/supabase-community/supabase-go 32 | ``` 33 | 34 | Then you can use 35 | 36 | ```go 37 | client, err := supabase.NewClient(API_URL, API_KEY, &supabase.ClientOptions{}) 38 | if err != nil { 39 | fmt.Println("cannot initalize client", err) 40 | } 41 | data, count, err := client.From("countries").Select("*", "exact", false).Execute() 42 | ``` 43 | 44 | ### Use authenticated client 45 | 46 | ```go 47 | 48 | client, err := supabase.NewClient(API_URL, API_KEY, &supabase.ClientOptions{}) 49 | if err != nil { 50 | fmt.Println("cannot initalize client", err) 51 | } 52 | client.SignInWithEmailPassword(USER_EMAIL, USER_PASSWORD) 53 | 54 | ``` 55 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package supabase 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "time" 7 | 8 | "github.com/supabase-community/functions-go" 9 | "github.com/supabase-community/gotrue-go" 10 | "github.com/supabase-community/gotrue-go/types" 11 | postgrest "github.com/supabase-community/postgrest-go" 12 | storage_go "github.com/supabase-community/storage-go" 13 | ) 14 | 15 | const ( 16 | REST_URL = "/rest/v1" 17 | STORAGE_URL = "/storage/v1" 18 | AUTH_URL = "/auth/v1" 19 | FUNCTIONS_URL = "/functions/v1" 20 | ) 21 | 22 | type Client struct { 23 | // Why is this a private field?? 24 | rest *postgrest.Client 25 | Storage *storage_go.Client 26 | // Auth is an interface. We don't need a pointer to an interface. 27 | Auth gotrue.Client 28 | Functions *functions.Client 29 | options clientOptions 30 | } 31 | 32 | type clientOptions struct { 33 | url string 34 | headers map[string]string 35 | } 36 | 37 | type ClientOptions struct { 38 | Headers map[string]string 39 | Schema string 40 | } 41 | 42 | // NewClient creates a new Supabase client. 43 | // url is the Supabase URL. 44 | // key is the Supabase API key. 45 | // options is the Supabase client options. 46 | func NewClient(url, key string, options *ClientOptions) (*Client, error) { 47 | 48 | if url == "" || key == "" { 49 | return nil, errors.New("url and key are required") 50 | } 51 | 52 | headers := map[string]string{ 53 | "Authorization": "Bearer " + key, 54 | "apikey": key, 55 | } 56 | 57 | if options != nil && options.Headers != nil { 58 | for k, v := range options.Headers { 59 | headers[k] = v 60 | } 61 | } 62 | 63 | client := &Client{} 64 | client.options.url = url 65 | // map is pass by reference, so this gets updated by rest of function 66 | client.options.headers = headers 67 | 68 | var schema string 69 | if options != nil && options.Schema != "" { 70 | schema = options.Schema 71 | } else { 72 | schema = "public" 73 | } 74 | 75 | client.rest = postgrest.NewClient(url+REST_URL, schema, headers) 76 | client.Storage = storage_go.NewClient(url+STORAGE_URL, key, headers) 77 | // ugly to make auth client use custom URL 78 | tmp := gotrue.New(url, key) 79 | client.Auth = tmp.WithCustomGoTrueURL(url + AUTH_URL) 80 | client.Functions = functions.NewClient(url+FUNCTIONS_URL, key, headers) 81 | 82 | return client, nil 83 | } 84 | 85 | // Wrap postgrest From method 86 | // From returns a QueryBuilder for the specified table. 87 | func (c *Client) From(table string) *postgrest.QueryBuilder { 88 | return c.rest.From(table) 89 | } 90 | 91 | // Wrap postgrest Rpc method 92 | // Rpc returns a string for the specified function. 93 | func (c *Client) Rpc(name, count string, rpcBody interface{}) string { 94 | return c.rest.Rpc(name, count, rpcBody) 95 | } 96 | 97 | func (c *Client) SignInWithEmailPassword(email, password string) (types.Session, error) { 98 | token, err := c.Auth.SignInWithEmailPassword(email, password) 99 | if err != nil { 100 | return types.Session{}, err 101 | } 102 | c.UpdateAuthSession(token.Session) 103 | 104 | return token.Session, err 105 | } 106 | 107 | func (c *Client) SignInWithPhonePassword(phone, password string) (types.Session, error) { 108 | token, err := c.Auth.SignInWithPhonePassword(phone, password) 109 | if err != nil { 110 | return types.Session{}, err 111 | } 112 | c.UpdateAuthSession(token.Session) 113 | return token.Session, err 114 | } 115 | 116 | func (c *Client) EnableTokenAutoRefresh(session types.Session) { 117 | go func() { 118 | attempt := 0 119 | expiresAt := time.Now().Add(time.Duration(session.ExpiresIn) * time.Second) 120 | 121 | for { 122 | sleepDuration := (time.Until(expiresAt) / 4) * 3 123 | if sleepDuration > 0 { 124 | time.Sleep(sleepDuration) 125 | } 126 | 127 | // Refresh the token 128 | newSession, err := c.RefreshToken(session.RefreshToken) 129 | if err != nil { 130 | attempt++ 131 | if attempt <= 3 { 132 | log.Printf("Error refreshing token, retrying with exponential backoff: %v", err) 133 | time.Sleep(time.Duration(1< github.com/roja/postgrest-go v0.0.11 21 | 22 | replace github.com/supabase-community/functions-go => github.com/roja/functions-go v0.0.2 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 4 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= 6 | github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= 7 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 8 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/roja/functions-go v0.0.2 h1:fh+1XHtZ7HdJJAwHoJCyT6IHIHXIBpQ6fcS4dWDCKtI= 12 | github.com/roja/functions-go v0.0.2/go.mod h1:nnIju6x3+OZSojtGQCQzu0h3kv4HdIZk+UWCnNxtSak= 13 | github.com/roja/postgrest-go v0.0.11 h1:vGgsfL4+Tvkpbukneo4fPy/43gbeWSSlKmum9+pwDCI= 14 | github.com/roja/postgrest-go v0.0.11/go.mod h1:cw6LfzMyK42AOSBA1bQ/HZ381trIJyuui2GWhraW7Cc= 15 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 16 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 17 | github.com/supabase-community/gotrue-go v1.2.0 h1:Zm7T5q3qbuwPgC6xyomOBKrSb7X5dvmjDZEmNST7MoE= 18 | github.com/supabase-community/gotrue-go v1.2.0/go.mod h1:86DXBiAUNcbCfgbeOPEh0PQxScLfowUbYgakETSFQOw= 19 | github.com/supabase-community/storage-go v0.7.0 h1:cJ8HLbbnL54H5rHPtHfiwtpRwcbDfA3in9HL/ucHnqA= 20 | github.com/supabase-community/storage-go v0.7.0/go.mod h1:oBKcJf5rcUXy3Uj9eS5wR6mvpwbmvkjOtAA+4tGcdvQ= 21 | github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= 22 | github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= 23 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 24 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | -------------------------------------------------------------------------------- /test/remote_client.go: -------------------------------------------------------------------------------- 1 | // This is basic example for postgrest-go library usage. 2 | // For now this example is represent wanted syntax and bindings for library. 3 | // After core development this test files will be used for CI tests. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "os" 11 | 12 | "github.com/joho/godotenv" 13 | "github.com/supabase-community/supabase-go" 14 | ) 15 | 16 | func main() { 17 | err := godotenv.Load() 18 | if err != nil { 19 | log.Fatal("Error loading .env file") 20 | } 21 | 22 | projectURL := os.Getenv("SUPABASE_URL") 23 | anonKey := os.Getenv("SUPABASE_ANON_KEY") 24 | email := os.Getenv("TESTUSER") 25 | password := os.Getenv("TESTUSERPASSWORD") 26 | 27 | client, err := supabase.NewClient(projectURL, anonKey, nil) 28 | if err != nil { 29 | fmt.Println("cannot initalize client", err) 30 | } 31 | client.SignInWithEmailPassword(email, password) 32 | 33 | // 34 | rooms, _, err := client.From("rooms").Select("*", "", false).ExecuteString() 35 | if err != nil { 36 | panic(err) 37 | } 38 | fmt.Println(rooms) 39 | 40 | } 41 | --------------------------------------------------------------------------------