Skip to content

Ders 04: Server Functions - Giriş

  • Server Functions nedir ve neden kullanılır?
  • İlk server function’ınızı nasıl yazarsınız?
  • Client’ten server fonksiyon çağırma
  • Input validation ile güvenli API’lar oluşturma
  • POST requests ile form handling

Server Functions, sadece sunucuda çalışan kod parçalarıdır. Client kodunuzdan sanki normal bir fonksiyonmuş gibi çağırabilirsiniz, ancak aslında sunucuda çalışırlar.

SenaryoServer FunctionNormal Fonksiyon
Database’e erişim✅ Güvenli❌ Güvensiz
API keys kullanımı✅ Gizli kalır❌ Client’te görünür
Dosya işlemleri✅ Sunucuda yapılır❌ Tarayıcıda çalışmaz
Gizli veriler✅ Korumalı❌ Herkes görebilir
// ❌ Yanlış - Client'te çalışmaz
async function getUser(id: string) {
return db.users.find({ where: { id } })
}
// ✅ Doğru - Server function
const getUser = createServerFn()
.inputValidator((id: string) => id)
.handler(async ({ data: id }) => {
return db.users.find({ where: { id } })
})

src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
// Server function tanımla
const getZaman = createServerFn({ method: 'GET' }).handler(async () => {
// Bu kod sadece sunucuda çalışır!
return {
saat: new Date().toLocaleTimeString('tr-TR'),
tarih: new Date().toLocaleDateString('tr-TR'),
}
})
export const Route = createFileRoute('/')({
component: HomePage,
loader: async () => {
// Server function'ı çağır
return await getZaman()
},
})
function HomePage() {
// Loader'dan gelen veriye eriş
const data = Route.useLoaderData()
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
<h1>Merhaba!</h1>
<p style={{ fontSize: '1.2rem' }}>
Şu an saat: <strong style={{ color: '#3b82f6' }}>{data.saat}</strong>
</p>
<p style={{ fontSize: '1rem', color: '#6b7280' }}>
Tarih: {data.tarih}
</p>
</div>
)
}

Component İçinde Server Function Çağırma

Section titled “Component İçinde Server Function Çağırma”
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn, useServerFn } from '@tanstack/react-start'
const selamla = createServerFn({ method: 'POST' })
.inputValidator((isim: string) => isim)
.handler(async ({ data: isim }) => {
return `Merhaba, ${isim}! Server'dan selamlar! 👋`
})
export const Route = createFileRoute('/selamla')({
component: SelamlamaPage,
})
function SelamlamaPage() {
// Server function hook ile
const selamlaFn = useServerFn(selamla)
const [isim, setIsim] = React.useState('')
const [mesaj, setMesaj] = React.useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
const result = await selamlaFn({ data: isim })
setMesaj(result)
}
return (
<div style={{ padding: '2rem', maxWidth: '500px', margin: '0 auto' }}>
<h1>Selamlama Sayfası</h1>
<form onSubmit={handleSubmit} style={{ marginBottom: '2rem' }}>
<input
type="text"
value={isim}
onChange={(e) => setIsim(e.target.value)}
placeholder="İsminizi girin"
style={{
width: '100%',
padding: '0.75rem',
marginBottom: '1rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
}}
/>
<button
type="submit"
disabled={!isim || !!mesaj}
style={{
width: '100%',
padding: '0.75rem',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: isim ? 'pointer' : 'not-allowed',
opacity: !isim || !!mesaj ? 0.5 : 1,
}}
>
Selamla!
</button>
</form>
{mesaj && (
<div
style={{
padding: '1rem',
backgroundColor: '#d1fae5',
borderRadius: '4px',
border: '1px solid #10b981',
}}
>
<p style={{ margin: 0 }}>{mesaj}</p>
</div>
)}
</div>
)
}

✅ Input Validation ile Güvenli API’lar

Section titled “✅ Input Validation ile Güvenli API’lar”

Client’ten gelen veriyi her zaman doğrulayın! Bunun için Zod kullanacağız.

Terminal window
# Zod'u yükleyin
pnpm add zod
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn, useServerFn } from '@tanstack/react-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
// Validation şeması
const todoSchema = z.object({
baslik: z.string().min(3, 'En az 3 karakter olmalı'),
aciklama: z.string().max(200, 'En fazla 200 karakter'),
oncelik: z.enum(['dusuk', 'orta', 'yuksek']).default('orta'),
})
// Server function with validation
const todoEkle = createServerFn({ method: 'POST' })
.inputValidator(zodValidator(todoSchema))
.handler(async ({ data }) => {
// data artik type-safe ve validated!
const yeniTodo = {
id: Date.now(),
...data,
durum: 'aktif',
}
// Database'e kaydet (mock)
console.log('Yeni todo:', yeniTodo)
return yeniTodo
})
export const Route = createFileRoute('/todo-ekle')({
component: TodoEklePage,
})
function TodoEklePage() {
const todoEkleFn = useServerFn(todoEkle)
const [form, setForm] = React.useState({
baslik: '',
aciklama: '',
oncelik: 'orta',
})
const [sonuc, setSonuc] = React.useState(null)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
try {
const result = await todoEkleFn({ data: form })
setSonuc(result)
setForm({ baslik: '', aciklama: '', oncelik: 'orta' })
} catch (error: any) {
setSonuc({ hata: error.message })
}
}
return (
<div style={{ padding: '2rem', maxWidth: '600px', margin: '0 auto' }}>
<h1>Todo Ekle</h1>
{sonuc?.hata ? (
<div style={{
padding: '1rem',
backgroundColor: '#fee2e2',
borderRadius: '4px',
marginBottom: '1rem',
border: '1px solid #ef4444',
}}>
Hata: {sonuc.hata}
</div>
) : sonuc?.id ? (
<div style={{
padding: '1rem',
backgroundColor: '#d1fae5',
borderRadius: '4px',
marginBottom: '1rem',
border: '1px solid #10b981',
}}>
Todo başarıyla eklendi! ID: {sonuc.id}
</div>
) : null}
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
Başlık: *
</label>
<input
type="text"
value={form.baslik}
onChange={(e) => setForm({ ...form, baslik: e.target.value })}
placeholder="Todo başlığı..."
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
Açıklama:
</label>
<textarea
value={form.aciklama}
onChange={(e) => setForm({ ...form, aciklama: e.target.value })}
placeholder="Açıklama..."
rows={3}
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
Öncelik:
</label>
<select
value={form.oncelik}
onChange={(e) => setForm({ ...form, oncelik: e.target.value as any })}
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
}}
>
<option value="dusuk">Düşük</option>
<option value="orta">Orta</option>
<option value="yuksek">Yüksek</option>
</select>
</div>
<button
type="submit"
disabled={!form.baslik}
style={{
padding: '0.75rem 1.5rem',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: form.baslik ? 'pointer' : 'not-allowed',
opacity: !form.baslik ? 0.5 : 1,
}}
>
Ekle
</button>
</form>
</div>
)
}
import { z } from 'zod'
const kullaciSchema = z
.object({
email: z.string().email('Geçerli bir email adresi girin'),
sifre: z
.string()
.min(8, 'Şifre en az 8 karakter olmalı')
.regex(/[A-Z]/, 'En az bir büyük harf içermeli')
.regex(/[0-9]/, 'En az bir rakam içermeli'),
yas: z
.number()
.min(18, '18 yaşından büyük olmalısınız')
.max(120, 'Geçerli bir yaş girin'),
})
.refine((data) => ({
...data,
sifreTekrar: data.sifre, // Şifre tekrarı
}))
const kayitOl = createServerFn({ method: 'POST' })
.inputValidator(zodValidator(kullaciSchema))
.handler(async ({ data }) => {
// Validasyon başarılı, veri kullanıma hazır
return { success: true, email: data.email }
})

import { createFileRoute } from '@tanstack/react-router'
import { createServerFn, useServerFn } from '@tanstack/react-start'
const formGonder = createServerFn({ method: 'POST' })
.inputValidator((formData: FormData) => {
// FormData'dan veri çıkar
return {
ad: formData.get('ad'),
email: formData.get('email'),
mesaj: formData.get('mesaj'),
}
})
.handler(async ({ data }) => {
// E-posta gönderme (mock)
console.log('Form verisi:', data)
// Gerçekte burada email gönderme işlemi yapılır
// await emailService.send({ ...data })
return { success: true }
})
export const Route = createFileRoute('/iletisim')({
component: IletisimPage,
})
function IletisimPage() {
const formGonderFn = useServerFn(formGonder)
const [gonderildi, setGonderildi] = React.useState(false)
const [hata, setHata] = React.useState('')
const handleSubmit = async (formData: FormData) => {
setHata('')
try {
await formGonderFn({ data: formData })
setGonderildi(true)
} catch (error: any) {
setHata(error.message)
}
}
return (
<div style={{ padding: '2rem', maxWidth: '600px', margin: '0 auto' }}>
<h1>İletişim</h1>
{gonderildi ? (
<div
style={{
padding: '2rem',
backgroundColor: '#d1fae5',
borderRadius: '8px',
textAlign: 'center',
}}
>
<h2>Mesajınız için teşekkürler!</h2>
<p>En kısa sürede size dönüş yapacağız.</p>
<button
onClick={() => setGonderildi(false)}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Başka Form Gönder
</button>
</div>
) : (
<form action={formGonderFn.url} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
Ad Soyad:
</label>
<input
type="text"
name="ad"
required
placeholder="Adınız Soyadınız"
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d55db',
borderRadius: '4px',
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
E-posta:
</label>
<input
type="email"
name="email"
required
placeholder="ornek@email.com"
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
Mesajınız:
</label>
<textarea
name="mesaj"
rows={5}
required
placeholder="Mesajınızı buraya yazın..."
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
}}
/>
</div>
{hata && (
<div
style={{
padding: '1rem',
backgroundColor: '#fee2e2',
borderRadius: '4px',
border: '1px solid #ef4444',
}}
>
Hata: {hata}
</div>
)}
<button
type="submit"
style={{
padding: '0.75rem 1.5rem',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Gönder
</button>
</form>
)}
</div>
)
}

💡 İpucu: formGonderFn.url kullanarak HTML formunun native submit özelliğini kullanabilirsiniz. JavaScript kapalıyken bile form çalışır!


Bazı kodlar sadece sunucuda çalışmalıdır (API keys, database bağlantısı vb.).

import { createServerOnlyFn } from '@tanstack/react-start'
// Bu fonksiyon sadece sunucuda çalışır
const getApiKey = createServerOnlyFn(() => {
return process.env.SECRET_API_KEY
})
const dbConnection = createServerOnlyFn(() => {
return createDatabaseConnection()
})
// ❌ Client'te çağrılırsa HATA verir!
// ✅ Sadece başka bir server function içinde kullanılabilir
const fetchUserData = createServerFn({ method: 'GET' })
.handler(async () => {
// Doğru kullanım
const apiKey = getApiKey() // ✅ Güvenli
const db = dbConnection() // ✅ Güvenli
return db.users.findMany()
})

Environment Variables Güvenli Kullanımı

Section titled “Environment Variables Güvenli Kullanımı”
import { createServerFn } from '@tanstack/react-start'
// ❌ YANLIŞ - Environment variable client'e sızar
const getEnv = createServerFn().handler(async () => {
// Bu kötü! Client'te görülebilir
return { apiKey: process.env.API_KEY }
})
// ✅ DOĞRU - Sadece server'da kullan
const getData = createServerFn().handler(async () => {
// API key sadece server function içinde kullanılır
const response = await fetch(process.env.API_URL + '/data', {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`, // Güvenli
},
})
return response.json()
})

Şimdi öğrendiklerimizle basit bir login sistemi yapalım!

src/routes/login.tsx
import { createFileRoute, useNavigate, redirect } from '@tanstack/react-router'
import { createServerFn, useServerFn } from '@tanstack/react-start'
import { zodValidator, z } from 'zod'
// Mock kullanıcı veritabanı
const KULLANICILAR = [
{ id: 1, email: 'admin@ornek.com', sifre: 'admin123', ad: 'Admin' },
{ id: 2, email: 'user@ornek.com', sifre: 'user123', ad: 'Kullanıcı' },
]
// Login server function
const login = createServerFn({ method: 'POST' })
.inputValidator(
z.object({
email: z.string().email(),
sifre: z.string().min(6),
})
)
.handler(async ({ data }) => {
// Kullanıcıyı bul
const kullanici = KULLANICILAR.find(
(k) => k.email === data.email && k.sifre === data.sifre
)
if (!kullanici) {
throw new Error('Geçersiz email veya şifre!')
}
// Gerçekte burada session oluşturulur
// return { kullanici, token: 'jwt-token' }
return {
kullanici: {
id: kullanici.id,
email: kullanici.email,
ad: kullanici.ad,
},
}
})
export const Route = createFileRoute('/login')({
component: LoginPage,
})
function LoginPage() {
const navigate = useNavigate()
const loginFn = useServerFn(login)
const [hata, setHata] = React.useState('')
const [yukleniyor, setYukleniyor] = React.useState(false)
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setHata('')
setYukleniyor(true)
try {
const formData = new FormData(e.currentTarget)
const email = formData.get('email') as string
const sifre = formData.get('sifre') as string
const result = await loginFn({
data: { email, sifre },
})
// Login başarılı, ana sayfaya yönlendir
alert(`Hoş geldin, ${result.kullanici.ad}!`)
navigate({ to: '/' })
} catch (error: any) {
setHata(error.message || 'Bir hata oluştu')
} finally {
setYukleniyor(false)
}
}
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '100vh',
backgroundColor: '#f3f4f6',
}}
>
<div
style={{
backgroundColor: 'white',
padding: '2rem',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
width: '100%',
maxWidth: '400px',
}}
>
<h1 style={{ textAlign: 'center', marginBottom: '1.5rem' }}>Giriş Yap</h1>
{hata && (
<div
style={{
padding: '0.75rem',
backgroundColor: '#fee2e2',
color: '#b91c1c',
borderRadius: '4px',
marginBottom: '1rem',
}}
>
{hata}
</div>
)}
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
E-posta:
</label>
<input
type="email"
name="email"
required
placeholder="ornek@email.com"
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
Şifre:
</label>
<input
type="password"
name="sifre"
required
placeholder="••••••••"
style={{
width: '100%',
padding: '0.75rem',
border: '1px solid #d1d5db',
borderRadius: '4px',
}}
/>
</div>
<button
type="submit"
disabled={yukleniyor}
style={{
padding: '0.75rem 1.5rem',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: yukleniyor ? 'not-allowed' : 'pointer',
opacity: yukleniyor ? 0.6 : 1,
}}
>
{yukleniyor ? 'Giriş yapılıyor...' : 'Giriş Yap'}
</button>
</form>
<p style={{ textAlign: 'center', marginTop: '1rem', fontSize: '0.9rem', color: '#6b7280' }}>
Test hesapları:<br />
admin@ornek.com / admin123<br />
user@ornek.com / user123
</p>
</div>
</div>
)
}

Bu derste öğrendiklerimiz:

KonuAçıklama
createServerFn()Server function oluşturma
methodHTTP method’i (GET, POST, vb.)
inputValidatorInput validation (Zod)
handlerServer-side kod
useServerFn()Client’te çağırma hook’u
zodValidator()Zod ile type-safe validation
FormDataForm handling
createServerOnlyFn()Server-only kod güvenliği

Şehir adına göre hava durumu getiren bir uygulama yapın:

const havaDurumuGetir = createServerFn({ method: 'GET' })
.inputValidator((sehir: string) => sehir)
.handler(async ({ data: sehir }) => {
// Mock API
const durumlar = ['güneşli', 'bulutlu', 'yağmurlu', 'karlı']
return {
sehir,
durum: durumlar[Math.floor(Math.random() * durumlar.length)],
sicaklik: Math.floor(Math.random() * 30) + 5,
}
})

Kullanıcıların anket dolduğu bir uygulama:

  • POST server function ile anek kaydetme
  • Input validation ile anket verilerini doğrulama
  • Başarı mesajı gösterme

Ziyaretçi sayacı yapın:

  • Her sayfa görüntülendiğinde server function çağır
  • Sayacıyı artır ve sonucu göster

🚀 Sonraki Ders: Server Functions - Orta Seviye

Section titled “🚀 Sonraki Ders: Server Functions - Orta Seviye”

Bir sonraki derste şunları öğreneceksiniz:

  • 🗄️ Context ve middleware ile gelişmiş server functions
  • 🔗 Server functions arası bağımlılık
  • 📂 File operations (dosya okuma/yazma)
  • 🍪 Session ve cookie yönetimi
  • ⚡ Error handling ve retry mekanizmaları

  1. Server function her zaman sunucuda mı çalışır?
  2. Input validation olmadan ne olur?
  3. Form data’nı nasıl alırız?

Bir sonraki derste görüşmek üzere! 👋