Skip to content

Tutorial: Relatório com Supabase/PostgREST

Neste tutorial você criará um relatório que busca dados de uma API Supabase (PostgREST) usando o PostgRestDataSourceResolver, com autenticação via ConnectionEntry.http e headers configuráveis.

O que você vai aprender:

  • Declarar um data source externo com driver: "postgrest"
  • Usar ConnectionEntry.http com defaultHeaders para autenticação
  • Montar queries com a sintaxe PostgREST (select, filtros, order)
  • Configurar o PostgRestDataSourceResolver do pacote sulfite_datasources
  • Usar um proxy para Flutter Web

Pré-requisitos

  1. Sulfite instalado (Instalação →)
  2. Instale os pacotes necessários:
yaml
dependencies:
  sulfite_core: ^<versão>
  sulfite_datasources: ^<versão>
  sulfite_report_manager: ^<versão>  # para ConnectionEntry e Registry
  1. Uma API Supabase ou PostgREST acessível (ou use o proxy de demo: https://sulfite-api-example.onrender.com/supabase)

Passo 1 — Configurar a conexão

Crie uma ConnectionEntry.http com a URL base da API e os headers de autenticação:

dart
import 'package:sulfite_core/sulfite_core.dart';
import 'package:sulfite_datasources/sulfite_datasources.dart';

final registry = MutableConnectionRegistry([
  ConnectionEntry.http(
    name: 'supabase_prod',
    baseUrl: 'https://xyz.supabase.co/rest/v1',
    defaultHeaders: {
      'apikey': 'your-anon-key',
      'Authorization': 'Bearer your-anon-key',
    },
  ),
]);

Os defaultHeaders são injetados automaticamente em todas as requisições feitas por essa conexão. Para Supabase, os headers apikey e Authorization são obrigatórios.

Passo 2 — Declarar o data source no relatório

O data source referencia a conexão pelo connectionRef e define a query na sintaxe PostgREST:

json
{
  "dataSources": [
    {
      "id": "vendas",
      "type": "list",
      "source": "external",
      "driver": "postgrest",
      "connectionRef": "supabase_prod",
      "query": "vendas?select=id,produto,valor,data&order=data.desc",
      "schema": {
        "id": "integer",
        "produto": "string",
        "valor": "number",
        "data": "string"
      }
    }
  ]
}

Anatomia da query PostgREST

O campo query usa a sintaxe de query string do PostgREST:

vendas?select=id,produto,valor,data&status=eq.active&valor=gt.100&order=data.desc&limit=50
│      │                            │                 │             │              │
│      └─ colunas                   └─ filtro =       └─ filtro >   └─ ordenação   └─ limite
└─ nome da tabela

Operadores disponíveis:

OperadorExemploDescrição
eqstatus=eq.activeIgual a
neqstatus=neq.cancelledDiferente de
gt, gtevalor=gt.100Maior que / Maior ou igual
lt, ltevalor=lt.1000Menor que / Menor ou igual
likenome=like.*silva*Busca textual (case-sensitive)
ilikenome=ilike.*silva*Busca textual (case-insensitive)
instatus=in.(active,pending)Está na lista
orderorder=data.descOrdenação
limitlimit=50Limitar resultados
selectselect=id,nome,totalSelecionar colunas

Passo 3 — Criar o resolver e gerar o relatório

dart
import 'package:sulfite_core/sulfite_core.dart';
import 'package:sulfite_datasources/sulfite_datasources.dart';

final engine = SulfiteEngineImpl();
final report = await engine.parseReport(reportJson);

final resolver = PostgRestDataSourceResolver(registry: registry);

final pdfBytes = await engine.generate(
  report,
  resolvers: [resolver],
  format: 'pdf',
);

await File('vendas.pdf').writeAsBytes(pdfBytes);

Ordem dos resolvers

Se o relatório tiver data sources REST e PostgREST, coloque o PostgRestDataSourceResolver antes do RestDataSourceResolver, pois ambos lidam com HTTP mas o PostgREST monta queries otimizadas:

dart
resolvers: [
  PostgRestDataSourceResolver(registry: registry),
  RestDataSourceResolver(),
],

Passo 4 — Usar com o Studio

Para preview em tempo real no Studio, passe os resolvers no SulfiteDesigner:

dart
SulfiteDesigner(
  initialReport: report,
  dataSourceResolvers: [
    PostgRestDataSourceResolver(registry: registry),
    RestDataSourceResolver(),
  ],
)

Gerenciar conexões no app

Use o ConnectionManagerWidget para permitir que o usuário configure conexões HTTP visualmente — incluindo o editor de headers:

dart
ConnectionManagerWidget(registry: registry)

O formulário de conexão HTTP exibe campos para nome, URL base e um editor de headers (pares chave-valor). Os headers são salvos no defaultHeaders da ConnectionEntry.http.

Modo proxy (Flutter Web)

Em Flutter Web, acessar a API Supabase diretamente pode expor a anon key no client-side. O padrão recomendado é usar um proxy no seu backend:

Flutter Web  →  Seu Backend (proxy)  →  Supabase PostgREST
              headers didáticos         headers reais injetados

Configuração do proxy

No backend (ex: Dart com Relic), crie um handler que:

  1. Recebe a requisição do client
  2. Injeta os headers reais do Supabase (apikey, Authorization com a chave real)
  3. Encaminha para a API Supabase
  4. Retorna a resposta ao client

Usar o proxy no relatório

json
{
  "id": "vendas",
  "type": "list",
  "source": "external",
  "driver": "postgrest",
  "connectionRef": "supabase_proxy",
  "query": "vendas?select=*&order=data.desc"
}
dart
final registry = MutableConnectionRegistry([
  ConnectionEntry.http(
    name: 'supabase_proxy',
    baseUrl: 'https://my-api.onrender.com/supabase',
    defaultHeaders: {
      'apikey': 'demo',
      'Authorization': 'Bearer demo',
    },
  ),
]);

O proxy recebe os headers didáticos e os substitui pelos reais antes de encaminhar ao Supabase.

Modo URL inline (alternativa)

Para protótipos rápidos, é possível embutir a URL e headers diretamente no data source:

json
{
  "id": "vendas",
  "type": "list",
  "source": "external",
  "url": "https://xyz.supabase.co/rest/v1",
  "headers": {
    "apikey": "your-anon-key",
    "Authorization": "Bearer your-anon-key"
  },
  "query": "vendas?select=*&order=data.desc"
}

Segurança

Evite embutir credenciais no JSON em produção. Use connectionRef com um ConnectionRegistry gerenciado pela aplicação.

Próximo passo

Sulfite do 🇧🇷 para o mundo © 2026 Rafael S. Pinheiro