Isomorphic Go client for PostgREST.
227
stars
161
commits
Go
primary language
Sep 8, 2026
updated
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 TestIntegration
Option 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.
Go
93.3%
PLpgSQL
6.7%
Isomorphic Go client for PostgREST.
227
stars
161
commits
Go
primary language
Sep 8, 2026
updated
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 TestIntegration
Option 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.
Go
93.3%
PLpgSQL
6.7%