# Firebase Data Connect: GraphQL + Cloud SQL
Firebase'in en büyük eleştirisi hep aynıydı: "Firestore güçlü ama ilişkisel veri modellemesi zayıf." Google bu sorunu Firebase Data Connect ile çözdü. Cloud SQL (PostgreSQL) üzerine inşa edilmiş, GraphQL ile sorgulanan ve otomatik olarak type-safe SDK üreten bir servis. Artık Firebase'in kolaylığını PostgreSQL'in gücüyle birleştirebilirsin. Haydi, Data Connect'in mimarisini, schema-first yaklaşımını, auto-generated SDK'ları ve production'da nasıl kullanacağını derinlemesine inceleyelim.
💡 Not: Firebase Data Connect, 2024'te GA oldu. Bu yazıdaki bilgiler Şubat 2026 itibarıyla güncel. Resmi kaynak: Data Connect Docs, backend: Cloud SQL, şema dili: GraphQL Spec. GraphQL Mobile yazımızda GraphQL temellerini ele almıştık — burada Firebase'e özgü detaylara odaklanıyoruz.
İçindekiler
- Data Connect Nedir?
- Schema-First Yaklaşım
- Query ve Mutation Tanımlama
- Auto-Generated SDK'lar
- iOS / Swift Entegrasyonu
- Flutter Entegrasyonu
- Authorization ve Güvenlik
- Firestore vs Data Connect
- Production Best Practices
- Sonuç ve Öneriler
Data Connect Nedir?
Firebase Data Connect, Firebase ekosistemi içinde çalışan bir GraphQL API servisi. Arkasında Cloud SQL for PostgreSQL var. Sen schema ve query tanımlarsın, Firebase otomatik olarak iOS, Android, Flutter ve Web için type-safe SDK üretir.
Mimari Genel Bakış
\\\`text
Mobil/Web App
↓ (Type-safe SDK)
Firebase Data Connect Service
↓ (GraphQL)
Cloud SQL for PostgreSQL
\\\`
Neden Data Connect?
Sorun | Firestore Çözümü | Data Connect Çözümü |
|---|---|---|
İlişkisel veri | Subcollection + denormalizasyon | Foreign key + JOIN |
Complex query | Composite index + client filtering | SQL query gücü |
Type safety | Zayıf (any benzeri) | Tam type-safe SDK |
Schema | Schema-less (esnek ama riskli) | Schema-first (güvenli) |
Transaction | Batch write (sınırlı) | ACID transaction |
Aggregate | Client-side hesaplama | SUM, AVG, COUNT server-side |
Migration | Manuel | SQL migration |
Kurulum
\\\`bash
# Firebase CLI güncelle
npm install -g firebase-tools@latest
# Data Connect başlat
firebase init dataconnect
# Proje yapısı oluşur:
# dataconnect/
# ├── schema/ → GraphQL schema dosyaları
# ├── connector/ → Query ve mutation tanımları
# └── dataconnect.yaml
\\\`
🔍 Pro Tip: Data Connect, mevcut Firebase projene eklenebilir. Firestore'u tamamen bırakmana gerek yok — real-time sync için Firestore, ilişkisel veriler için Data Connect kullanabilirsin.
Schema-First Yaklaşım
Data Connect'te önce veritabanı şemasını GraphQL SDL ile tanımlarsın. Firebase bunu otomatik olarak PostgreSQL tablolarına dönüştürür.
GraphQL Schema + Query Hızlı Başlangıç
\\\`graphql
# dataconnect/schema/schema.gql — minimal blog şeması
type Post @table {
id: UUID! @default(expr: "uuidV4()")
title: String!
slug: String! @unique
content: String!
status: PostStatus! @default(value: "DRAFT")
author: User! @relation
publishedAt: Timestamp
createdAt: Timestamp! @default(expr: "request.time")
}
enum PostStatus { DRAFT PUBLISHED ARCHIVED }
# connector/queries.gql — yayınlanmış yazıları listele
query ListPosts($limit: Int! = 10) @auth(level: PUBLIC) {
posts(
where: { status: { eq: PUBLISHED } }
orderBy: [{ publishedAt: DESC }]
limit: $limit
) {
id title slug publishedAt
author { displayName }
}
}
\\\`
Schema tanımlandıktan sonra \firebase dataconnect:sdk:generate\ komutu çalıştırılır ve TypeScript, Swift, Dart için type-safe SDK'lar otomatik oluşturulur. Oluşturulan SDK dosyaları elle düzenlenmemelidir.
Schema Tanımı
\\\`graphql
# dataconnect/schema/schema.gql
# Kullanıcı tablosu
type User @table {
id: UUID! @default(expr: "uuidV4()")
email: String! @unique
displayName: String!
photoUrl: String
bio: String
createdAt: Timestamp! @default(expr: "request.time")
updatedAt: Timestamp! @default(expr: "request.time")
}
# Blog yazısı
type Post @table {
id: UUID! @default(expr: "uuidV4()")
title: String!
slug: String! @unique
content: String!
excerpt: String
status: PostStatus! @default(value: "DRAFT")
publishedAt: Timestamp
author: User! @relation
category: Category @relation
viewCount: Int! @default(value: 0)
createdAt: Timestamp! @default(expr: "request.time")
}
# Kategori
type Category @table {
id: UUID! @default(expr: "uuidV4()")
name: String! @unique
slug: String! @unique
description: String
posts: [Post!]! @relation # Ters ilişki
}
# Yorum
type Comment @table {
id: UUID! @default(expr: "uuidV4()")
content: String!
author: User! @relation
post: Post! @relation
parentComment: Comment @relation # Nested yorumlar
createdAt: Timestamp! @default(expr: "request.time")
}
# Beğeni (many-to-many)
type Like @table(
key: ["user", "post"] # Composite primary key
) {
user: User! @relation
post: Post! @relation
createdAt: Timestamp! @default(expr: "request.time")
}
# Enum
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
\\\`
Schema Direktifleri
Direktif | Açıklama | Örnek |
|---|---|---|
\`@table\` | PostgreSQL tablosu oluştur | \`type User @table\` |
\`@unique\` | Unique constraint | \`email: String! @unique\` |
\`@default\` | Varsayılan değer | \`@default(value: 0)\` |
\`@relation\` | Foreign key ilişkisi | \`author: User! @relation\` |
\`@index\` | Veritabanı indexi | \`@index(fields: ["status"])\` |
\`@check\` | Constraint kontrolü | \`@check(expr: "rating >= 1")\` |
Query ve Mutation Tanımlama
Schema'dan sonra, query (okuma) ve mutation (yazma) işlemlerini tanımlarsın:
Query Tanımları
\\\`graphql
# dataconnect/connector/queries.gql
# Tüm yayınlanmış yazıları getir (sayfalama ile)
query ListPublishedPosts(
$limit: Int! = 10,
$offset: Int! = 0,
$categorySlug: String
) @auth(level: PUBLIC) {
posts(
where: {
status: { eq: PUBLISHED },
category: { slug: { eq: $categorySlug } }
},
orderBy: [{ publishedAt: DESC }],
limit: $limit,
offset: $offset
) {
id
title
slug
excerpt
publishedAt
viewCount
author {
displayName
photoUrl
}
category {
name
slug
}
}
}
# Tek yazı detayı (slug ile)
query GetPostBySlug($slug: String!) @auth(level: PUBLIC) {
post: posts_findMany(where: { slug: { eq: $slug }, status: { eq: PUBLISHED } }) {
id
title
slug
content
publishedAt
viewCount
author {
id
displayName
photoUrl
bio
}
category {
name
slug
}
comments(orderBy: [{ createdAt: DESC }]) {
id
content
createdAt
author {
displayName
photoUrl
}
}
}
}
# Kullanıcı profili ve istatistikleri
query GetUserProfile($userId: UUID!) @auth(level: USER) {
user(id: $userId) {
id
displayName
bio
photoUrl
posts_on_author(where: { status: { eq: PUBLISHED } }) {
id
title
viewCount
}
comments_on_author {
id
}
likes_on_user {
post {
id
title
}
}
}
}
\\\`
Mutation Tanımları
\\\`graphql
# dataconnect/connector/mutations.gql
# Yeni yazı oluştur
mutation CreatePost($data: Post_Data!) @auth(level: USER) {
post_insert(data: $data)
}
# Yazı güncelle (sadece yazar)
mutation UpdatePost(
$postId: UUID!,
$title: String,
$content: String,
$status: PostStatus
) @auth(expr: "auth.uid == this.author.id") {
post_update(
id: $postId,
data: {
title: $title,
content: $content,
status: $status,
updatedAt_expr: "request.time"
}
)
}
# Yorum ekle
mutation AddComment(
$postId: UUID!,
$content: String!,
$parentCommentId: UUID
) @auth(level: USER) {
comment_insert(data: {
content: $content,
post: { id: $postId },
author: { id_expr: "auth.uid" },
parentComment: { id: $parentCommentId }
})
}
# Beğeni toggle
mutation ToggleLike($postId: UUID!) @auth(level: USER) {
like_upsert(data: {
user: { id_expr: "auth.uid" },
post: { id: $postId }
})
}
# Yazı sil (soft delete → ARCHIVED)
mutation ArchivePost($postId: UUID!) @auth(expr: "auth.uid == this.author.id") {
post_update(
id: $postId,
data: { status: ARCHIVED }
)
}
\\\`
🔍 Pro Tip: Mutation'larda \@auth\direktifini MUTLAKA kullan. \level: USER\giriş yapmış herkes, \expr: "auth.uid == this.author.id"\sadece kaynak sahibi. Güvenlik sıfırdan düşünülmeli. iOS Security yazımızdaki prensipleri backend'e de uygula.
Auto-Generated SDK'lar
Data Connect'in en büyük avantajı: query ve mutation tanımlarından otomatik olarak iOS (Swift), Android (Kotlin), Flutter (Dart) ve Web (TypeScript) SDK'ları üretmesi.
SDK Oluşturma
\\\`bash
# SDK'ları oluştur
firebase dataconnect:sdk:generate
# Çıktı:
# ├── ios/ → Swift SDK
# ├── android/ → Kotlin SDK
# ├── dart/ → Flutter SDK
# └── web/ → TypeScript SDK
\\\`
TypeScript SDK Kullanımı
\\\`typescript
// Auto-generated — elle düzenleme YAPMA
import {
listPublishedPosts,
getPostBySlug,
createPost,
toggleLike,
ListPublishedPostsData,
} from '@firebasegen/my-connector';
// Type-safe query
const posts: ListPublishedPostsData = await listPublishedPosts({
limit: 10,
offset: 0,
categorySlug: 'typescript',
});
// posts.posts[0].title → string (type-safe!)
// posts.posts[0].author.displayName → string
// Type-safe mutation
await createPost({
data: {
title: 'Yeni Yazı',
slug: 'yeni-yazi',
content: 'İçerik...',
status: 'DRAFT',
// author otomatik: auth.uid
},
});
\\\`
iOS / Swift Entegrasyonu
Swift SDK Kullanımı
\\\`swift
import FirebaseDataConnect
// Auto-generated connector
let connector = DataConnect.dataConnect(
connectorConfig: ConnectorConfig(
serviceId: "my-service",
location: "us-central1"
)
)
// Type-safe query
func loadPosts(category: String? = nil) async throws -> [Post] {
let result = try await ListPublishedPostsQuery
.ref(limit: 10, offset: 0, categorySlug: category)
.execute()
return result.data.posts.map { post in
Post(
id: post.id,
title: post.title,
slug: post.slug,
excerpt: post.excerpt ?? "",
authorName: post.author.displayName,
categoryName: post.category?.name ?? "Genel"
)
}
}
// Type-safe mutation
func createNewPost(title: String, content: String) async throws {
try await CreatePostMutation
.ref(data: Post_Data(
title: title,
slug: title.slugified(),
content: content,
status: .draft
))
.execute()
}
// Beğeni toggle
func toggleLike(postId: UUID) async throws {
try await ToggleLikeMutation
.ref(postId: postId)
.execute()
}
\\\`
Flutter Entegrasyonu
Dart SDK Kullanımı
\\\`dart
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:my_connector/my_connector.dart';
class PostRepository {
final _connector = MyConnector.instance;
// Yayınlanmış yazıları getir
Future<List<Post>> getPublishedPosts({
int limit = 10,
int offset = 0,
String? categorySlug,
}) async {
final result = await _connector.listPublishedPosts
.ref(
limit: limit,
offset: offset,
categorySlug: categorySlug,
)
.execute();
return result.data.posts.map((p) => Post(
id: p.id,
title: p.title,
slug: p.slug,
excerpt: p.excerpt ?? '',
authorName: p.author.displayName,
viewCount: p.viewCount,
)).toList();
}
// Slug ile tek yazı
Future<PostDetail?> getPostBySlug(String slug) async {
final result = await _connector.getPostBySlug
.ref(slug: slug)
.execute();
final posts = result.data.post;
if (posts.isEmpty) return null;
final p = posts.first;
return PostDetail(
id: p.id,
title: p.title,
content: p.content,
author: Author(
name: p.author.displayName,
photo: p.author.photoUrl,
),
comments: p.comments.map((c) => Comment(
content: c.content,
authorName: c.author.displayName,
createdAt: c.createdAt,
)).toList(),
);
}
}
\\\`
🔍 Pro Tip: Flutter Clean Architecture yazımızdaki repository pattern ile Data Connect SDK'yı sarmala. Böylece veri kaynağını (Firestore vs Data Connect) kolayca değiştirebilirsin. Domain layer SDK detaylarından habersiz kalır.
Authorization ve Güvenlik
Data Connect, Firebase Auth ile entegre güçlü bir authorization sistemi sunar:
Auth Seviyeleri
Seviye | Açıklama | Kullanım |
|---|---|---|
\`PUBLIC\` | Herkes erişebilir | Blog listesi, ürün kataloğu |
\`USER_ANON\` | Anonim dahil tüm auth | Rate limiting gereken yerlerde |
\`USER\` | Email doğrulanmış kullanıcı | Yorum yazma, beğeni |
\`USER_EMAIL_VERIFIED\` | Email doğrulanmış | Hassas işlemler |
\`NO_ACCESS\` | Sadece admin SDK | Server-side işlemler |
CEL Expression ile Detaylı Kontrol
\\\`graphql
# Sadece kaynak sahibi düzenleyebilir
mutation UpdateProfile($data: User_Data!)
@auth(expr: "auth.uid == vars.data.id") {
user_update(data: $data)
}
# Admin rolü kontrolü
mutation DeletePost($postId: UUID!)
@auth(expr: "'admin' in auth.token.roles") {
post_delete(id: $postId)
}
# Zaman bazlı kontrol (son 24 saat)
mutation EditComment($commentId: UUID!, $content: String!)
@auth(expr: "auth.uid == this.author.id && (request.time - this.createdAt) < duration('24h')") {
comment_update(id: $commentId, data: { content: $content })
}
\\\`
Firestore vs Data Connect
Her iki servisi de production'da kullanan biri olarak karşılaştıralım:
Kriter | Firestore | Data Connect |
|---|---|---|
**Veri Modeli** | NoSQL (Document) | İlişkisel (SQL) |
**Schema** | Schema-less | Schema-first (GraphQL) |
**Query** | Collection query | SQL gücünde GraphQL |
**JOIN** | Yok (denormalizasyon) | Native JOIN |
**Aggregation** | count() (sınırlı) | SUM, AVG, COUNT, GROUP BY |
**Real-time** | Snapshot listener | Henüz sınırlı |
**Type Safety** | Zayıf | Tam (auto-generated SDK) |
**Offline** | Güçlü cache | Henüz yok |
**Migration** | Manuel | SQL migration |
**Fiyat** | Read/write/delete başına | Cloud SQL instance + query |
**Ölçeklenme** | Otomatik | Instance boyutunu ayarla |
Ne Zaman Hangisi?
- Firestore:: Real-time sync, offline-first, basit veri modeli, hızlı prototipleme
- Data Connect:: İlişkisel veri, karmaşık sorgular, type safety, SQL bilgisi olan ekip
Production Best Practices
1. Schema Migration
\\\`bash
# Schema değişikliği yaptıktan sonra
firebase dataconnect:sql:migrate --force
# Migration SQL'ini görmek için
firebase dataconnect:sql:diff
\\\`
2. Query Optimizasyonu
\\\`graphql
# KÖTÜ: Tüm alanları çek (over-fetching)
query GetPosts @auth(level: PUBLIC) {
posts {
id title slug content excerpt status
publishedAt viewCount createdAt updatedAt
author { id displayName email photoUrl bio }
comments { id content author { id displayName } }
}
}
# İYİ: Sadece ihtiyacın olan alanlar
query GetPostCards @auth(level: PUBLIC) {
posts(
where: { status: { eq: PUBLISHED } }
limit: 10
) {
id title slug excerpt
author { displayName }
category { name }
}
}
\\\`
3. Index Stratejisi
\\\`graphql
# Schema'da index tanımla
type Post @table @index(fields: ["status", "publishedAt"]) {
# ...
}
# Composite index
type Comment @table @index(fields: ["post", "createdAt"]) {
# ...
}
\\\`
Sonuç ve Öneriler
Firebase Data Connect, Firebase ekosisteminin en büyük eksikliğini kapatıyor: ilişkisel veri yönetimi. GraphQL schema-first yaklaşımı, auto-generated type-safe SDK'lar ve Cloud SQL'in gücü ile production uygulamaları için güçlü bir seçenek. GraphQL Mobile yazımızdaki GraphQL best practice'leri burada da geçerli. Firebase Advanced ile birleştirince Firebase ekosisteminin tam gücünü kullanırsın.
Öneriler
- İlişkisel veri? Data Connect kullan — Firestore'da JOIN yapma çabası boşa
- Schema-first düşün — Önce veri modelini, sonra UI'ı tasarla
- SDK'yı repository pattern ile sarmala — Veri kaynağı değişimine hazır ol. Swift 6 Yenilikler yazımızdaki concurrency pattern'leri ile güçlendir
- Auth direktiflerini her yere koy — Güvenlik varsayılan olmalı
- Firestore ile birlikte kullan — Real-time için Firestore, ilişkisel için Data Connect
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Data Connect + Firestore hibrit pattern: İlişkisel verileri (kullanıcılar, siparişler, ürünler) Data Connect'te tut, real-time ihtiyaçları (chat mesajları, online durum, bildirimler) Firestore'da tut. İki servisi köprüleyen bir Cloud Function yaz — Data Connect'te sipariş oluşunca Firestore'a real-time bildirim yaz. Bu hibrit yaklaşım, her iki dünyanın en iyisini sunar.

