Göktay Logo

Golang Gin Framework: HTTP Temellerinden Production API'ye Kapsamlı Rehber

9 dk okuma

Go ile web servisi yazmaya başlayan hemen herkes bir noktada aynı isimle karşılaşır: Gin. Son dönemde arama trendlerinde de belirgin bir yükselişte. Peki Gin tam olarak nedir, neden bu kadar popüler ve onunla gerçek bir API nasıl yazılır?

Bu yazıyı, konuyu daha önce hiç görmemiş biri bile okuyup sonunda kafasında soru işareti kalmayacak şekilde tasarladım. O yüzden en temelden — yani HTTP’nin ne olduğundan — başlayıp, adım adım production kalitesinde bir Gin API’sine kadar geleceğiz. Acelesi olanlar için içindekiler yukarıda; ama tavsiyem baştan okumanız, çünkü Gin’i gerçekten anlamak için önce altındaki HTTP’yi anlamak gerekir.

Önce Temel: HTTP Nedir?

Bir web tarayıcısına adres yazdığınızda ya da bir mobil uygulama sunucuya veri gönderdiğinde perde arkasında konuşulan dil HTTP’dir (HyperText Transfer Protocol). En basit haliyle HTTP, bir istemcinin (client) bir sunucuya (server) “şunu istiyorum” dediği, sunucunun da “buyur” ya da “olmadı” diye cevap verdiği bir soru-cevap protokolüdür.

İki temel kavram var:

HTTP’nin kritik bir özelliği durumsuz (stateless) olmasıdır: her istek kendi başına bağımsızdır, sunucu bir önceki isteği “hatırlamaz”. Bu, sistemleri ölçeklendirmeyi kolaylaştıran temel tasarım kararlarından biridir — nitekim Go’nun yüksek trafikli sistemlerdeki gücünü Neden Golang yazımda bu yüzden övmüştüm.

HTTP Metotları: Ne Yapmak İstiyorsun?

Bir istek yalnızca “ne” istediğinizi değil, “nasıl bir işlem” yapmak istediğinizi de belirtir. Bunu HTTP metotları (fiiller) ile yaparız:

MetotAmaçÖrnek
GETVeri okuma (yan etkisiz)Bir ürünü getir
POSTYeni kayıt oluşturmaYeni sipariş ekle
PUTBir kaydı tümüyle güncellemeKullanıcıyı baştan yaz
PATCHBir kaydı kısmen güncellemeSadece e-postayı değiştir
DELETEKayıt silmeÜrünü sil

Bu ayrım sadece bir gelenek değil, aynı zamanda bir sözleşmedir. GET bir isteği tekrar tekrar çağırmak sistemi değiştirmemeli; POST ise her çağrıldığında yeni bir kayıt oluşturabilir. Bu davranışa idempotency denir ve API tasarımının belkemiğidir.

HTTP Durum Kodları: Sunucu Ne Diyor?

Her HTTP yanıtı, üç haneli bir durum kodu (status code) ile başlar. Bu kod, “işlem nasıl sonuçlandı?” sorusunun standart cevabıdır. Kodun ilk hanesi, sonucun genel sınıfını söyler:

Gerçek hayatta en sık kullandığınız kodlar şunlardır:

KodAnlamıNe zaman kullanılır
200 OKBaşarılıBaşarılı GET, güncelleme
201 CreatedOluşturulduBaşarılı POST (yeni kayıt)
204 No Contentİçerik yokBaşarılı DELETE
400 Bad RequestHatalı istekEksik/geçersiz veri
401 UnauthorizedKimlik doğrulanmadıToken yok/geçersiz
403 ForbiddenYasakKimlik var ama yetki yok
404 Not FoundBulunamadıKaynak yok
409 ConflictÇakışmaÖr. aynı e-posta ikinci kez
422 Unprocessable Entityİşlenemeyen veriDoğrulama (validation) hatası
429 Too Many RequestsÇok fazla istekRate limit aşıldı
500 Internal Server ErrorSunucu hatasıBeklenmeyen çökme
503 Service UnavailableServis dışıBakım / aşırı yük

Production Notu: 401 ile 403’ü karıştırmak çok yaygın bir hatadır. 401 “kim olduğunu bilmiyorum, giriş yap” demektir; 403 ise “kim olduğunu biliyorum ama bunu yapamazsın” demektir. Doğru kodu döndürmek, hem istemci geliştiricilerin hem de arama motorlarının davranışını doğrudan etkiler.

İşte tam bu noktada, tüm bu HTTP mekaniğini elle yönetmek yerine bize kolaylık sağlayan araçlar devreye giriyor. Go’da bu araçların en popüleri de Gin.

Gin Nedir?

Gin, Go için yazılmış, minimalist ama son derece hızlı bir HTTP web framework’üdür. Go’nun standart net/http paketinin üzerine kurulur ve onu yok saymaz; sadece günlük işleri (routing, JSON ayrıştırma, doğrulama, middleware) çok daha az kod yazarak halletmenizi sağlar.

Gin’in popülerliğinin üç temel nedeni var:

  1. Performans: Gin, httprouter tabanlı, radix-tree kullanan çok hızlı bir yönlendiriciye (router) sahiptir. Yüksek istek hacimlerinde bellek tahsisini (allocation) minimumda tutar.
  2. Sadelik: API’si çok az kavram içerir. gin.Context üzerinden neredeyse her şeyi yaparsınız; öğrenme eğrisi düşüktür.
  3. Zengin ekosistem: Hazır middleware’ler (logger, recovery, CORS, JWT), veri bağlama (binding) ve doğrulama (validation) kutudan çıkar.

Gin mi, Saf net/http mi?

Dürüst olalım: Go’nun standart kütüphanesi, özellikle 1.22 sonrası gelişen ServeMux ile birlikte, birçok basit servis için tek başına yeterlidir. Aşırı mühendislik yazımda savunduğum gibi, bir bağımlılığı eklemeden önce gerçekten ihtiyaç olup olmadığını sorgulamak gerekir.

Gin’i şu durumlarda tercih ederim: çok sayıda endpoint, karmaşık routing (parametreli, gruplu), yoğun JSON binding/validation ihtiyacı ve paylaşılan middleware zincirleri (auth, logging, rate limit). Küçük bir iç araç ya da tek endpoint’lik bir webhook için ise saf net/http çoğu zaman daha doğrudur.

Kurulum ve İlk Sunucu

Gin’i kullanmak için önce bir Go modülü başlatıp paketi çekelim:

go mod init ornek/api
go get github.com/gin-gonic/gin

Ardından klasik “merhaba dünya” — ama bu sefer bir HTTP sunucusu olarak:

package main

import "github.com/gin-gonic/gin"

func main() {
    // gin.Default() → Logger ve Recovery middleware'leri hazır gelir
    r := gin.Default()

    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong"})
    })

    r.Run(":8080") // http://localhost:8080 üzerinde dinler
}

Çalıştırıp test edelim:

go run main.go
curl http://localhost:8080/ping
# {"message":"pong"}

Burada iki önemli detay var. gin.Default(), size hazır bir Logger (her isteği loglar) ve Recovery (bir handler panic ederse sunucuyu çökertmeden 500 döner) middleware’i verir. gin.H ise map[string]any için kısa bir takma addır — JSON gövdesini elle kurmadan yazmanızı sağlar.

Routing: İstekleri Doğru Yere Yönlendirmek

Routing, gelen bir isteği (metot + yol) doğru fonksiyona (handler) eşleştirme işidir. Gin bunu son derece okunabilir kılar:

r.GET("/products", listProducts)
r.POST("/products", createProduct)
r.GET("/products/:id", getProduct)     // yol parametresi
r.PUT("/products/:id", updateProduct)
r.DELETE("/products/:id", deleteProduct)

Yol ve Sorgu Parametreleri

:id gibi yol parametrelerini ve ?page=2 gibi sorgu parametrelerini gin.Context üzerinden okursunuz:

func getProduct(c *gin.Context) {
    id := c.Param("id")                 // /products/42 → "42"
    fields := c.Query("fields")         // ?fields=name → "name" (yoksa "")
    page := c.DefaultQuery("page", "1") // yoksa "1"

    c.JSON(200, gin.H{"id": id, "page": page, "fields": fields})
}

İstek Verisini Okumak: Binding ve Validation

Gerçek API’lerde asıl iş, istemcinin gönderdiği JSON gövdesini bir Go struct’ına güvenle çevirmektir. Gin bunu binding ile yapar ve struct etiketleri (tags) sayesinde aynı anda doğrulama (validation) da yapabilir:

type CreateProductRequest struct {
    Name  string  `json:"name" binding:"required"`
    Price float64 `json:"price" binding:"required,gt=0"`
    Email string  `json:"email" binding:"required,email"`
}

func createProduct(c *gin.Context) {
    var req CreateProductRequest

    // ShouldBindJSON: gövdeyi çöz + binding kurallarını uygula
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // Buraya geldiysek req geçerli: name dolu, price > 0, email formatı doğru
    c.JSON(201, gin.H{"created": req})
}

binding:"required,gt=0" gibi etiketler, arka planda go-playground/validator kütüphanesini çalıştırır. required, email, gt (greater than), min, max, oneof gibi onlarca kural hazır gelir. Böylece if çorbası yazmadan, bildirimsel (declarative) bir doğrulama elde edersiniz.

Mühendislik Notu: İstemciden gelen JSON yapısını inceleyip ondan doğru json etiketli Go struct’ları üretmek için hazırladığım Advanced JSON Formatter aracını kullanabilirsiniz. Veritabanı tablolarınızdan model üretmek içinse SQL to GORM Model Converter işinizi hızlandırır.

Yanıt Döndürmek

Gin, farklı formatlarda yanıt döndürmek için gin.Context üzerinde net metotlar sunar:

c.JSON(200, gin.H{"status": "ok"})      // JSON (en yaygın)
c.String(200, "Merhaba %s", "dünya")    // düz metin
c.XML(200, data)                         // XML
c.Status(204)                            // gövdesiz, sadece durum kodu
c.Redirect(302, "/yeni-adres")           // yönlendirme

Durum kodunu her zaman bilinçli seçin: yeni kayıt için 201, silme için 204, doğrulama hatası için 400 veya 422. Bunlar yukarıdaki tablodaki sözleşmenin API’nizdeki karşılığıdır.

Middleware: Zincirin Halkaları

Middleware, bir isteğin handler’a ulaşmadan önce veya yanıt gitmeden sonra çalışan ara katmandır. Loglama, kimlik doğrulama, CORS, rate limit, hata yakalama — hepsi middleware ile yapılır. Gin’de bir middleware, aslında sadece bir gin.HandlerFunc döndüren fonksiyondur.

İşte basit bir kimlik doğrulama (auth) middleware’i:

func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            // Zinciri kes ve 401 dön
            c.AbortWithStatusJSON(401, gin.H{"error": "yetkilendirme gerekli"})
            return
        }
        // Token doğrulaması... (gerçekte JWT çözülür)
        c.Set("userID", 42) // sonraki handler'lar okuyabilsin diye context'e yaz
        c.Next()            // zinciri devam ettir
    }
}

İki kritik metot: c.Next() zincirin bir sonraki halkasına geçer; c.Abort() (veya c.AbortWithStatusJSON) zinciri keser, sonraki handler’lar hiç çalışmaz. Kimlik doğrulama başarısızsa, isteğin asıl handler’a ulaşmasını istemeyiz — işte Abort tam da bunun içindir.

Production Notu: Authorization başlığındaki JWT token’larının içeriğini geliştirme sırasında incelemek için hazırladığım JWT Decoder & Verifier aracını kullanabilirsiniz. Token’ın exp (son kullanma) ve iat (verilme) alanlarını gözle görmek, “neden 401 alıyorum?” hatalarını dakikalar içinde çözer.

Hepsini Birleştirmek: Küçük Bir REST API

Şimdi öğrendiklerimizi tek bir örnekte toplayalım. Route grupları (Group) ile ortak bir ön ek (/api/v1) ve ortak middleware paylaşan, gerçekçi bir yapı:

func main() {
    r := gin.Default()

    // Sürümlü API grubu
    v1 := r.Group("/api/v1")
    {
        v1.GET("/products", listProducts)
        v1.GET("/products/:id", getProduct)

        // Yalnızca korumalı endpoint'ler için auth middleware
        authed := v1.Group("/")
        authed.Use(AuthRequired())
        {
            authed.POST("/products", createProduct)
            authed.DELETE("/products/:id", deleteProduct)
        }
    }

    r.Run(":8080")
}

Burada mimari bir mesaj var: handler’lar HTTP’yi konuşur, ama iş mantığını kendileri yapmaz. Bir handler; isteği ayrıştırır, bir servis/usecase katmanını çağırır ve sonucu uygun durum koduyla döndürür. Bu ayrımı neden bu kadar önemsediğimi Clean Architecture ve dış API çağrıları yazılarımda ayrıntılı ele almıştım. Gin, bu mimaride yalnızca “delivery” (teslimat) katmanıdır — değerli iş mantığınız Gin’e bağımlı olmamalıdır.

Production’a Çıkarken Dikkat Edilecekler

Gin ile bir servisi canlıya alırken deneyimle öğrendiğim birkaç kritik nokta:

srv := &http.Server{
    Addr:         ":8080",
    Handler:      r,
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout:  120 * time.Second,
}
srv.ListenAndServe()

Sıkça Sorulan Sorular

Gin, Go’nun net/http’sinin yerini mi alır? Hayır, üzerine kurulur. Gin’in altında hâlâ net/http çalışır; Gin sadece routing, binding ve middleware’i kolaylaştırır. İkisini birlikte kullanırsınız.

Gin ile Echo/Fiber arasındaki fark nedir? Üçü de hızlı ve popüler. Gin en yaygın olanı ve en büyük ekosisteme sahip; Echo benzer bir felsefeye sahip; Fiber ise net/http yerine fasthttp üzerine kuruludur (bu, bazı standart kütüphane davranışlarını değiştirir). Yeni başlıyorsanız Gin en güvenli tercihtir.

Gin öğrenmek için önce ne bilmeliyim? Go’nun temelleri (struct, interface, hata yönetimi) ve bu yazının ilk yarısındaki HTTP kavramları yeterli. Gin’in kendisi çok az yeni kavram getirir.

Doğrulama (validation) için ekstra kütüphane gerekir mi? Hayır. Gin, binding etiketleri üzerinden go-playground/validator’ı kutudan sunar. required, email, min, max gibi kuralları doğrudan struct’ta yazarsınız.

Gin production’da yeterince hızlı mı? Evet. Radix-tree tabanlı router’ı ve düşük bellek tahsisiyle Gin, çok yüksek istek hacimlerini rahatça kaldırır. Çoğu sistemde darboğaz Gin değil, veritabanı veya dış servis çağrılarıdır.

Sonuç

HTTP’nin ne olduğundan başladık, durum kodlarının dilini öğrendik ve Gin ile routing’den binding’e, middleware’den production ayarlarına kadar uçtan uca geçtik. Artık elinizde yalnızca “çalışan” bir Gin API’si değil, neden öyle çalıştığını bilerek yazabileceğiniz bir zemin var.

Son bir hatırlatma: Gin güçlü bir araçtır ama bir amaç değildir. İş mantığınızı Gin’in içine gömmeyin; Gin’i yalnızca dış dünyayla konuşan ince bir katman olarak tutun. HTTP’yi ve altındaki mantığı anladığınızda, hangi framework’ü kullandığınız ikincil bir detaya dönüşür — asıl ustalık, aracın arkasındaki mekaniği bilmektir.