pg-api 0.3.7

A high-performance PostgreSQL REST API driver with rate limiting, connection pooling, and observability
pg-api-0.3.7 is not a library.

PG-API

Rust License: MIT Version

Um driver PostgreSQL de alta performance via REST API, construído em Rust.

PG-API permite executar queries SQL, transações e gerenciar bancos de dados PostgreSQL através de uma API REST simples e segura, com autenticação via API keys, rate limiting e connection pooling.


✨ Funcionalidades

  • 🔌 Query Execution - Execute queries SQL via HTTP POST
  • 📦 Batch Operations - Múltiplas queries em uma única requisição
  • 🔒 Transações - Suporte a transações ACID
  • 🔑 Autenticação - API keys com controle de acesso granular
  • ⏱️ Rate Limiting - Limitação de requisições por conta
  • 🏊 Connection Pooling - Pool de conexões PostgreSQL eficiente
  • 📊 Observabilidade - Métricas opcionais para OpenSearch
  • 📖 OpenAPI - Documentação automática em /docs

🔒 Segurança (v0.3.0)

  • Fail-closed: sem config/accounts.json válido o servidor não inicia (sem credenciais padrão).
  • API keys com hash SHA-256 em repouso; chaves legadas em texto puro continuam funcionando com aviso de rotação.
  • Gate SQL real: 1 statement por request, WITH com escrita exige permissão de escrita, COPY/controle transacional/comandos de sessão e funções perigosas (pg_read_file, dblink) negados.
  • options aplicadas: statement_timeout (default 60s, teto 300s) e read_only.
  • Transação real em /v1/transaction; batch/transação limitados a 50 itens.
  • CORS e limites (tamanho do body, timeout) lidos do server.json; rode atrás de TLS (ex.: nginx).

⬆️ Migrando de 0.2.x: garanta que accounts.json existe, rode com a mesma config, troque PG_API_KEY quando puder e trate os novos códigos MULTI_STATEMENT_NOT_ALLOWED, STATEMENT_NOT_ALLOWED e STATEMENT_TIMEOUT. Detalhes no CHANGELOG.


🚀 Quick Start

1. Instalação

# Clone o repositório
git clone https://gitlab.com/aerunti/pg-api.git
cd pg-api

# Build do projeto
cargo build --release

2. Configuração

# Crie a configuração de contas
mkdir -p config
cat > config/accounts.json << 'EOF'
{
  "accounts": [
    {
      "id": "acc_001",
      "name": "Default Account",
      "api_keys": ["sk_test_123456789"],
      "role": "owner",
      "rate_limit": 1000,
      "max_connections": 50,
      "databases": [
        {
          "database": "postgres",
          "username": "postgres",
          "password": "postgres",
          "permissions": ["SELECT", "INSERT", "UPDATE", "DELETE"]
        }
      ]
    }
  ]
}
EOF

# Configuração do servidor
cat > config/server.json << 'EOF'
{
  "host": "127.0.0.1",
  "port": 8580,
  "log_level": "info"
}
EOF

3. Execute

# Modo desenvolvimento
cargo run

# Modo produção
./target/release/pg-api

O servidor estará disponível em http://localhost:8580


📖 Documentação


🛠️ Exemplo de Uso

cURL

# Health check
curl http://localhost:8580/health

# Executar uma query
curl -X POST http://localhost:8580/v1/query \
  -H "X-API-Key: sk_test_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "database": "postgres",
    "query": "SELECT version()"
  }'

Python

import requests

client = requests.Session()
client.headers.update({"X-API-Key": "sk_test_123456789"})

# Executar query
response = client.post(
    "http://localhost:8580/v1/query",
    json={
        "database": "postgres",
        "query": "SELECT * FROM users WHERE id = $1",
        "params": [1]
    }
)
print(response.json())

Rust

use reqwest::Client;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let response = client
        .post("http://localhost:8580/v1/query")
        .header("X-API-Key", "sk_test_123456789")
        .json(&json!({
            "database": "postgres",
            "query": "SELECT version()"
        }))
        .send()
        .await?;
    
    println!("{}", response.text().await?);
    Ok(())
}

Veja mais exemplos em examples/.


📋 Endpoints Principais

Método Endpoint Descrição
GET /health Health check
POST /v1/query Executar query SQL
POST /v1/batch Executar batch de queries
POST /v1/transaction Executar transação
GET /v1/databases Listar databases
GET /v1/account Informações da conta
GET /docs Documentação Swagger UI

🏗️ Arquitetura

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Cliente   │────▶│   Axum/HTTP │────▶│  Middleware │
│  (cURL/JS)  │     │    Server   │     │(Auth/Rate)  │
└─────────────┘     └─────────────┘     └──────┬──────┘
                                               │
                         ┌─────────────────────┘
                         ▼
                ┌─────────────────┐
                │  Query Handler  │
                └────────┬────────┘
                         │
            ┌────────────┼────────────┐
            ▼            ▼            ▼
      ┌─────────┐  ┌─────────┐  ┌─────────┐
      │  Pool   │  │  Batch  │  │   Tx    │
      │ Manager │  │ Handler │  │ Handler │
      └────┬────┘  └─────────┘  └─────────┘
           │
           ▼
      ┌─────────┐
      │PostgreSQL│
      └─────────┘

⚙️ Configuração

Configuração efetiva

# Servidor
APP__ADDR=127.0.0.1:8580
APP__LOG_LEVEL=info

# O servidor e o pool são configurados em arquivos JSON.
# CONFIG_DIR aponta para o diretório que contém:
#   server.json  (host/port)
#   pool.json    (max_connections, min_idle, worker_threads)
#   accounts.json (database, username, password, permissions)
CONFIG_DIR=/etc/pg-api

# Observabilidade (opcional)
OPENSEARCH_ENABLED=false
OPENSEARCH_API_URL=https://opensearch.example.com
OPENSEARCH_API_TOKEN=sk_live_xxxxx

O accounts.json é obrigatório. Cada database deve apontar para o PostgreSQL real que o processo do PG-API alcança pela rede; criar um banco em outro host não o torna automaticamente disponível. O pool.json limita conexões por instância do PG-API e não aumenta max_connections do PostgreSQL. Configure pool.max_connections abaixo do limite do PostgreSQL, preservando conexões para outros serviços.

Para claims concorrentes, use /v1/transaction com uma transação real e SELECT ... FOR UPDATE SKIP LOCKED; não use /v1/batch, pois batch não é atômico. O corpo de /v1/transaction é um array de QueryRequest, cada item contendo database, query e opcionalmente params.


🧪 Testes

# Rodar todos os testes
cargo test

# Rodar com output detalhado
cargo test -- --nocapture

# Benchmarks
cargo bench

🤝 Contribuindo

  1. Fork o projeto
  2. Crie sua branch (git checkout -b feature/nova-feature)
  3. Commit suas mudanças (git commit -am 'feat: nova feature')
  4. Push para a branch (git push origin feature/nova-feature)
  5. Abra um Pull Request

📄 Licença

Este projeto está licenciado sob a licença MIT - veja LICENSE.md para detalhes.


🔗 Links