Isomorphic Go client for PostgREST.
- stars
- 230
- watchers
- 4
- forks
- 44
- open issues
- 17
- Last updated
- 10/21/2026
Isomorphic Go client for PostgREST.
Important
Update, September 2026:
This codebase is not being actively maintained by Supabase or the community, but it's not the end of the road...
We're busy building an official Supabase SDK for Go that will make this repository and the module it publishes obsolete.
This thread has more information, as well as providing a venue for discussion in the meantime.
Golang client for PostgREST. The goal of this library is to make an "ORM-like" restful interface.
Full documentation can be found here.
go get github.com/supabase-community/postgrest-go
go get without a version installs the latest tagged release (currently v0.0.12). The main branch has a newer API with generics, structured responses, and context-aware Execute methods.
| Install command | API |
|---|---|
go get github.com/supabase-community/postgrest-go |
v0.0.12 — Select(columns, count, head), Execute() returns ([]byte, int64, error) |
go get github.com/supabase-community/postgrest-go@main |
main — Select(columns, opts), Execute(ctx) returns *PostgrestResponse |
The examples below (except Basic usage v0.0.12) require the main branch:
go get github.com/supabase-community/postgrest-go@main
If you installed the default release tag, use this API:
package main import ( "encoding/json" "fmt" "github.com/supabase-community/postgrest-go" ) func main() { client := postgrest.NewClient("http://localhost:3000/rest/v1", "public", nil) if client.ClientError != nil { panic(client.ClientError) } data, count, err := client.From("users").Select("*", "", false).Execute() if err != nil { panic(err) } fmt.Printf("count: %d\n", count) fmt.Println(string(data)) }
package main import ( "context" "encoding/json" "fmt" "github.com/supabase-community/postgrest-go" ) func main() { // Create a new client client := postgrest.NewClient("http://localhost:3000/rest/v1", "public", nil) if client.ClientError != nil { panic(client.ClientError) } // Select data response, err := client.From("users").Select("*", nil).Execute(context.Background()) if err != nil { panic(err) } if response.Error != nil { panic(response.Error) } // Print results data, _ := json.Marshal(response.Data) fmt.Println(string(data)) }
// Select with filters opts := &postgrest.SelectOptions{Count: "exact"} response, err := client. From("users"). Select("id, name, email", opts). Eq("status", "active"). Limit(10, nil). Order("name", &postgrest.OrderOptions{Ascending: true}). Execute(context.Background())
// Insert a single row insertOpts := &postgrest.InsertOptions{Count: "exact"} response, err := client. From("users"). Insert(map[string]interface{}{ "name": "John Doe", "email": "john@example.com", }, insertOpts). Execute(context.Background())
// Update rows updateOpts := &postgrest.UpdateOptions{Count: "exact"} response, err := client. From("users"). Update(map[string]interface{}{ "status": "inactive", }, updateOpts). Eq("id", 1). Execute(context.Background())
// Delete rows deleteOpts := &postgrest.DeleteOptions{Count: "exact"} response, err := client. From("users"). Delete(deleteOpts). Eq("status", "inactive"). Execute(context.Background())
// Upsert (insert or update) upsertOpts := &postgrest.UpsertOptions{ OnConflict: "email", IgnoreDuplicates: false, Count: "exact", } response, err := client. From("users"). Upsert(map[string]interface{}{ "email": "john@example.com", "name": "John Doe", }, upsertOpts). Execute(context.Background())
// Call a PostgreSQL function rpcOpts := &postgrest.RpcOptions{Count: "exact"} response, err := client. Rpc("get_user_by_email", map[string]interface{}{ "email": "john@example.com", }, rpcOpts). Execute(context.Background())
// Multiple filters response, err := client. From("users"). Select("*", nil). Eq("status", "active"). Gte("age", 18). Lte("age", 65). In("role", []interface{}{"admin", "user"}). Like("name", "%John%"). Execute(context.Background()) // Text search textSearchOpts := &postgrest.TextSearchOptions{ Type: "websearch", Config: "english", } response, err := client. From("posts"). Select("*", nil). TextSearch("content", "golang tutorial", textSearchOpts). Execute(context.Background())
// Get a single row response, err := client. From("users"). Select("*", nil). Eq("id", 1). Limit(1, nil). Single(). Execute(context.Background()) // Get a single row or null (maybeSingle) response, err := client. From("users"). Select("*", nil). Eq("id", 999). MaybeSingle(). Execute(context.Background())
// Unmarshal directly into a struct type User struct { ID int64 `json:"id"` Name string `json:"name"` Email string `json:"email"` } var users []User count, err := client. From("users"). Select("*", nil). Eq("status", "active"). ExecuteTo(context.Background(), &users)
// Switch to a different schema client = client.Schema("private") response, err := client.From("sensitive_data").Select("*", nil).Execute(context.Background())
response, err := client.From("users").Select("*", nil).Execute(context.Background()) if err != nil { // Network or parsing error panic(err) } if response.Error != nil { // PostgREST error fmt.Printf("Error: %s (Code: %s)\n", response.Error.Message, response.Error.Code) fmt.Printf("Details: %s\n", response.Error.Details) fmt.Printf("Hint: %s\n", response.Error.Hint) }
// Throw errors instead of returning them in response response, err := client. From("users"). Select("*", nil). ThrowOnError(). Execute(context.Background()) // If there's an error, it will be thrown here
NewClient(url, schema, headers) - Create a new clientFrom(table) - Start a query on a tableRpc(function, args, opts) - Call a PostgreSQL functionSchema(schema) - Switch to a different schemaSetApiKey(key) - Set API key headerSetAuthToken(token) - Set authorization tokenChangeSchema(schema) - Change schema for subsequent requestsSelect(columns, opts) - Select columnsInsert(values, opts) - Insert rowsUpdate(values, opts) - Update rowsUpsert(values, opts) - Upsert rowsDelete(opts) - Delete rowsEq(column, value) - EqualNeq(column, value) - Not equalGt(column, value) - Greater thanGte(column, value) - Greater than or equalLt(column, value) - Less thanLte(column, value) - Less than or equalLike(column, pattern) - Case-sensitive pattern matchIlike(column, pattern) - Case-insensitive pattern matchIs(column, value) - IS operator (for NULL checks)In(column, values) - IN operatorContains(column, value) - Contains (for arrays/jsonb)ContainedBy(column, value) - Contained byTextSearch(column, query, opts) - Full-text searchMatch(query) - Match multiple columnsNot(column, operator, value) - Negate operatorOr(filters, opts) - OR conditionOrder(column, opts) - Order resultsLimit(count, opts) - Limit resultsRange(from, to, opts) - Range resultsSingle() - Get single resultMaybeSingle() - Get single result or nullSelect(columns) - Select after insert/update/deleteCSV() - Return CSV formatGeoJSON() - Return GeoJSON formatExplain(opts) - Get query planRollback() - Rollback transactionMaxAffected(value) - Set max affected rowstype PostgrestResponse[T any] struct { Error *PostgrestError `json:"error,omitempty"` Data T `json:"data,omitempty"` Count *int64 `json:"count,omitempty"` Status int `json:"status"` StatusText string `json:"statusText"` }
Some tests are implemented to run against mocked Postgrest endpoints using httpmock. To run unit tests:
go test ./...Integration tests use testcontainers-go to spin up real PostgreSQL and PostgREST containers for testing. This ensures tests run against actual PostgREST instances.
Option 1: Using Testcontainers (Default)
Integration tests will automatically use testcontainers to start PostgreSQL and PostgREST containers:
go test -v -run TestIntegrationOption 2: Using Existing Services
If you have a running PostgREST instance, you can use it instead:
export POSTGREST_URL=http://localhost:3000 export USE_TESTCONTAINERS=false go test -v -run TestIntegration
Option 3: Using Docker Compose
You can also use the provided docker-compose.yaml to start services manually:
docker-compose up -d export POSTGREST_URL=http://localhost:3000 export USE_TESTCONTAINERS=false go test -v -run TestIntegration docker-compose down
The integration tests cover:
The integration tests use the schema defined in test/00-schema.sql and seed data from test/01-dummy-data.sql. These files define:
users table with various data typeschannels and messages tables for relationship testingkitchen_sink table for comprehensive type testingmovie, person, profile tables for foreign key testingpublic and personal) for schema switching testsMade with contrib.rocks.
This repo is licensed under the Apache License.
We are building the features of Firebase using enterprise-grade, open source products. We support existing communities wherever possible, and if the products don't exist we build them and open source them ourselves. Thanks to these sponsors who are making the OSS ecosystem better for everyone.