O que é?
PostgREST é um servidor open-source escrito em Haskell que lê o schema do seu Postgres e gera uma API REST automaticamente. Você não escreve nenhuma linha de controller ou handler.
O mapeamento é direto:
| Postgres | HTTP |
|---|---|
Tabela tasks | Endpoint /tasks |
Coluna title | Filtro ?title=eq.foo |
FK tasks.author_id → users | Embed ?select=*,author:users(*) |
| View | Endpoint read-only (ou writable com triggers) |
| Function | Endpoint /rpc/<function> |
Você cria a tabela. PostgREST detecta. API existe.
Por que importa?
- Zero código de CRUD. Quanto tempo você passou escrevendo controllers
list/get/create/update/delete? PostgREST elimina isso. - RLS aplicada automaticamente. Cada request herda o JWT — não tem onde "esquecer" autorização.
- Performance comparável a query direta. PostgREST gera SQL otimizado e usa connection pooling — overhead da camada REST é minúsculo.
- Schema é a fonte da verdade. Mudou a tabela? API muda junto. Sem migrar 2 lugares.
- Auto-discovery. Cliente pode ver o OpenAPI completo em
GET /— útil pra clients gerados automaticamente.
Como funciona no SuperDB
O PostgREST roda como serviço separado no SuperDB, conectado ao seu Postgres. Ele:
- Recebe HTTP request com JWT no header
Authorization - Valida assinatura do JWT contra o secret do projeto
- Inicia transação Postgres com
SET ROLE= role do JWT (authenticatedouanon) - Seta
SET request.jwt.claimspra RLS ler - Traduz a URL pra SQL e executa
- Retorna JSON
- Commit / rollback
O db.from(...) do SDK é só uma camada que monta a URL:
// SDK
db.from('tasks').select('id, title').eq('done', false).limit(10)
// URL gerada
GET /tasks?select=id,title&done=eq.false&limit=10
// SQL executado
SELECT id, title FROM tasks WHERE done = false LIMIT 10
-- + filtros RLS adicionados pelo Postgres
Quando usar (e quando não)
| Operação | PostgREST? | Alternativa |
|---|---|---|
| CRUD simples | Sim — é pra isso | — |
| Filtros, ordenação, paginação | Sim, rico | — |
| Join via FK | Embed automático | — |
| Upsert | ?on_conflict=... | — |
| Full-text search | Sim, via ?col=fts.termo | View com tsvector |
| Agregação (sum, avg) | Não direto | SQL function (/rpc/) |
| Transação multi-tabela complexa | Não cabe na URL | SQL function (/rpc/) |
| Lógica condicional / loop | REST não é Turing-complete | SQL function ou app backend |
Regra de bolso: se cabe em uma SELECT simples ou um INSERT/UPDATE direto, usa PostgREST. Se precisa de BEGIN/COMMIT ou múltiplas queries coordenadas, vira SQL function e chama via db.rpc().
Exemplos práticos
1. SELECT com filtro
GET /tasks?select=id,title,due_at&done=eq.false&due_at=lte.2026-12-31&order=due_at.asc&limit=20
Authorization: Bearer eyJhbGciOi...
const { data } = await db
.from('tasks')
.select('id, title, due_at')
.eq('done', false)
.lte('due_at', '2026-12-31')
.order('due_at', { ascending: true })
.limit(20)
2. INSERT
POST /tasks
Content-Type: application/json
Prefer: return=representation
{ "title": "Comprar café", "due_at": "2026-05-20" }
3. Join automático por FK
Se você tem posts.author_id → users.id como FK:
GET /posts?select=id,title,author:users(name,avatar_url)
[
{
"id": "p1",
"title": "Lançamento",
"author": { "name": "Felipe", "avatar_url": "..." }
}
]
Armadilhas comuns
- Limit padrão é 1000. Pediu sem
limit? Recebe no máximo 1000 rows. Sempre pagine ou seja explícito. count=exactem tabela grande é lento. PostgREST roda umSELECT count(*)que escaneia tudo. Pra tabela com milhões de rows, usecount=estimated.- Sem FK, sem embed. Join automático depende de FK definida.
posts.author_idsemREFERENCES users(id)não permite?select=*,author:users(*). - Order em coluna não indexada é lento.
?order=created_at.descem tabela com 1M rows sem índice emcreated_at= full scan. - OR não é trivial.
?or=(status.eq.open,priority.eq.high)— sintaxe específica, não é o que você pensa. - Modificar schema invalida cache. PostgREST faz cache do schema na inicialização. Adicionou tabela? Mande
NOTIFY pgrst, 'reload schema';ou aguarde (SuperDB faz isso automaticamente). - Coluna gerada (
GENERATED ALWAYS) bloqueia INSERT. Tente passar valor pra ela = erro 400. Use?select=col1,col2no INSERT pra ignorar. - Não tem WebSocket. PostgREST é REST puro. Pra updates ao vivo, use o Realtime (que escuta WAL e empurra pro client).
Não exponha tudo. PostgREST expõe todas as tabelas do schema por padrão. Se você tem internal_audit_log que não quer público, ou cria em outro schema (não exposto), ou REVOKE select da role anon / authenticated.
Termos relacionados
- API REST — referência completa da sintaxe
- Cookbook: PostgREST filtros — todos os operadores
- RLS — onde a autorização acontece
- Service-role vs anon — qual JWT use no SDK
- PostgREST.org — docs upstream