İçeriğe geç
Teknik YazıArchitecture

Firebase ile Realtime Chat Uygulaması

React ve Firebase Realtime Database kullanarak anlık mesajlaşma uygulaması geliştirme — auth, listeners ve optimize render.

5 Aralık 202310 dk okuma

Firebase Realtime Database, WebSocket'i yönetmeye gerek kalmadan anlık veri senkronizasyonu sağlar. Bu yazıda React ile sıfırdan çalışan bir chat uygulaması kuruyoruz — auth, mesaj akışı ve performans optimizasyonuyla.

Proje Yapısı ve Firebase Kurulumu

bash
npm create vite@latest chat-app -- --template react-ts
cd chat-app
npm install firebase
ts
// lib/firebase.ts
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getDatabase } from 'firebase/database'

const app = initializeApp({
  apiKey:            process.env.NEXT_PUBLIC_FB_API_KEY,
  authDomain:        process.env.NEXT_PUBLIC_FB_AUTH_DOMAIN,
  databaseURL:       process.env.NEXT_PUBLIC_FB_DB_URL,
  projectId:         process.env.NEXT_PUBLIC_FB_PROJECT_ID,
})

export const auth = getAuth(app)
export const db   = getDatabase(app)

Anonim Auth ile Hızlı Başlangıç

tsx
import { signInAnonymously, onAuthStateChanged } from 'firebase/auth'
import { auth } from '@/lib/firebase'

function useAuth() {
  const [uid, setUid] = useState<string | null>(null)

  useEffect(() => {
    const unsub = onAuthStateChanged(auth, (user) => {
      if (user) setUid(user.uid)
      else signInAnonymously(auth)
    })
    return unsub
  }, [])

  return uid
}

Mesajları Dinlemek ve Göndermek

tsx
import { ref, push, onValue, query, limitToLast } from 'firebase/database'
import { db } from '@/lib/firebase'

interface Message {
  uid: string; text: string; timestamp: number
}

function useMessages(roomId: string) {
  const [messages, setMessages] = useState<Message[]>([])

  useEffect(() => {
    const q = query(ref(db, `rooms/${roomId}/messages`), limitToLast(50))
    const unsub = onValue(q, (snap) => {
      const data = snap.val() ?? {}
      setMessages(Object.values(data))
    })
    return unsub
  }, [roomId])

  return messages
}

async function sendMessage(roomId: string, uid: string, text: string) {
  await push(ref(db, `rooms/${roomId}/messages`), {
    uid, text, timestamp: Date.now(),
  })
}

⚠️ Dikkat

limitToLast(50) olmadan oda büyüdükçe tüm mesaj geçmişini her bağlantıda indirirsiniz. Büyük uygulamalar için cursor tabanlı pagination kurun.

Re-render Optimizasyonu

Her mesaj geldiğinde tüm listeyi yeniden render etmemek için React.memo ve sabit referanslar kullanın.

tsx
const MessageItem = React.memo(({ msg }: { msg: Message }) => (
  <div className="flex gap-2 p-2">
    <span className="font-mono text-xs text-indigo-400">{msg.uid.slice(0,6)}</span>
    <p>{msg.text}</p>
  </div>
))
MessageItem.displayName = 'MessageItem'

// Liste:
<div ref={bottomRef}>
  {messages.map((m) => (
    <MessageItem key={m.timestamp} msg={m} />
  ))}
</div>

Otomatik Scroll

tsx
const bottomRef = useRef<HTMLDivElement>(null)

useEffect(() => {
  bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [messages])

💡 İpucu

Kullanıcı yukarı scroll etmişse otomatik aşağı kaydırma can sıkıcı olur. Intersection Observer ile "en altta mı?" kontrolü yapın ve sadece öyleyse scroll edin.