├── go.mod ├── .gitignore ├── test ├── integration │ └── functions_test.go └── functions │ └── hello │ └── index.ts ├── LICENSE ├── functions.go ├── README.md └── client.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/supabase-community/functions-go 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | #Ide Folder Ignore 18 | .idea 19 | 20 | #VSCode Folder Ignore 21 | .vscode 22 | 23 | # Supabase 24 | supabase 25 | 26 | .env -------------------------------------------------------------------------------- /test/integration/functions_test.go: -------------------------------------------------------------------------------- 1 | package functions_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/supabase-community/functions-go" 8 | ) 9 | 10 | const ( 11 | rawUrl = "https://your-supabase-url.co/functions/v1" 12 | token = "supbase-service-key" 13 | ) 14 | 15 | // TestHello tests the normal function 16 | func TestHello(t *testing.T) { 17 | client := functions.NewClient(rawUrl, token, nil) 18 | type Body struct { 19 | Name string `json:"name"` 20 | } 21 | b := Body{Name: "world"} 22 | resp, err := client.Invoke("hello", b) 23 | if err != nil { 24 | t.Fatalf("Invoke failed: %s", err) 25 | } 26 | fmt.Println(resp) 27 | } 28 | 29 | // TestErrorHandling tests the error handling of the functions client 30 | func TestErrorHandling(t *testing.T) { 31 | client := functions.NewClient(rawUrl, token, map[string]string{"custom-header": "custom-header"}) 32 | type Body struct { 33 | Name string `json:"name"` 34 | } 35 | b := Body{Name: "error"} 36 | resp, err := client.Invoke("hello", b) 37 | if err != nil { 38 | t.Fatalf("Invoke failed: %s", err) 39 | } 40 | fmt.Println(resp) 41 | } 42 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /functions.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | func (c *Client) Invoke(functionName string, payload interface{}) (string, error) { 12 | // Marshal the payload to JSON 13 | jsonData, err := json.Marshal(payload) 14 | if err != nil { 15 | return "", err 16 | } 17 | 18 | // Build the URL and create the request 19 | url := c.clientTransport.baseUrl.String() + "/" + functionName 20 | req, err := http.NewRequest("POST", url, bytes.NewReader(jsonData)) 21 | if err != nil { 22 | return "", err 23 | } 24 | 25 | // Set headers 26 | req.Header.Set("Content-Type", "application/json") 27 | req.Header.Set("Accept", "application/json") 28 | 29 | // Execute the request using the client's session 30 | resp, err := c.session.Do(req) 31 | if err != nil { 32 | return "", err 33 | } 34 | defer resp.Body.Close() 35 | 36 | // Read the response body 37 | responseBody, err := io.ReadAll(resp.Body) 38 | if err != nil { 39 | return "", err 40 | } 41 | 42 | // Check HTTP response status code 43 | if resp.StatusCode >= 400 { 44 | return "", fmt.Errorf("server responded with error: %s", resp.Status) 45 | } 46 | 47 | return string(responseBody), nil 48 | } 49 | -------------------------------------------------------------------------------- /test/functions/hello/index.ts: -------------------------------------------------------------------------------- 1 | // Follow this setup guide to integrate the Deno language server with your editor: 2 | // https://deno.land/manual/getting_started/setup_your_environment 3 | // This enables autocomplete, go to definition, etc. 4 | 5 | console.log("Hello from Functions!"); 6 | 7 | Deno.serve(async (req) => { 8 | const { name } = await req.json(); 9 | const data = { 10 | message: `Hello ${name}!`, 11 | }; 12 | 13 | if (name === "") { 14 | return new Response(JSON.stringify({ error: "Body request is invalid" }), { 15 | status: 400, 16 | headers: { "Content-Type": "application/json" }, 17 | }); 18 | } 19 | if (name === "error") { 20 | return Error("An error occurred"); 21 | } 22 | 23 | return new Response(JSON.stringify(data), { 24 | headers: { "Content-Type": "application/json" }, 25 | }); 26 | }); 27 | 28 | /* To invoke locally: 29 | 30 | 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) 31 | 2. Make an HTTP request: 32 | 33 | curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/hello' \ 34 | --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \ 35 | --header 'Content-Type: application/json' \ 36 | --data '{"name":"Functions"}' 37 | 38 | */ 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `functions-go` 2 | 3 | Golang client library to interact with Supabase Functions. 4 | 5 | ## Quick start 6 | 7 | ### Installation 8 | 9 | Install the package using: 10 | 11 | ```shell 12 | go get github.com/supabase-community/functions-go 13 | ``` 14 | 15 | ### Usage 16 | 17 | The following example demonstrates how to create a client, marshal data into JSON, and make a request to execute a function on the server. 18 | 19 | ```go 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | "log" 25 | 26 | "github.com/supabase-community/functions-go" 27 | ) 28 | 29 | func main() { 30 | client := functions.NewClient("https://abc.supabase.co/functions/v1", "", nil) 31 | 32 | // Define your data struct 33 | type Post struct { 34 | Title string `json:"title"` 35 | Content string `json:"content"` 36 | } 37 | post := Post{Title: "Hello, world!", Content: "This is a new post."} 38 | 39 | // Invoke the function with the post data 40 | response, err := client.Invoke("createPost", post) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | fmt.Println("Response from server:", response) 46 | } 47 | ``` 48 | 49 | This code will marshal the `Post` struct into JSON, send it to the `createPost` function, and print the response. 50 | 51 | ## License 52 | 53 | This repository is licensed under the MIT License. 54 | 55 | ## Credits 56 | 57 | For further inspiration and a JavaScript-based client, visit: 58 | 59 | - [functions-js](https://github.com/supabase/functions-js) 60 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | ) 7 | 8 | type Client struct { 9 | clientError error 10 | session http.Client 11 | clientTransport transport 12 | } 13 | 14 | type transport struct { 15 | header http.Header 16 | baseUrl url.URL 17 | } 18 | 19 | func (t transport) RoundTrip(request *http.Request) (*http.Response, error) { 20 | for headerName, values := range t.header { 21 | for _, val := range values { 22 | request.Header.Add(headerName, val) 23 | } 24 | } 25 | request.URL = t.baseUrl.ResolveReference(request.URL) 26 | return http.DefaultTransport.RoundTrip(request) 27 | } 28 | 29 | // TokenAuth sets authorization headers for subsequent requests. 30 | func (c *Client) TokenAuth(token string) *Client { 31 | c.clientTransport.header.Set("Authorization", "Bearer "+token) 32 | //c.clientTransport.header.Set("apikey", token) 33 | return c 34 | } 35 | 36 | /* 37 | NewClient constructs a new client given a URL to a functions-go instance 38 | 39 | Usage: 40 | 41 | client := functions.NewClient("https://abc.functions.supabase.co", "", nil) 42 | 43 | Inspired By Postgrest and storage-go. 44 | */ 45 | func NewClient(rawUrl string, token string, headers map[string]string) *Client { 46 | baseURL, err := url.Parse(rawUrl) 47 | if err != nil { 48 | return &Client{ 49 | clientError: err, 50 | } 51 | } 52 | 53 | t := transport{ 54 | header: http.Header{}, 55 | baseUrl: *baseURL, 56 | } 57 | 58 | c := Client{ 59 | session: http.Client{Transport: t}, 60 | clientTransport: t, 61 | } 62 | 63 | // Set required headers 64 | c.clientTransport.header.Set("Accept", "application/json") 65 | c.clientTransport.header.Set("Content-Type", "application/json") 66 | c.clientTransport.header.Set("Authorization", "Bearer "+token) 67 | 68 | // Optional headers [if exists] 69 | for key, value := range headers { 70 | c.clientTransport.header.Set(key, value) 71 | } 72 | 73 | return &c 74 | } 75 | --------------------------------------------------------------------------------