PostgreSQL Tablosunu Hasura ile Otomatik API’ye Dönüştürme

Veritabanı şeması hazır, ama üstüne bir API katmanı yazmak için saatlerce harcamak zorunda mısın? Hasura tam da bu noktada devreye giriyor. PostgreSQL tablolarını anında GraphQL API’ye dönüştüren bu araç, backend geliştirme sürecini dramatik biçimde kısaltıyor. Bu yazıda sıfırdan başlayarak gerçek bir senaryo üzerinden Hasura kurulumunu, tablo bağlantısını, izin yönetimini ve production’a hazır hale getirmeyi ele alacağız.

Hasura Nedir ve Neden Kullanmalısın?

Hasura, PostgreSQL veritabanın üzerine otomatik olarak GraphQL ve REST API üreten açık kaynaklı bir engine. Elle yazdığın resolver’lara, ORM konfigürasyonlarına ya da controller katmanlarına gerek kalmıyor. Tabloyu bağlıyorsun, izinleri tanımlıyorsun, API hazır.

Gerçek dünyada şöyle bir senaryo düşün: Bir e-ticaret uygulaması geliştiriyorsun. products, orders, users, order_items tablolarına sahipsin. Normalde her entity için CRUD endpoint’leri yazmak, validasyon eklemek, join sorgularını yönetmek günler alır. Hasura ile bu işi birkaç saate indirebilirsin.

Hasura’nın öne çıkan özellikleri:

  • Instant GraphQL: Tablo oluşturduktan saniyeler sonra query, mutation ve subscription hazır
  • Real-time subscriptions: WebSocket üzerinden canlı veri akışı
  • Row-level security: Her kullanıcı rolü için granüler izin tanımı
  • Remote schemas: Başka GraphQL API’leri tek endpoint’te birleştirme
  • Event triggers: Veritabanı olaylarına bağlı webhook’lar
  • Actions: Custom business logic için REST endpoint entegrasyonu

Ortam Kurulumu

Docker Compose ile Hızlı Başlangıç

Production ortamında Hasura’yı her zaman veritabanıyla birlikte container’da çalıştırmanı öneririm. Aşağıdaki docker-compose.yml dosyası hem PostgreSQL hem de Hasura’yı ayağa kaldırır.

mkdir hasura-project && cd hasura-project
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  postgres:
    image: postgres:15
    restart: always
    environment:
      POSTGRES_PASSWORD: mysecretpassword
      POSTGRES_DB: ecommerce
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  hasura:
    image: hasura/graphql-engine:v2.36.0
    ports:
      - "8080:8080"
    depends_on:
      - postgres
    restart: always
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:mysecretpassword@postgres:5432/ecommerce
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
      HASURA_GRAPHQL_DEV_MODE: "true"
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
      HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256","key":"supersecretjwtkey32charslong1234"}'

volumes:
  postgres_data:
EOF

docker-compose up -d

Birkaç saniye sonra http://localhost:8080/console adresinden Hasura Console’a erişebilirsin. Admin secret olarak myadminsecretkey kullanacaksın.

Hasura CLI Kurulumu

Console üzerinden da yönetebilirsin ama CLI kullanmak migration ve metadata yönetimi için çok daha temiz bir iş akışı sağlar.

# Linux/macOS için Hasura CLI kurulumu
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash

# Versiyonu kontrol et
hasura version

# Projeyi başlat
hasura init ecommerce-hasura --endpoint http://localhost:8080 --admin-secret myadminsecretkey
cd ecommerce-hasura

PostgreSQL Tablolarını Oluşturma

E-ticaret senaryomuz için önce veritabanı şemasını oluşturalım. Bu kısmı ya doğrudan PostgreSQL’e bağlanarak ya da Hasura Console üzerindeki SQL editörüyle yapabilirsin.

# PostgreSQL container'ına bağlan
docker exec -it hasura-project-postgres-1 psql -U postgres -d ecommerce

# Tabloları oluştur
-- Kullanıcılar tablosu
CREATE TABLE users (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  name VARCHAR(255) NOT NULL,
  role VARCHAR(50) DEFAULT 'customer',
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Ürünler tablosu
CREATE TABLE products (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  description TEXT,
  price DECIMAL(10,2) NOT NULL,
  stock_count INTEGER DEFAULT 0,
  category VARCHAR(100),
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Siparişler tablosu
CREATE TABLE orders (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  status VARCHAR(50) DEFAULT 'pending',
  total_amount DECIMAL(10,2),
  shipping_address TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Sipariş kalemleri
CREATE TABLE order_items (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  order_id UUID REFERENCES orders(id) ON DELETE CASCADE,
  product_id UUID REFERENCES products(id),
  quantity INTEGER NOT NULL,
  unit_price DECIMAL(10,2) NOT NULL
);

-- Test verisi ekle
INSERT INTO users (email, name, role) VALUES
  ('[email protected]', 'Admin User', 'admin'),
  ('[email protected]', 'Ahmet Yilmaz', 'customer'),
  ('[email protected]', 'Zeynep Kaya', 'customer');

INSERT INTO products (name, price, stock_count, category) VALUES
  ('Laptop', 25000.00, 10, 'electronics'),
  ('Klavye', 850.00, 50, 'electronics'),
  ('Mouse', 450.00, 75, 'electronics');

Tabloları Hasura’ya Bağlama

Tablolar hazır, şimdi Hasura’ya tanıtalım. CLI ile migration kullanmak best practice’tir.

# Migration oluştur
hasura migrate create "track_tables" --up-sql "
-- Tablolar zaten oluşturuldu, bu migration metadata için
SELECT 1;
" --down-sql "SELECT 1;" --database-name default

# Metadata'yı export et
hasura metadata export

Console üzerinden tablo track etmek daha görsel: Data sekmesine git, public schema altındaki tablolar listesinde tüm tabloları seç ve Track butonuna bas. Ya da API ile:

# Hasura API üzerinden tablo track etme
curl -X POST http://localhost:8080/v1/metadata 
  -H "x-hasura-admin-secret: myadminsecretkey" 
  -H "Content-Type: application/json" 
  -d '{
    "type": "pg_track_table",
    "args": {
      "source": "default",
      "schema": "public",
      "name": "users"
    }
  }'

# İlişkileri track et (foreign key'ler varsa Hasura öneri sunar)
curl -X POST http://localhost:8080/v1/metadata 
  -H "x-hasura-admin-secret: myadminsecretkey" 
  -H "Content-Type: application/json" 
  -d '{
    "type": "pg_create_array_relationship",
    "args": {
      "source": "default",
      "table": {"schema": "public", "name": "users"},
      "name": "orders",
      "using": {
        "foreign_key_constraint_on": {
          "table": {"schema": "public", "name": "orders"},
          "column": "user_id"
        }
      }
    }
  }'

Tüm tablolar track edildiğinde ve foreign key ilişkileri tanımlandığında, Hasura otomatik olarak nested query imkanı sunar.

GraphQL API’yi Test Etme

Artık API hazır. Hasura Console’daki API sekmesinden ya da herhangi bir GraphQL client ile test edebilirsin.

# Tüm kullanıcıları çek (admin secret ile)
curl -X POST http://localhost:8080/v1/graphql 
  -H "x-hasura-admin-secret: myadminsecretkey" 
  -H "Content-Type: application/json" 
  -d '{
    "query": "query { users { id email name orders { id status total_amount order_items { product { name } quantity } } } }"
  }'

# Filtreli sorgu - sadece aktif ürünleri getir
curl -X POST http://localhost:8080/v1/graphql 
  -H "x-hasura-admin-secret: myadminsecretkey" 
  -H "Content-Type: application/json" 
  -d '{
    "query": "query GetActiveProducts($category: String!) { products(where: {is_active: {_eq: true}, category: {_eq: $category}}, order_by: {price: asc}) { id name price stock_count } }",
    "variables": {"category": "electronics"}
  }'

Nested query’nin gücü burada ortaya çıkıyor. Tek sorguda kullanıcı + siparişler + sipariş kalemleri + ürün bilgileri geliyor. Normalde bu için birkaç JOIN ya da birden fazla endpoint yazman gerekirdi.

İzin Yönetimi (Permissions)

Bu kısım production’da en kritik nokta. Hasura’da her tablo için rol bazlı izinler tanımlıyorsun. customer rolündeki bir kullanıcı sadece kendi siparişlerini görebilmeli, başkasının siparişlerine erişememeli.

# 'customer' rolü için orders tablosunda row-level permission
curl -X POST http://localhost:8080/v1/metadata 
  -H "x-hasura-admin-secret: myadminsecretkey" 
  -H "Content-Type: application/json" 
  -d '{
    "type": "pg_create_select_permission",
    "args": {
      "source": "default",
      "table": {"schema": "public", "name": "orders"},
      "role": "customer",
      "permission": {
        "columns": ["id", "status", "total_amount", "created_at"],
        "filter": {
          "user_id": {"_eq": "X-Hasura-User-Id"}
        },
        "limit": 50,
        "allow_aggregations": false
      }
    }
  }'

# 'customer' rolü için insert permission
curl -X POST http://localhost:8080/v1/metadata 
  -H "x-hasura-admin-secret: myadminsecretkey" 
  -H "Content-Type: application/json" 
  -d '{
    "type": "pg_create_insert_permission",
    "args": {
      "source": "default",
      "table": {"schema": "public", "name": "orders"},
      "role": "customer",
      "permission": {
        "columns": ["shipping_address"],
        "check": {},
        "set": {
          "user_id": "X-Hasura-User-Id",
          "status": "pending"
        }
      }
    }
  }'

X-Hasura-User-Id JWT token’dan ya da webhook’tan gelen bir session variable. Bu sayede kullanıcı kendi user_id‘sini manipüle edemez, Hasura bunu otomatik inject eder.

JWT Entegrasyonu

Frontend tarafından token gönderildiğinde Hasura bunu doğrular ve içinden rol ile user ID bilgisini çeker.

# Test JWT token oluşturma (Node.js ile)
# jwt.io ya da aşağıdaki gibi bir script kullanabilirsin

node -e "
const jwt = require('jsonwebtoken');
const token = jwt.sign({
  'https://hasura.io/jwt/claims': {
    'x-hasura-allowed-roles': ['customer'],
    'x-hasura-default-role': 'customer',
    'x-hasura-user-id': 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
  }
}, 'supersecretjwtkey32charslong1234', {expiresIn: '1h'});
console.log(token);
"

# Token ile sorgu gönder
curl -X POST http://localhost:8080/v1/graphql 
  -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" 
  -H "Content-Type: application/json" 
  -d '{
    "query": "query { orders { id status total_amount } }"
  }'

Bu sorgu çalıştığında customer rolündeki kullanıcı sadece kendi siparişlerini görecek. Başka kullanıcının siparişine erişmeye çalışırsa boş array dönecek.

Event Triggers ile Otomatik Webhook

Sipariş oluşturulduğunda email göndermek ya da başka bir servisi tetiklemek istiyorsun diyelim. Bunun için Event Trigger kullanırsın.

# Sipariş oluşturulduğunda webhook tetikle
curl -X POST http://localhost:8080/v1/metadata 
  -H "x-hasura-admin-secret: myadminsecretkey" 
  -H "Content-Type: application/json" 
  -d '{
    "type": "pg_create_event_trigger",
    "args": {
      "name": "order_created_notification",
      "source": "default",
      "table": {"schema": "public", "name": "orders"},
      "webhook": "https://your-notification-service.com/webhooks/order-created",
      "insert": {
        "columns": "*"
      },
      "retry_conf": {
        "num_retries": 3,
        "interval_sec": 10,
        "timeout_sec": 60
      },
      "headers": [
        {
          "name": "x-webhook-secret",
          "value_from_env": "WEBHOOK_SECRET"
        }
      ]
    }
  }'

Hasura yeni sipariş oluştuğunda bu webhook’u otomatik çağırır, hata durumunda belirttiğin retry konfigürasyonuna göre tekrar dener. Gönderilen payload içinde yeni kaydın tüm kolonları bulunur.

Migrations ve Metadata Yönetimi

Takım çalışmasında migration yönetimi kritik. CLI ile hem schema hem de metadata değişikliklerini version control altına alabilirsin.

# Yeni migration oluştur
hasura migrate create "add_product_reviews" 
  --database-name default

# migrations/default/TIMESTAMP_add_product_reviews/up.sql dosyasını düzenle
cat > migrations/default/*/up.sql << 'EOF'
CREATE TABLE product_reviews (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  product_id UUID REFERENCES products(id) ON DELETE CASCADE,
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  rating INTEGER CHECK (rating >= 1 AND rating <= 5),
  comment TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_reviews_product_id ON product_reviews(product_id);
CREATE INDEX idx_reviews_user_id ON product_reviews(user_id);
EOF

# Migration uygula
hasura migrate apply --database-name default

# Metadata export et ve git'e commit et
hasura metadata export
git add migrations/ metadata/
git commit -m "feat: product reviews tablosu eklendi"

# Staging ya da production'a uygula
hasura migrate apply --endpoint https://hasura-staging.yourapp.com 
  --admin-secret $STAGING_ADMIN_SECRET 
  --database-name default

hasura metadata apply --endpoint https://hasura-staging.yourapp.com 
  --admin-secret $STAGING_ADMIN_SECRET

Bu iş akışı sayesinde her ortam için aynı şema ve izin konfigürasyonunu tekrarlanabilir şekilde uygulayabilirsin.

Performance: Caching ve Query Optimization

Hasura varsayılan olarak her sorguyu veritabanına iletir. Yoğun trafik altında prepared statement cache ve bağlantı havuzu kritik hale gelir.

# docker-compose.yml'e PgBouncer ekle
cat >> docker-compose.yml << 'EOF'

  pgbouncer:
    image: edoburu/pgbouncer:latest
    environment:
      DATABASE_URL: "postgres://postgres:mysecretpassword@postgres:5432/ecommerce"
      POOL_MODE: transaction
      MAX_CLIENT_CONN: 1000
      DEFAULT_POOL_SIZE: 25
    ports:
      - "5433:5432"
    depends_on:
      - postgres
EOF

# Hasura'yı PgBouncer üzerinden bağla
# HASURA_GRAPHQL_DATABASE_URL değerini güncelle:
# postgres://postgres:mysecretpassword@pgbouncer:5432/ecommerce

Hasura Console’dan Settings > Data > Connection Settings kısmında bağlantı havuzu ayarlarını da yapılandırabilirsin:

  • Maximum connections: Eş zamanlı maksimum DB bağlantısı (önerilen: toplam PostgreSQL max_connections’ın %80’i)
  • Idle timeout: Boşta kalan bağlantının kapatılma süresi
  • Connection lifetime: Bağlantının zorunlu yenileme süresi

Monitoring ve Logging

Production’da ne olduğunu görmek için log yapılandırmasını detaylandıralım.

# Hasura loglarını izle
docker-compose logs -f hasura | grep -E "(error|warn|query-log)"

# Prometheus metrics endpoint'i aktif et
# docker-compose.yml'de environment'a ekle:
# HASURA_GRAPHQL_ENABLED_APIS: "metadata,graphql,pgdump,config"
# HASURA_GRAPHQL_METRICS_SECRET: "prometheussecret"

# Metrics endpoint'i test et
curl http://localhost:8080/v1/metrics 
  -H "x-hasura-metrics-secret: prometheussecret"

# Yavaş sorguları tespit etmek için query log analizi
docker-compose logs hasura | 
  python3 -c "
import sys, json
for line in sys.stdin:
    try:
        log = json.loads(line)
        if log.get('type') == 'http-log':
            detail = log.get('detail', {})
            if detail.get('query', {}).get('execution_time', 0) > 1000:
                print(f'Yavash sorgu: {detail}')
    except: pass
"

Güvenlik Kontrol Listesi

Production’a geçmeden önce bunları mutlaka kontrol et:

  • Admin secret: Uzun ve rastgele bir değer kullan, environment variable’dan oku
  • Dev mode kapalı: HASURA_GRAPHQL_DEV_MODE: "false" production’da
  • Console erişimi: Production’da console’u kapat: HASURA_GRAPHQL_ENABLE_CONSOLE: "false"
  • Introspection: Gerekli değilse kapat: HASURA_GRAPHQL_ENABLE_ALLOWLIST: "true"
  • Rate limiting: Önünde nginx ya da API gateway kullan
  • SSL/TLS: Hasura’ya doğrudan değil, reverse proxy üzerinden eriş
  • Depth limit: Çok derin nested query’leri engelle: HASURA_GRAPHQL_MAX_RECURSION_DEPTH: "3"
# Nginx reverse proxy örneği
cat > /etc/nginx/sites-available/hasura << 'EOF'
server {
    listen 443 ssl;
    server_name api.yourapp.com;

    ssl_certificate /etc/letsencrypt/live/api.yourapp.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourapp.com/privkey.pem;

    location /v1/graphql {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # Rate limiting
        limit_req zone=graphql burst=20 nodelay;
    }

    # Admin endpoint'i dışarıya açma
    location /v1/metadata {
        deny all;
    }
}
EOF

Sonuç

PostgreSQL tablolarını Hasura ile API’ye dönüştürmek, özellikle CRUD ağırlıklı uygulamalarda geliştirme süresini ciddi ölçüde kısaltıyor. Docker Compose ile ortamı ayağa kaldırmak birkaç dakika, tabloları track etmek birkaç tıklama, izinleri tanımlamak ise anlaşıldığında oldukça sezgisel.

Bu yazıda ele aldığımız akışı özetlersek: Docker ile Hasura ve PostgreSQL’i ayağa kaldırdık, gerçek bir e-ticaret şeması oluşturduk, tabloları track ederek GraphQL API’yi aktif hale getirdik, JWT tabanlı authentication ve row-level permission yapılandırdık, event trigger ile webhook entegrasyonu ekledik, CLI ile migration yönetimini gösterdik ve production hazırlığı için güvenlik ve performance adımlarını inceledik.

Hasura her projeye uymaz. Çok karmaşık business logic, stored procedure’lere yoğun bağımlılık ya da alışılmadık veri modelleri olan durumlarda kısıtlamalarla karşılaşabilirsin. Ama standart bir web uygulaması için, özellikle başlangıç aşamasında, Hasura’nın sağladığı hız tartışmasız. İzin katmanını doğru kurduğunda güvenlikten ödün vermeden bu hızı production’da da sürdürebilirsin.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir