Neden ayrı bir backend kurmak istemiyorsun?
Küçük-orta ölçekli bir proje başlatıyorsun. Frontend Nuxt 3, veritabanı PostgreSQL ya da MongoDB. Arada bir Express ya da Fastify sunucusu kurmak, ayrı repo açmak, deploy pipeline'ı çoğaltmak istemiyorsun. İşte server routes tam bu noktada devreye giriyor.
Nuxt 3, Nitro engine üzerinde çalışan bir server katmanı sunuyor. server/api/ dizinine bir dosya atıyorsun, o dosya otomatik olarak bir API endpoint'i oluyor. Ayrı bir backend framework'ü yok, ayrı bir port yok. Build aldığında Nitro bunu Node.js sunucusu, Cloudflare Worker, Vercel Edge Function ya da Deno olarak deploy edebiliyor.
Biz bunu birkaç dahili projede kullandık. Basit CRUD API'leri, webhook handler'ları ve üçüncü parti servis proxy'leri için gerçekten işe yarıyor. Ama her şey için doğru araç değil. Ona da geleceğim.
İlk endpoint'ini oluştur
Proje kökünde server/api/ dizini oluştur. İçine bir dosya at:
Bu kadar. /api/hello adresine GET isteği attığında JSON dönecek. Dosya adındaki .get kısmı HTTP method'unu belirtiyor. .post, .put, .delete de kullanabilirsin. Method belirtmezsen tüm HTTP method'larına yanıt verir.
Dikkat: defineEventHandler Nitro'nun (h3 kütüphanesinin) fonksiyonu. Express'teki req, res yerine tek bir event objesi alıyorsun. Response'u return etmen yeterli, res.json() çağırmana gerek yok.
Request body ve query parametreleri
POST endpoint'inde body okumak için readBody, query string için getQuery kullanıyorsun:
createError ile hata fırlattığında Nuxt bunu uygun HTTP status code'uyla JSON olarak döndürüyor. Try-catch bloklarıyla uğraşmana gerek yok, ama production'da global bir error handler eklemeyi unutma.
Dinamik route'lar ve iç içe dizinler
Dosya tabanlı routing, Nuxt'ın page'lerindeki mantıkla aynı çalışıyor. Köşeli parantezle parametre tanımlıyorsun:
Bu dosya /api/users/123 gibi isteklere yanıt verir. Catch-all route için [...slug].ts kullanabilirsin ama genelde buna ihtiyaç duymazsın.
Dizin yapısını REST convention'a göre düzenlemeni öneririm:
Middleware ve paylaşılan mantık
Her endpoint'te auth kontrolü tekrarlamak istemezsin. server/middleware/ dizinindeki dosyalar tüm server isteklerinde çalışır:
Burada bir sorun var: middleware tüm route'lara uygulanıyor. Seçici olman gerekiyor, yoksa public endpoint'lerin de auth ister. URL kontrolü biraz kaba bir yöntem ama Nuxt 3'te route-level middleware tanımlama şu an bu şekilde çalışıyor. Alternatif olarak ortak bir utils fonksiyonu yazıp sadece ihtiyacın olan handler'larda çağırabilirsin.
server/utils/ dizinindeki dosyalar Nitro tarafından otomatik import ediliyor. Ayrıca import yazman gerekmez.
Veritabanı bağlantısı: gerçek dünyada nasıl?
Biz Prisma ile kullandık. Bağlantıyı her istekte yeniden oluşturmamak için bir singleton pattern gerekiyor:
Drizzle ORM de iyi çalışıyor ve tip güvenliği konusunda bir adım önde. Hangi ORM'i seçersen seç, connection pooling ayarını production'da mutlaka yap. Serverless ortamda (Vercel, Cloudflare) deploy ediyorsan connection limit sorunuyla karşılaşabilirsin.
Ne zaman yeterli değil?
Server routes ile bir noktaya kadar gidebilirsin. Ama şu durumlarda ayrı bir backend düşünmeye başla:
- WebSocket desteği lazımsa: Nitro'da deneysel WebSocket desteği var ama production-ready değil. Gerçek zamanlı iletişim gerekiyorsa Socket.io veya ayrı bir servis daha sağlam.
- Karmaşık iş mantığı büyüyorsa: 20-30 endpoint'i geçtikten sonra dosya tabanlı routing karmaşıklaşıyor. Service layer, repository pattern gibi yapılar kurmak istiyorsan NestJS ya da AdonisJS gibi bir framework daha uygun.
- Farklı takımlar frontend ve backend'i ayrı geliştiriyorsa: Monorepo'da bile olsan, ayrı deploy cycle'ları istiyorsan ayrı servisler daha mantıklı.
- CPU-yoğun işlemler varsa: Resim işleme, PDF oluşturma gibi ağır işler Nitro'nun event loop'unu bloklar. Bunları bir queue sistemiyle ayrı bir worker'a taşıman gerekir.
Küçük-orta ölçekli projelerde, dahili araçlarda veya MVP aşamasında server routes fazlasıyla yeterli. 5-6 endpoint'li bir admin paneli için Express sunucusu ayağa kaldırmak overkill.
Runtime config ve environment değişkenleri
API anahtarlarını ve hassas bilgileri .env dosyasında tutarsın. Server route'larından erişmek için:
Nuxt, NUXT_ prefix'iyle başlayan env değişkenlerini otomatik olarak runtimeConfig'e eşliyor. NUXT_API_SECRET yazdığında runtimeConfig.apiSecret olarak erişebilirsin. Tip güvenliği istiyorsan runtimeConfig için bir interface tanımla.
Sırada ne var?
Server routes'u production'da kullanacaksan birkaç şeyi es geçme: rate limiting (bir middleware ile basitçe yapılır), request validation (zod veya valibot ile şema tanımla), ve error logging (Sentry veya benzeri bir servise bağla). Nitro'nun server/plugins/ dizini uygulama başlarken çalışacak kodlar için iyi bir yer. Veritabanı migration'ları, cron job tanımları gibi şeyleri oraya koyabilirsin.
Resmi dökümandaki server directory sayfasını yer işaretine ekle. Nitro'nun kendi dökümantasyonu da ayrıca okunmaya değer çünkü Nuxt'ın server tarafı aslında tamamen Nitro.