Go Image Manipulation API
Fiber v3, GORM (MySQL) ve libvips/bimg tabanlı, JWT kimlik doğrulamalı resim yükleme ve işleme REST API.
İçindekiler
- Özellikler
- Teknoloji Yığını
- Proje Yapısı
- Gereksinimler
- Kurulum ve Çalıştırma
- Ortam Değişkenleri
- API Referansı
- Kimlik Doğrulama
- Veri Modelleri
- Resim İşleme
- Swagger UI
- Testler
- Deployment
Özellikler
| Özellik | Detay |
|---|---|
| 🔐 JWT Auth | Access token (15 dk) + Refresh token (7 gün), role claim içerir |
| 🖼️ Resim Yükleme | UUID tabanlı isim, otomatik format dönüşümü |
| ⚙️ Resim İşleme | Yeniden boyutlandırma, kırpma, cover modu, kalite, format değişimi |
| 🗂️ Resim Listeleme | Sayfalı listeleme, tek resim detayı |
| 🌐 Statik Erişim | Yüklenen resimlere doğrudan URL ile erişim |
| 🛡️ Admin Panel | Kullanıcıya API token atama, tüm resimleri listeleme |
| 🗄️ MySQL + GORM | Otomatik migrasyon, soft delete |
| ⚡ Redis | Bağlantı havuzu hazır |
| 📄 Swagger UI | /swagger adresinde interaktif API belgesi |
Teknoloji Yığını
| Katman | Kütüphane/Araç |
|---|---|
| HTTP Framework | Fiber v3 |
| ORM | GORM + MySQL driver |
| Resim İşleme | bimg (libvips bağlayıcısı) |
| JWT | golang-jwt/jwt v5 |
| Redis | go-redis/redis v9 |
| Parola Hashing | bcrypt |
| UUID | google/uuid |
| Swagger | swaggo/swag |
| Env Yönetimi | godotenv |
Proje Yapısı
goimgApi/
├── main.go # Uygulama başlangıç noktası
├── go.mod / go.sum
├── .env # Ortam değişkenleri (Git'e eklenmez)
│
├── accounts/ # Kimlik doğrulama & kullanıcı yönetimi
│ ├── models.go # User struct (GORM modeli)
│ ├── handlers.go # Register, Login, Refresh, Middleware, Admin handlers
│ ├── jwt.go # Token üretimi & parse, rol normalizasyonu
│ ├── accounts_test.go # JWT & rol testleri
│ ├── jwt_test.go # Token claim testleri
│ └── models/ # İkincil model paketi (sosyal hesap, profil, refresh token)
│ ├── account.go
│ ├── token.go
│ └── models_test.go
│
├── images/ # Resim yükleme & işleme
│ ├── models.go # Image struct (GORM modeli)
│ ├── handlers.go # Upload, ListImages, GetImage, AdminListImages, Process
│ ├── processor.go # bimg işleme motoru
│ └── handlers_test.go # Path yardımcı & sayfalama testleri
│
├── configs/ # Bağlantı yapılandırmaları
│ ├── db.go # MySQL bağlantısı, CORS/rate-limit seed yardımcıları
│ ├── redis.go # Redis bağlantısı
│ └── configs_test.go # Yardımcı fonksiyon testleri
│
├── router/
│ ├── routers.go # Tüm route tanımları, statik dosya sunumu
│ └── routers_test.go # Swagger & statik route testleri
│
├── docs/ # Swaggo tarafından üretilen Swagger dosyaları
│ ├── docs.go
│ ├── swagger.json
│ ├── swagger.yaml
│ └── docs_test.go
│
├── uploads/ # Yüklenen resimlerin depolandığı dizin
└── tmp/ # Geliştirme sırasında kullanılan geçici scriptler
Gereksinimler
- Go 1.26+
- MySQL 8.0+
- Redis 6+
- libvips 8.8+ (bimg için)
libvips Kurulumu
# Ubuntu / Debian
sudo apt-get install -y libvips-dev
# macOS
brew install vips
# Arch Linux
sudo pacman -S libvips
Kurulum ve Çalıştırma
1. Depoyu klonlayın
git clone https://github.com/kullanici/goimgApi.git
cd goimgApi
2. Bağımlılıkları yükleyin
go mod download
3. Ortam değişkenlerini ayarlayın
cp .env.example .env
# .env dosyasını düzenleyin
4. Swagger spec'ini üretin
# swag CLI kurulumu (tek seferlik)
go install github.com/swaggo/swag/cmd/swag@latest
# docs/ klasörünü yeniden üret
swag init --parseDependency --parseInternal
⚠️ Önemli: Yeni bir endpoint ekledikten veya mevcut bir endpoint'in godoc yorumunu değiştirdikten sonra bu komutu mutlaka tekrar çalıştırın; aksi hâlde Swagger UI güncellenmiş endpoint'leri göstermez.
5. Uygulamayı başlatın
go run .
Sunucu http://localhost:8080 adresinde başlar.
Geliştirme modunda (hot-reload)
# air kurulumu (opsiyonel)
go install github.com/air-verse/air@latest
air
Ortam Değişkenleri
Proje kök dizininde bir .env dosyası oluşturun:
# ─── Veritabanı (MySQL) ──────────────────────────────────────────────────────
DB_HOST=localhost
DB_PORT=3306
DB_USER=go_imgapi
DB_PASSWORD=your_password
DB_NAME=go_imgapi
# ─── Redis ───────────────────────────────────────────────────────────────────
REDIS_URL=redis://default:your_password@localhost:6379/0
# veya ayrı ayrı:
# REDIS_HOST=localhost
# REDIS_PORT=6379
# REDIS_PASSWORD=
# ─── JWT ─────────────────────────────────────────────────────────────────────
JWT_SECRET=en-az-32-karakter-uzun-gizli-anahtar
JWT_REFRESH_SECRET=farkli-bir-gizli-anahtar-refresh-icin
# ─── Sunucu ──────────────────────────────────────────────────────────────────
PORT=8080
# ─── CORS Bootstrap (opsiyonel) ──────────────────────────────────────────────
CORS_BOOTSTRAP_WHITELIST_ORIGINS=http://localhost:3000,https://yourfrontend.com
# ─── Rate Limit Bootstrap (opsiyonel) ────────────────────────────────────────
RL_BOOTSTRAP_LOGIN_MAX_REQUESTS=10
RL_BOOTSTRAP_LOGIN_WINDOW_SECONDS=60
RL_BOOTSTRAP_API_MAX_REQUESTS=120
RL_BOOTSTRAP_API_WINDOW_SECONDS=60
Not:
JWT_SECRETveJWT_REFRESH_SECRETbirbirinden farklı ve en az 32 karakter olmalıdır.
API Referansı
Base URL: http://localhost:8080
Auth
POST /auth/register — Kayıt
Yeni kullanıcı oluşturur.
İstek (multipart/form-data)
| Alan | Tür | Zorunlu | Açıklama |
|---|---|---|---|
email |
string | ✅ | Geçerli e-posta adresi |
password |
string | ✅ | En az 6 karakter |
Başarılı Yanıt 201 Created
{
"message": "User registered",
"user_id": 1
}
Hata Yanıtları
| Kod | Açıklama |
|---|---|
400 |
Email boş / şifre 6 karakterden kısa / email zaten kullanımda |
POST /auth/login — Giriş
Kullanıcıyı doğrular ve JWT token çifti döner.
İstek (multipart/form-data)
| Alan | Tür | Zorunlu |
|---|---|---|
email |
string | ✅ |
password |
string | ✅ |
Başarılı Yanıt 200 OK
{
"access_token": "eyJhbGci...",
"refresh_token": "eyJhbGci...",
"user": {
"id": 1,
"email": "kullanici@ornek.com",
"role": "admin"
}
}
roledeğeri"admin"veya"user"olur.
Hata Yanıtları
| Kod | Açıklama |
|---|---|
401 |
Geçersiz e-posta veya şifre |
POST /auth/refresh — Token Yenile
Geçerli bir refresh token ile yeni token çifti üretir. Refresh sırasında kullanıcının güncel rolü DB'den yeniden okunur.
İstek (multipart/form-data)
| Alan | Tür | Zorunlu |
|---|---|---|
refresh_token |
string | ✅ |
Başarılı Yanıt 200 OK
{
"access_token": "eyJhbGci...",
"refresh_token": "eyJhbGci..."
}
Hata Yanıtları
| Kod | Açıklama |
|---|---|
401 |
Refresh token eksik, geçersiz veya süresi dolmuş |
401 |
Token'a ait kullanıcı bulunamadı (silinmiş olabilir) |
Images
Tüm /images endpoint'leri Authorization: Bearer <access_token> başlığı gerektirir.
POST /images — Resim Yükle
İstek (multipart/form-data)
| Alan | Tür | Zorunlu | Açıklama |
|---|---|---|---|
image |
file | ✅ | Resim dosyası |
w |
int | Hedef genişlik (px) | |
h |
int | Hedef yükseklik (px) | |
q |
int | Kalite 1-100 (varsayılan: 85) | |
f |
string | Format: webp, avif, png, jpg |
|
mode |
string | cover — kırparak doldur |
Başarılı Yanıt 201 Created
{
"message": "Image uploaded successfully",
"image_id": 12,
"filename": "550e8400-e29b-41d4-a716-446655440000.webp",
"public_path": "/uploads/550e8400-e29b-41d4-a716-446655440000.webp",
"image_url": "http://localhost:8080/uploads/550e8400-e29b-41d4-a716-446655440000.webp"
}
public_pathvsimage_urlfarkı:
public_path— domain bağımsız, DB'ye kaydedilen göreli yol (/uploads/...). Farklı ortamlarda (staging, prod, CDN) taşınabilir.image_url— isteği yapan host'a göre dinamik üretilen tam URL.X-Forwarded-Host/X-Forwarded-Protobaşlıkları varsa onları kullanır (proxy/CDN için).Frontend'de resmi göstermek için
image_urldeğerini doğrudan kullanabilir ya daAPI_BASE_URL + public_pathformülünü tercih edebilirsiniz.
GET /images — Resimleri Listele
Kimliği doğrulanmış kullanıcının resimlerini sayfalı döner.
Query Parametreleri
| Parametre | Varsayılan | Maks | Açıklama |
|---|---|---|---|
page |
1 |
— | Sayfa numarası |
limit |
20 |
100 |
Sayfa başına kayıt |
Başarılı Yanıt 200 OK
{
"data": [
{
"id": 12,
"user_id": 1,
"filename": "550e8400....webp",
"public_path": "/uploads/550e8400....webp",
"image_url": "http://localhost:8080/uploads/550e8400....webp",
"mime_type": "image/jpeg",
"size_kb": 42,
"width": 800,
"height": 600,
"quality": 85,
"format": "webp",
"mode": "original",
"created_at": "2026-04-10T01:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}
GET /images/:id — Tek Resim Detayı
Kimliği doğrulanmış kullanıcıya ait belirli bir resmin detayını döner.
Başarılı Yanıt 200 OK
Listeleme endpoint'indeki tek kayıt yapısı ile aynı.
Hata Yanıtları
| Kod | Açıklama |
|---|---|
404 |
Resim bulunamadı veya kullanıcıya ait değil |
GET /images/:id/process — Resmi İşle ve Döndür
API token ile korunur (JWT gerektirmez). Web sitelerinde <img src="..."> ile doğrudan kullanım için tasarlanmıştır.
Query Parametreleri
| Parametre | Zorunlu | Açıklama |
|---|---|---|
token |
✅ | Kullanıcıya ait API token |
w |
Hedef genişlik (px) | |
h |
Hedef yükseklik (px) | |
q |
Kalite 1-100 | |
f |
Format: webp, avif, png, jpg |
|
mode |
cover — kırparak doldur |
Başarılı Yanıt 200 OK — İşlenmiş resim binary verisi (image/webp, image/jpeg, vb.)
Örnek Kullanım
<!-- Orijinal resmi göster -->
<img src="http://localhost:8080/images/12/process?token=API_TOKEN">
<!-- 400×300 WebP olarak göster -->
<img src="http://localhost:8080/images/12/process?token=API_TOKEN&w=400&h=300&f=webp">
<!-- Cover kırpma ile 200×200 thumbnail -->
<img src="http://localhost:8080/images/12/process?token=API_TOKEN&w=200&h=200&mode=cover&f=webp">
Hata Yanıtları
| Kod | Açıklama |
|---|---|
401 |
Token eksik, geçersiz veya süresi dolmuş |
404 |
Resim bulunamadı |
500 |
Dosya okunamadı veya işleme hatası |
Admin
Tüm /admin endpoint'leri hem Authorization: Bearer <access_token> hem de admin yetkisi gerektirir.
POST /admin/users/:id/api-token — API Token Oluştur
Belirtilen kullanıcıya yeni bir API token atar.
Path Parametreleri
| Parametre | Tür | Açıklama |
|---|---|---|
id |
int | Hedef kullanıcı ID |
İstek (multipart/form-data veya query)
| Alan | Tür | Açıklama |
|---|---|---|
expires_in_days |
int | Kaç gün geçerli (0 = süresiz) |
Başarılı Yanıt 200 OK
{
"message": "API token created successfully",
"api_token": "550e8400-e29b-41d4-a716-446655440000",
"expires_at": "2026-05-10T01:00:00Z"
}
GET /admin/images — Tüm Resimleri Listele
Tüm kullanıcılara ait resimleri sayfalı döner.
Query Parametreleri
| Parametre | Açıklama |
|---|---|
page |
Sayfa numarası (varsayılan: 1) |
limit |
Sayfa başına kayıt (varsayılan: 20, maks: 100) |
user_id |
Belirli bir kullanıcıya göre filtrele |
Başarılı Yanıt 200 OK
{
"data": [
{
"id": 12,
"user_id": 3,
"filename": "550e8400-e29b-41d4-a716-446655440000.webp",
"public_path": "/uploads/550e8400-e29b-41d4-a716-446655440000.webp",
"image_url": "http://localhost:8080/uploads/550e8400-e29b-41d4-a716-446655440000.webp",
"mime_type": "image/jpeg",
"size_kb": 42,
"width": 800,
"height": 600,
"quality": 85,
"format": "webp",
"mode": "original",
"created_at": "2026-04-10T01:00:00Z"
}
],
"total": 50,
"page": 1,
"limit": 20
}
Static Files
GET /uploads/:filename — Yüklenen Resme Eriş
Yüklenen resimlere doğrudan URL ile erişim sağlar. Path traversal saldırılarına karşı korumalıdır.
GET /uploads/550e8400-e29b-41d4-a716-446655440000.webp
Kimlik Doğrulama
JWT Token Yapısı
{
"user_id": 1,
"role": "admin",
"exp": 1775771479,
"iat": 1775770579
}
| Claim | Açıklama |
|---|---|
user_id |
Kullanıcı birincil anahtarı |
role |
"admin" veya "user" |
exp |
Token son geçerlilik zamanı (Unix timestamp) |
iat |
Token üretim zamanı |
Token Ömürleri
| Token | Ömür |
|---|---|
| Access Token | 15 dakika |
| Refresh Token | 7 gün |
| API Token | Admin tarafından belirlenir (sınırsız veya belirli gün) |
İstek Başlığı
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Token Yenileme Akışı
POST /auth/login
→ access_token (15 dk) + refresh_token (7 gün)
access_token süresi dolunca:
POST /auth/refresh body: refresh_token=...
→ yeni access_token + yeni refresh_token
Veri Modelleri
User (accounts paketi)
| Alan | Tür | Açıklama |
|---|---|---|
id |
uint | Birincil anahtar |
email |
string | Benzersiz, zorunlu |
password_hash |
string | bcrypt (JSON'da gizli) |
is_admin |
bool | Admin yetkisi (varsayılan: false) |
api_token |
string | Resim işleme için API token |
api_token_expires_at |
*time | Token bitiş zamanı (null = süresiz) |
created_at |
time | |
updated_at |
time | |
deleted_at |
gorm.DeletedAt | Soft delete |
Image (images paketi)
| Alan | Tür | Açıklama |
|---|---|---|
id |
uint | Birincil anahtar |
user_id |
uint | Sahibi (indexed) |
filename |
string | UUID tabanlı dosya adı |
public_path |
string | /uploads/<uuid>.<ext> |
mime_type |
string | Orijinal MIME türü |
size |
int64 | KB cinsinden boyut |
width |
int | Piksel genişlik |
height |
int | Piksel yükseklik |
quality |
int | 1–100 arası kalite değeri |
format |
string | bimg format adı |
mode |
string | original veya cover |
created_at |
time |
Resim İşleme
Resimler libvips ile işlenir. Desteklenen işlemler:
| İşlem | Parametre | Örnek |
|---|---|---|
| Genişlik ayarla | w=400 |
400px genişliğe küçült |
| Yükseklik ayarla | h=300 |
300px yüksekliğe küçült |
| Kalite ayarla | q=75 |
%75 kalite |
| Format dönüştür | f=webp |
WebP formatına çevir |
| Cover kırpma | mode=cover |
Oranı koruyarak kırp ve doldur |
Desteklenen Formatlar
| Format | Parametre Değeri | MIME Türü |
|---|---|---|
| JPEG | jpg veya jpeg |
image/jpeg |
| PNG | png |
image/png |
| WebP | webp |
image/webp |
| AVIF | avif |
image/avif |
Yükleme Sırasında İşleme
Upload endpoint'ine w, h, f, q parametreleri verilirse resim diske yazılmadan önce işlenir:
curl -X POST http://localhost:8080/images \
-H "Authorization: Bearer TOKEN" \
-F "image=@foto.jpg" \
-F "w=800" \
-F "f=webp" \
-F "q=85"
Swagger UI
Tarayıcıda aç:
http://localhost:8080/swagger
Swagger JSON spec:
http://localhost:8080/docs/swagger.json
Spec içindeki host ve schemes alanları çalışma zamanında dinamik olarak kaldırılır; böylece farklı ortamlarda (proxy, HTTPS, CDN) Swagger UI her zaman mevcut host'a istek atar.
Swagger'dan istek atmak için:
/auth/loginendpoint'ini çalıştırın- Dönen
access_token'ı kopyalayın - Sağ üstteki Authorize butonuna tıklayın
Bearer <token>formatında girin
Testler
# Tüm paket testleri
go test ./...
# Detaylı çıktı
go test -v ./...
# Belirli paket
go test -v ./accounts
go test -v ./images
go test -v ./router
go test -v ./configs
go test -v ./docs
go test -v ./accounts/models
Test Kapsamı
| Paket | Test Sayısı | Kapsam |
|---|---|---|
accounts |
15 | JWT üretimi, parse, rol normalizasyonu, roleFromUser, User model |
accounts/models |
6 | IsEmailVerified, JSON tag güvenliği, GORM tag, RefreshToken alanları |
configs |
27 | normalizeOrigin (8), parseOriginList (4), envIntOr/envInt64Or (8), bootstrap (7) |
docs |
10 | SwaggerInfo alanları, şablon içeriği, swaggo registry kaydı |
images |
9 | Path yardımcıları, buildImageURL (forwarded header), sayfalama |
router |
2 | Swagger JSON host/scheme kaldırma, statik dosya servisi |
Veritabanı veya Redis bağlantısı gerektiren testler mevcut değildir; tüm testler izole ve bağımsız çalışır.
Swagger Spec Güncelleme
Yeni endpoint eklendiğinde veya mevcut godoc yorumu değiştirildiğinde spec'i yeniden üretmek gerekir:
swag init --parseDependency --parseInternal
Bu komut şu dosyaları günceller:
docs/
├── docs.go ← Go kodu (swaggo runtime için)
├── swagger.json ← Swagger UI'ın okuduğu spec
└── swagger.yaml ← YAML formatı
Deployment
Docker ile
FROM golang:1.26-alpine AS builder
RUN apk add --no-cache vips-dev build-base
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o goimgApi .
FROM alpine:3.19
RUN apk add --no-cache vips
WORKDIR /app
COPY --from=builder /app/goimgApi .
COPY --from=builder /app/docs ./docs
RUN mkdir -p uploads
EXPOSE 8080
CMD ["./goimgApi"]
docker build -t goimgapi .
docker run -p 8080:8080 --env-file .env goimgapi
Nginx Reverse Proxy Örneği
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
client_max_body_size 50M;
}
}
X-Forwarded-ProtoveX-Forwarded-Hostbaşlıkları, upload response'unda dönenimage_urldeğerinin doğru domain ile üretilmesi için gereklidir.
Production için .env Kontrol Listesi
JWT_SECRET— en az 64 karakter, rastgele üretilmişJWT_REFRESH_SECRET— JWT_SECRET'tan farklı, en az 64 karakterDB_PASSWORD— güçlü, production şifresiCORS_BOOTSTRAP_WHITELIST_ORIGINS— yalnızca gerçek frontend domain'leriuploads/dizini uygulama tarafından yazılabilir (chmod 755)
Katkı
- Fork'layın
- Feature branch oluşturun:
git checkout -b feature/ozellik-adi - Testler ekleyin ve geçtiğinden emin olun:
go test ./... - Pull request açın
Lisans
MIT