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:
- İstek (Request): İstemcinin sunucuya gönderdiği mesaj. “Şu kullanıcının bilgilerini ver” gibi.
- Yanıt (Response): Sunucunun geri döndüğü mesaj. Hem bir sonuç kodu hem de (genelde) veri içerir.
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:
| Metot | Amaç | Örnek |
|---|---|---|
GET | Veri okuma (yan etkisiz) | Bir ürünü getir |
POST | Yeni kayıt oluşturma | Yeni sipariş ekle |
PUT | Bir kaydı tümüyle güncelleme | Kullanıcıyı baştan yaz |
PATCH | Bir kaydı kısmen güncelleme | Sadece e-postayı değiştir |
DELETE | Kayı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:
- 2xx — Başarı: İstek başarıyla işlendi.
- 3xx — Yönlendirme: İstenen kaynak başka bir yerde; tarayıcı yönlendirilmeli.
- 4xx — İstemci Hatası: Hata istemcide (yanlış istek, yetkisiz erişim vb.).
- 5xx — Sunucu Hatası: Hata sunucuda; istemci doğru davrandı ama biz patladık.
Gerçek hayatta en sık kullandığınız kodlar şunlardır:
| Kod | Anlamı | Ne zaman kullanılır |
|---|---|---|
200 OK | Başarılı | Başarılı GET, güncelleme |
201 Created | Oluşturuldu | Başarılı POST (yeni kayıt) |
204 No Content | İçerik yok | Başarılı DELETE |
400 Bad Request | Hatalı istek | Eksik/geçersiz veri |
401 Unauthorized | Kimlik doğrulanmadı | Token yok/geçersiz |
403 Forbidden | Yasak | Kimlik var ama yetki yok |
404 Not Found | Bulunamadı | Kaynak yok |
409 Conflict | Çakışma | Ör. aynı e-posta ikinci kez |
422 Unprocessable Entity | İşlenemeyen veri | Doğrulama (validation) hatası |
429 Too Many Requests | Çok fazla istek | Rate limit aşıldı |
500 Internal Server Error | Sunucu hatası | Beklenmeyen çökme |
503 Service Unavailable | Servis dışı | Bakım / aşırı yük |
Production Notu:
401ile403’ü karıştırmak çok yaygın bir hatadır.401“kim olduğunu bilmiyorum, giriş yap” demektir;403ise “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:
- Performans: Gin,
httproutertabanlı, radix-tree kullanan çok hızlı bir yönlendiriciye (router) sahiptir. Yüksek istek hacimlerinde bellek tahsisini (allocation) minimumda tutar. - Sadelik: API’si çok az kavram içerir.
gin.Contextüzerinden neredeyse her şeyi yaparsınız; öğrenme eğrisi düşüktür. - 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
jsonetiketli 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:
Authorizationbaş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’ınexp(son kullanma) veiat(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:
- Release moduna geçin. Geliştirme modunda Gin ayrıntılı debug logları basar. Production’da
gin.SetMode(gin.ReleaseMode)deyin (veyaGIN_MODE=releaseortam değişkenini verin). - Timeout’ları elle kurun.
r.Run()pratiktir ama timeout’suz bir sunucu açar. Ciddi sistemlerde kendihttp.Server{}’iniziReadTimeout,WriteTimeoutveIdleTimeoutile yapılandırıp Gin’i onunHandler’ı olarak verin. - Graceful shutdown ekleyin. Sunucu kapanırken devam eden istekleri yarıda kesmeyin;
contextile nazikçe kapatın. - Recovery’yi bırakın.
gin.Default()ile gelen Recovery middleware’i, bir panic’in tüm servisi çökertmesini engeller — production’da hayat kurtarır.
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.