Bu kılavuzda, paylaştığınız JavaScript eğitim
müfredatını en başından en sonuna kadar derinlemesine inceliyoruz. İstediğiniz
gibi süre bilgilerini tamamen kaldırdım, "Önizleme"
kısmını atladım, her konuyu bolca günlük hayat örneği ve kod uygulamalarıyla
detaylandırdım.
"Öğrenecekleriniz"
Paragrafının Açıklaması
Bu kurs, modern bir web uygulamasının hem görünen
yüzünü (Front-End) hem de arkada dönen tüm veri ve
motor işlerini (Back-End) sıfırdan inşa etmeyi
hedefler. Müfredatta belirtilen maddelerin detaylı anlamları şunlardır:
1. Bölüm: The 10 Days of JavaScript: The Language Itself (JavaScript
Dilinin Kendisi)
Bu bölümde web dünyasının ana dili olan
JavaScript'in temel mekanizmalarını öğrenirsiniz.
Getting Started (Başlangıç)
Functions (Fonksiyonlar
/ İşlevler)
·
function selamlamaVer(isim) {
·
return "Merhaba
" + isim + ", hoş geldin!";
·
}
·
console.log(selamlamaVer("Ahmet")); // Çıktı:
Merhaba Ahmet, hoş geldin!
Objects (Objeler / Nesneler)
·
let kedi = {
·
isim: "Pamuk",
·
renk: "Beyaz",
·
miyavla: function() {
·
return "Miyavvv!";
·
}
·
};
·
console.log(kedi.isim); // Pamuk
·
console.log(kedi.miyavla()); // Miyavvv!
Arrays & Arrays Exercise (Diziler ve Dizi
Egzersizi)
·
let meyveler = ["Elma", "Armut", "Muz"];
·
console.log(meyveler[0]); // Elma (Sayım
sıfırdan başlar)
Making Decisions (Karar Verme Yapıları)
·
let yas = 20;
·
if (yas >= 18) {
·
console.log("Ehliyet alabilirsin.");
·
} else {
·
console.log("Ehliyet almak için büyümelisin.");
·
}
Higher-Order Functions (Yüksek Mertebeden Fonksiyonlar)
·
let sayilar = [1, 2, 3];
·
// map bir yüksek mertebeden fonksiyondur, içine başka bir
fonksiyon alır
·
let ikiKati = sayilar.map(function(sayi) {
·
return sayi * 2;
·
});
·
console.log(ikiKati); // [2, 4, 6]
Returning vs Mutating (Geriye Değer
Döndürmek vs Orijinali Değiştirmek)
·
let renkler = ["Kırmızı", "Mavi"];
·
renkler.push("Yeşil"); // Mutating: Orijinal diziyi değiştirdi.
Scope & Context (Kapsam ve Bağlam - Bölüm
1 ve 2)
·
function kapsamTesti() {
·
let gizliKelime = "Sır"; // Sadece bu
fonksiyon içinden erişilebilir
·
}
·
//
console.log(gizliKelime); // Hata verir! Dışarıdan
okunamaz.
Miscellaneous Info (Çeşitli Bilgiler - Bölüm 1
ve 2)
Building To-Do App (Yapılacaklar Listesi Uygulaması - Bölüm 1 ve 2)
2. Bölüm:
Server Basics (Sunucu Temelleri)
Web sitelerinin arkasındaki görünmeyen beyin
sistemini kurmayı öğrenirsiniz.
Why Do We Need a Server? (Neden Bir
Sunucuya İhtiyacımız Var?)
Node.js Intro (Node.js
Giriş)
Text Editor
Software (Metin Editörü Yazılımı)
Our First Basic
Server (İlk Temel Sunucumuz)
Express Intro (Express Giriş - Bölüm 1 ve 2)
·
const express = require('express');
·
const app = express();
·
·
app.get('/', function(istek, cevap) {
·
cevap.send("Ana Sayfaya Hoş Geldiniz!");
·
});
·
app.listen(3000); // 3000
numaralı kapıdan dünyayı dinle
What's Next? (Big Picture / Büyük Resim)
3. Bölüm:
Database Basics (Veri Tabanı Temelleri)
Kullanıcıların yüklediği verileri ömür boyu
saklayacağımız disk alanını yönetme bölümüdür.
First Taste of a
Database & CRUD (Veri Tabanına İlk Bakış ve CRUD)
Host Your Database Locally On Your Computer (Veri Tabanını Bilgisayarınızda Yerel Olarak
Barındırmak)
Initial Setup for App #1 (Uygulama #1 İçin İlk
Kurulum)
Save Time: Automatic Node App Restarts (Otomatik Yeniden
Başlatma ile Zaman Kazanma)
Connecting Node App to
Database (Node Uygulamasını Veri Tabanına Bağlamak)
Solution to Common Database Problem & Installing
NPM Packages (Sık Karşılaşılan Sorunlar ve NPM
Paketleri Kurulumu)
Reading, Updating, Deleting Items & Create Without Page Reload (Veri Okuma,
Güncelleme, Silme ve Sayfa Yenilenmeden Veri Oluşturma)
Client-Side Rendering
(İstemci Taraflı Ekrana Çizme)
Security & Front-End
Security (Güvenlik ve Ön Yüz Güvenliği)
Pushing Our App Onto
The Internet (Uygulamayı İnternete Fırlatmak /
Canlıya Almak)
4. Bölüm: Starting Our Complex
App (Karmaşık Uygulamamıza Başlıyoruz
- Uygulama #2)
Bu bölümde tam teşekküllü, çok kullanıcılı bir
sosyal ağ platformu projesine başlanır.
Let's Begin App #2 & Dynamic Footer (Uygulama #2'ye
Başlıyoruz ve Dinamik Alt Bilgi)
What Is A Router, Controller, Model? (Router,
Controller ve Model Nedir? - MVC Mimarisi)
Adding Validation To Our
Model (Modele Doğrulama Ekleme)
Best Practice:
Environment Variables (En İyi Yöntem: Çevre
Değişkenleri)
Letting Users Log In & Promises (Kullanıcı Girişi ve Sözler / Vaatler)
Hashing User Passwords (Kullanıcı Şifrelerini Kriptolamak/Öğütmek)
Session Security
& Understanding Sessions
& Logout (Oturum Güvenliği, Oturumları Anlamak ve
Çıkış Yapma)
Adding Flash Messages (Anlık Uyarı Mesajları)
Adding User Profile Photos (Kullanıcı Profil Fotoğrafları Ekleme)
5. Bölüm: User
Created Posts (Kullanıcı
Tarafından Oluşturulan Gönderiler)
Sitede kullanıcıların kendi blog yazılarını veya
tweet benzeri gönderilerini paylaşabilmesini sağlayan dinamik altyapıdır.
Letting Users Create Posts
& Post Model (Kullanıcıların Gönderi Oluşturması ve Gönderi Modeli)
ObjectId Instead of ObjectID & New Keyword (ObjectId Kavramı)
Viewing a Post & Performing a Lookup in MongoDB (Bir Gönderiyi Görüntüleme ve Veri Birleştirme)
User Profile Screen
& View Posts by Author (Kullanıcı Profil Ekranı ve Yazara Göre
Gönderileri Görme)
Is the Current Visitor the Owner of the
Post? (Mevcut Ziyaretçi Gönderinin Sahibi mi?)
The "Edit" Screen & Updating/Deleting Posts (Düzenleme Ekranı, Güncelleme ve Silme)
Markdown: Safe User Generated HTML (Markdown: Güvenli Kullanıcı HTML İçerikleri)
6. Bölüm: Live
Search Feature (Canlı Arama
Özelliği)
Kullanıcıların site içindeki gönderileri sayfa
yenilenmeden, harf yazdıkça anlık aramasını sağlayan gelişmiş bölümdür.
Front-End JavaScript
& Showing/Hiding Search Overlay (Ön Yüz JavaScript
ve Arama Ekranını Açma/Kapama)
Responding to Key Press
Events (Klavye Tuş Basma Olaylarına Tepki Verme)
Back-End Aspect of Search & DB Indexes (Aramanın Arka Yüzü ve Veri Tabanı İndeksleri)
Generating HTML for Search Results
& Sanitizing (Arama Sonuçları İçin HTML Oluşturma
ve Temizleme)
7. Bölüm: Letting Users Follow
Each Other (Kullanıcıların
Birbirini Takip Etmesi)
Sosyal ağların temel etkileşim mekanizması olan
takip sisteminin kurulmasıdır.
Saving a "Follow" Action & Stop Following
(Takip Etme Eylemini Kaydetme ve Takibi Bırakma)
Profile Followers &
Following Screens (Profil
Takipçiler ve Takip Edilenler Ekranları)
Homepage Feed (Ana Sayfa Akışı - Takip
Edilenlerin Gönderileri)
8. Bölüm: Live
Chat (Socket.IO) (Canlı Mesajlaşma Odası)
Kullanıcıların sayfayı yenilemeden birbiriyle
anlık mesajlaşmasını sağlayan teknolojidir.
Socket.IO Introduction
(Socket.IO Giriş)
Finishing Chat & Dynamic Title Tag
(Sohbeti Bitirme ve Dinamik Sekme Başlığı)
9. Bölüm: Live
Validation for Registration Form (Kayıt Formu İçin Canlı Doğrulama)
Kullanıcı deneyimini (UX) üst seviyeye çıkaran
form kontrol mekanizmasıdır.
Live Form Validation
(Canlı Form Doğrulama - Bölüm 1, 2, 3, 4)
10. Bölüm: What is CSRF? (Security) (CSRF Nedir? - Güvenlik Bölümü)
Uygulamanın hacklenmesini
önlemek için kritik bir güvenlik duvarı konusudur.
Preventing Cross-Site Request Forgery (Siteler Arası
İstek Sahtekarlığı Engelleme)
11. Bölüm: Let's Create an API (Bir API
Oluşturalım)
Uygulamamızın verilerini dış dünyaya veya mobil
uygulamalara açma yöntemidir.
Setting Up The Skeleton & JSON Web Tokens (JWT) (API İskeleti ve JSON Web Jetonları)
Understanding CORS (CORS
Mekanizmasını Anlamak)
12. Bölüm: Deploying Complex App to Render
(Karmaşık Uygulamayı Render Platformuna Canlıya Alma)
Yaptığımız devasa projeyi internete yükleme
aşamasıdır.
Pushing Complex App & Sending Email (Uygulamayı Yükleme
ve E-posta Gönderme)
Convert ".then()" Syntax To Async
/ Await (Async/Await Dönüşümü)
13. Bölüm: Turning Our App
Into a SPA (Uygulamayı Tek Sayfalık Uygulamaya
Dönüştürme)
Uygulamayı bir web sitesinden çok anlık tepki
veren bir mobil uygulama hissiyatına kavuşturma cilasıdır.
Preventing Default Behavior of Links & History API (Linklerin Varsayılan Davranışını Engelleme ve
Tarayıcı Geçmişi)
Fetching Data & Submitting Forms (JS ile Veri Çekme ve Form Gönderme)
Harika bir karar! Teorik bilgileri pratiğe
dökerek pekiştirmek, yazılım öğrenmenin en etkili yoludur. Bu egzersizde,
paylaştığınız müfredatta yer alan Express, Yönlendirme (Routing), Şifre
Güvenliği (Hashing), Doğrulama (Validation)
ve Oturum Yönetimi (Session) gibi kritik
kavramları bir araya getiren harika bir "Üyelik ve Giriş Sistemi"
arka yüz kodlaması yapacağız.
Kodları parça parça ele alacak, ardından
müfredattaki konularla bağdaştırarak en derin detayına kadar
inceleyeceğiz.
Bölüm 1:
Projenin Kurulumu ve İskeleti (Server Basics)
Öncelikle projemizin ana dosyası olan server.js dosyasını
oluşturuyoruz. Bu kısım müfredattaki "Server Basics" ve "Express
Intro" konularını kapsar.
// server.js -
Temel Sunucu ve Ayarlar
const express = require('express');
const bcrypt = require('bcrypt'); // Şifre kriptolamak için
const session = require('express-session'); // Oturum
yönetimi için
const app = express();
// Middleware
(Ara Yazılım) Ayarları
app.use(express.urlencoded({ extended: false })); // Formlardan
gelen verileri okuyabilmek için
app.use(express.json()); // JSON formatındaki verileri okuyabilmek için
// Oturum (Session)
Ayarları
app.use(session({
secret: 'cok_gizli_bir_anahtar_kelime', // Güvenlik
mührü
resave: false,
saveUninitialized: false,
cookie: { maxAge: 1000 * 60 * 60 * 24 } // Oturum 24 saat geçerli olsun
}));
// Geçici Veri Tabanı (Müfredattaki
MongoDB yerine şimdilik hafızada dizi tutuyoruz)
const kullaniciVeritabani = [];
🔍 Derin
Değerlendirme ve Analiz:
Bölüm 2:
Kullanıcı Kaydı ve Şifre Güvenliği (Hashing & Validation)
Şimdi kullanıcıların güvenli bir şekilde üye
olmasını sağlayan kodu yazıyoruz. Müfredattaki "Hashing
User Passwords" ve "Model Validation" konularını içerir.
// Kullanıcı Kayıt Rotası (Registration)
app.post('/kayit-ol', async (req, res) => {
try {
const { kullaniciAdi, eposta, sifre } = req.body;
// 1.
Doğrulama (Validation) Adımı
if (!kullaniciAdi || !eposta || !sifre) {
return res.status(400).send("Lütfen tüm alanları
doldurun!");
}
if (sifre.length < 6) {
return res.status(400).send("Şifre en az 6 karakter
olmalıdır!");
}
// 2.
Benzersizlik Kontrolü (Unique Check)
const var Mi
= kullaniciVeritabani.find(k => k.eposta === eposta);
if (varMi) {
return res.status(400).send("Bu e-posta adresi zaten
kayıtlı!");
}
// 3. Şifreyi
Kriptolama (Hashing)
const tuzlamaKatsayisi = 10; // Salt rounds
const kriptoluSifre = await bcrypt.hash(sifre, tuzlamaKatsayisi);
// 4. Veriyi
Kaydetme
const yeniKullanici = {
id: Date.now().toString(),
kullaniciAdi,
eposta,
sifre: kriptoluSifre // Dümdüz şifreyi değil, kriptolu halini saklıyoruz!
};
kullaniciVeritabani.push(yeniKullanici);
res.status(201).send("Tebrikler, kayıt başarıyla
tamamlandı!");
} catch (error) {
res.status(500).send("Sunucu hatası oluştu.");
}
});
🔍 Derin
Değerlendirme ve Analiz:
Bölüm 3: Giriş
Yapma ve Oturum Açma (Log-in & Promises)
Kullanıcının şifresini kontrol edip içeri
aldığımız kısımdır. Müfredattaki "Letting Users Log In" ve "What is a Promise?"
bölümlerini inceler.
// Kullanıcı Giriş Rotası (Log-in)
app.post('/giris-yap', async (req, res) => {
try {
const { eposta, sifre } = req.body;
// 1.
Kullanıcıyı Bulma
const kullanici = kullaniciVeritabani.find(k
=> k.eposta === eposta);
if (!kullanici) {
return res.status(400).send("Hatalı e-posta veya
şifre!");
}
// 2. Şifre
Karşılaştırma (Bcrypt Promise
yapısıyla çalışır)
const sifreDogruMu = await bcrypt.compare(sifre, kullanici.sifre);
if (sifreDogruMu) {
// 3. Oturum Kartı Tanımlama (Session)
req.session.kullanici = {
id:
kullanici.id,
kullaniciAdi: kullanici.kullaniciAdi
};
return res.send(`Giriş başarılı! Hoş geldin, ${kullanici.kullaniciAdi}`);
} else {
return res.status(400).send("Hatalı e-posta veya
şifre!");
}
} catch (error) {
res.status(500).send("Giriş esnasında bir hata
oluştu.");
}
});
🔍 Derin
Değerlendirme ve Analiz:
Bölüm 4:
Yetkilendirme Koruması ve Çıkış Yapma (Authorization
& Log-out)
Sadece giriş yapmış kişilerin görebileceği özel
bir sayfa (Örn: Profil) ve çıkış yapma mekanizması
kuruyoruz.
// Özel Koruma Ara Yazılımı (Auth Middleware)
function yetkiKontrolEt(req, res, next) {
if (req.session.kullanici) {
next(); // Oturum
varsa geçişe izin ver, sonraki fonksiyona git
} else {
res.status(401).send("Bu sayfayı görmek için önce
giriş yapmalısınız!");
}
}
// Sadece Giriş Yapanların
Görebileceği Özel Sayfa (Gizli Akış / Panel)
app.get('/panel', yetkiKontrolEt, (req, res) => {
res.send(`Özel Yönetim Paneline Hoş Geldin, ${req.session.kullanici.kullaniciAdi}. Bu veriler gizlidir.`);
});
// Çıkış Yapma Rotası (Log-out)
app.post('/cikis-yap', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.send("Çıkış yapılırken bir hata oluştu.");
}
res.send("Başarıyla çıkış yapıldı. Oturum sonlandırıldı.");
});
});
// Sunucuyu Başlatma
app.listen(3000, () => console.log("Sunucu 3000 numaralı kapıda çalışıyor..."));
🔍 Derin
Değerlendirme ve Analiz:
Büyük Resim
Özeti ve İlerleme Durumu
Bu yaptığımız mini uygulamada müfredatınızın
neredeyse tüm ana damarlarını birbirine bağladık:
Mükemmel bir
adıma geçiyoruz! Bu aşamada projemizi tam anlamıyla modern, güvenli ve kurumsal
bir SPA (Single Page
Application / Tek Sayfalık Uygulama) mimarisine kavuşturuyoruz.
İstediğiniz
güncellemeleri yaparken çok önemli bir sektörel güncellemeden bahsetmeliyim:
Orijinal csurf paketi artık NPM üzerinde güvenlik zafiyetleri
ve bakım eksikliği nedeniyle kullanımdan kaldırılmıştır (deprecated).
Bu nedenle, modern Node.js ekosisteminde onun birebir alternatifi ve en güvenli
standardı olan csrf-csrf kütüphanesini kullanacağız.
Mantığı tamamen aynıdır (Double-Submit Cookie tekniği) ancak çok daha güvenlidir. [1, 2, 3, 4, 5]
Aşağıda hem
sunucu (server.js) hem de tarayıcı (index.html) kodlarını,
müfredatla bağdaştırılmış en derin analizleriyle hazırladım.
1. Bölüm: Arka Yüz (Back-End) Kodları: server.js
Bu kod veri
tabanı bağlantısını sağlar, CSRF jetonlarını üretip denetler ve SPA için sadece
ham JSON çıktıları verir.
// server.js
const express = require('express');
const mongoose = require('mongoose'); // Gerçek Veri Tabanı (MongoDB)
const bcrypt = require('bcrypt');
const session = require('express-session');
const cookieParser = require('cookie-parser'); // CSRF
Çerezleri için şarttır
const { doubleCsrf } = require('csrf-csrf'); // Modern Güvenli CSRF Kütüphanesi
const app = express();
// 1. Veri Tabanı Bağlantısı
(Database Basics)
// Yerelinizdeki MongoDB'ye bağlanır. 'uyelikSistemi'
adında bir DB oluşturur.
mongoose.connect('mongodb://127.0.0.1:27017/uyelikSistemi')
.then(()
=> console.log("MongoDB
Veri Tabanına Başarıyla Bağlanıldı."))
.catch(err => console.error("Veri Tabanı Bağlantı Hatası:", err));
// MongoDB
Kullanıcı Şablonu (Model Yapısı)
const KullaniciSchema = new mongoose.Schema({
kullaniciAdi: { type: String, required: true },
eposta: { type: String, required: true, unique: true },
sifre: { type: String, required: true }
});
const Kullanici = mongoose.model('Kullanici', KullaniciSchema);
// 2. Temel Ara Yazılımlar (Middleware)
app.use(express.static('public')); // HTML ve JS dosyalarımızı sunmak
için
app.use(express.json()); // SPA için JSON gövdelerini okur
app.use(cookieParser("gizli_cerez_sifresi")); // Çerezleri
şifreli okumak için
app.use(session({
secret: 'cok_gizli_oturum_anahtari',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 1000 * 60 * 60 * 24 }
}));
// 3. CSRF Güvenlik Katmanı
Ayarları (What is CSRF?)
const {
invalidCsrfTokenErrorMiddleware,
generateToken,
doubleCsrfProtection
} = doubleCsrf({
getSecret: () => "ust_duzey_gizli_csrf_sirri",
cookieName: "x-csrf-token", // Tarayıcıda
saklanacak çerez adı
cookieOptions: { sameSite: "lax", secure: false }, // Yerel test
için secure: false
getTokenFromRequest: (req) => req.headers["x-csrf-token"] // İsteğin
neresine bakılacak?
});
// Tarayıcının ilk açılışta gizli
CSRF jetonunu talep edeceği kapı
app.get('/api/csrf-token', (req, res) => {
const token = generateToken(req, res);
res.json({
csrfToken: token }); // Jetonu JSON olarak SPA'ya fırlatır
});
// 4. SPA Üyelik ve Giriş Rotaları
(Routes & Business Logic)
// Kayıt Olma Rotası
app.post('/api/kayit-ol', doubleCsrfProtection, async (req, res) => {
try {
const { kullaniciAdi, eposta, sifre } = req.body;
if (!kullaniciAdi || !eposta || !sifre || sifre.length < 6) {
return res.status(400).json({ hata: "Veriler
geçersiz veya şifre çok kısa!" });
}
const varMi = await Kullanici.findOne({ eposta });
if (varMi) return res.status(400).json({ hata: "Bu e-posta zaten kayıtlı!" });
const kriptoluSifre = await bcrypt.hash(sifre, 10);
// MongoDB'ye Kalıcı Kaydetme (CRUD -
Create)
const yeniKullanici = new Kullanici({ kullaniciAdi, eposta,
sifre: kriptoluSifre });
await yeniKullanici.save();
res.status(201).json({ mesaj: "Kayıt
başarıyla MongoDB'ye işlendi!" });
} catch (e) {
res.status(500).json({ hata: "Sistem
hatası." });
}
});
// Giriş Yapma Rotası
app.post('/api/giris-yap', doubleCsrfProtection, async (req, res) => {
try {
const { eposta, sifre } = req.body;
const kullanici = await Kullanici.findOne({ eposta }); // MongoDB'den Okuma (CRUD - Read)
if (!kullanici) return res.status(400).json({ hata: "Hatalı e-posta veya şifre!" });
const sfreDogruMu = await bcrypt.compare(sifre, kullanici.sifre);
if (!sfreDogruMu) return res.status(400).json({ hata: "Hatalı e-posta veya şifre!" });
req.session.kullanici
= { id: kullanici._id, ad: kullanici.kullaniciAdi };
res.json({
mesaj: `Giriş başarılı! Hoş geldin ${kullanici.kullaniciAdi}` });
} catch (e) {
res.status(500).json({ hata: "Giriş
hatası." });
}
});
// CSRF Hata Yakalayıcı (Eğer jeton
sahteyse bu devreye girer)
app.use(invalidCsrfTokenErrorMiddleware);
app.listen(3000, () => console.log("SPA Sunucusu port 3000'de yayında..."));
🔍 Arka Yüzün Derin Analiz Değerlendirmesi:
2. Bölüm: Ön Yüz (Front-End) Kodları: public/index.html
Bu dosya
tarayıcıda çalışan tek sayfadır. Sayfa hiç yenilenmeden arkadan gizlice fetch kullanarak verileri çeker ve ekranı günceller
(SPA).
<!-- public/index.html -->
<!DOCTYPE
html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>Güvenli SPA Üyelik Sistemi</title>
<style>
body { font-family: sans-serif; padding: 50px; background: #f4f4f9; }
.form-kutusu {
background: white; padding:
20px;
border-radius: 8px; max-width: 400px; margin-bottom: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
.mesaj { padding: 10px; margin-top: 10px; font-weight: bold; border-radius:
4px;
display: none; }
.basari {
background: #d4edda; color: #155724; }
.hata {
background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<h1>Güvenli Tek Sayfa Uygulaması (SPA)</h1>
<!-- Kayıt Formu -->
<div class="form-kutusu">
<h3>Kayıt Ol (MongoDB &
CSRF Korumalı)</h3>
<input type="text"
id="kayitAd" placeholder="Kullanıcı Adı"><br><br>
<input type="email"
id="kayitEposta" placeholder="E-posta"><br><br>
<input type="password" id="kayitSifre" placeholder="Şifre (Min 6 Karakter)"><br><br>
<button onclick="kayitOl()">Kayıt Ol</button>
<div id="kayitMesaj" class="mesaj"></div>
</div>
<!-- Giriş Formu -->
<div class="form-kutusu">
<h3>Giriş Yap</h3>
<input type="email"
id="girisEposta" placeholder="E-posta"><br><br>
<input type="password" id="girisSifre" placeholder="Şifre"><br><br>
<button onclick="girisYap()">Giriş Yap</button>
<div id="girisMesaj" class="mesaj"></div>
</div>
<script>
// Sayfa yüklenir yüklenmez hafızada
tutulacak CSRF Jetonu değişkeni
let
aktifCsrfToken = "";
// 1. Sayfa Açıldığında Sunucudan Gizli CSRF Jetonunu
İstiyoruz (SPA Başlangıcı)
async
function csrfTokenAl() {
try {
const yanit = await fetch('/api/csrf-token');
const veri = await yanit.json();
aktifCsrfToken = veri.csrfToken;
// Jeton hafızaya alındı
console.log("CSRF Jetonu Başarıyla Alındı ve Hafızaya Kilitlendi.");
} catch (err) {
console.error("CSRF
Jetonu alınamadı!");
}
}
csrfTokenAl();
// 2. Fetch ile Sayfa
Yenilenmeden Kayıt Olma İşlemi (Client-Side Rendering)
async
function kayitOl() {
const ad = document.getElementById('kayitAd').value;
const eposta = document.getElementById('kayitEposta').value;
const sifre = document.getElementById('kayitSifre').value;
const mesajKutusu = document.getElementById('kayitMesaj');
// JavaScript ile Sunucuya İstek
Paketi Hazırlama
const yanit = await fetch('/api/kayit-ol', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': aktifCsrfToken // Güvenlik duvarını geçmek için
zorunlu başlık!
},
body: JSON.stringify({ kullaniciAdi:
ad, eposta: eposta, sifre: sifre
})
});
const veri = await yanit.json();
mesajKutusu.style.display = "block";
if (yanit.ok) {
mesajKutusu.className
= "mesaj basari";
mesajKutusu.innerText
= veri.mesaj;
} else {
mesajKutusu.className
= "mesaj hata";
mesajKutusu.innerText
= veri.hata;
}
}
// 3. Fetch ile Giriş Yapma
İşlemi
async
function girisYap() {
const eposta = document.getElementById('girisEposta').value;
const sifre = document.getElementById('girisSifre').value;
const mesajKutusu = document.getElementById('girisMesaj');
const yanit = await fetch('/api/giris-yap', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': aktifCsrfToken // Her güvenli POST işleminde bu
gönderilmelidir
},
body: JSON.stringify({ eposta, sifre })
});
const veri = await yanit.json();
mesajKutusu.style.display = "block";
if (yanit.ok) {
mesajKutusu.className
= "mesaj basari";
mesajKutusu.innerText
= veri.mesaj;
} else {
mesajKutusu.className
= "mesaj hata";
mesajKutusu.innerText
= veri.hata;
}
}
</script>
</body>
</html>
🔍 Ön Yüz ve SPA Mantığının Derin Analiz Değerlendirmesi:
Harika bir
noktaya geldik! Bu aşamada projemizi tam teşekküllü bir sosyal ağ platformuna
dönüştürüyoruz. Müfredatta yer alan en heyecan verici üç ileri seviye özelliği
uygulamamıza kazandıracağız: Socket.IO ile Canlı Sohbet, Anlık Form
Doğrulama (Live Validation) ve Takipçi
İlişkisi Veri Tabanı Mimarisi (Follower DB Design).
Aşağıda hem
sunucu (server.js) hem de istemci (public/index.html) tarafındaki
güncellemeleri tüm derinliğiyle hazırladım.
1. Bölüm: Geliştirilmiş Arka Yüz: server.js
Bu kodda
Socket.IO entegrasyonu yapılmış, Takipçi şeması eklenmiş ve anlık e-posta
kontrolü için özel bir API kapısı açılmıştır.
// server.js
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const { doubleCsrf } = require('csrf-csrf');
const http = require('http'); // Socket.IO
için HTTP modülü şarttır
const { Server } = require('socket.io'); // Canlı
Sohbet kütüphanesi
const app = express();
const server = http.createServer(app); // Express'i HTTP sunucusuna sarıyoruz
const io = new
Server(server); // Socket.IO'yu
bu sunucuya bağlıyoruz
// 1. MongoDB
Bağlantısı ve Yeni Şemalar (Follower DB Design)
mongoose.connect('mongodb://127.0.0.1:27017/uyelikSistemi')
.then(()
=> console.log("MongoDB
Bağlantısı Başarılı."))
.catch(err => console.error(err));
// Kullanıcı Şeması
const KullaniciSchema = new mongoose.Schema({
kullaniciAdi: { type: String, required: true },
eposta: { type: String, required: true, unique: true },
sifre: { type: String, required: true }
});
const Kullanici = mongoose.model('Kullanici', KullaniciSchema);
// Takipçi İlişkisi Şeması (Follower Model)
// Kim, kimi takip ediyor? İki
veriyi de Kullanıcı ID'si olarak tutuyoruz.
const TakipSchema = new mongoose.Schema({
takipEdenId: { type: mongoose.Schema.Types.ObjectId, ref: 'Kullanici', required: true },
takipEdilenId: { type: mongoose.Schema.Types.ObjectId, ref: 'Kullanici', required: true }
});
// Aynı kişinin aynı kişiyi iki kez
takip etmesini engellemek için benzersiz indeks
TakipSchema.index({ takipEdenId: 1, takipEdilenId: 1 }, { unique: true });
const Takip = mongoose.model('Takip', TakipSchema);
// 2. Temel Ayarlar ve CSRF
app.use(express.static('public'));
app.use(express.json());
app.use(cookieParser("gizli_cerez_sifresi"));
const sessionMiddleware = session({
secret: 'cok_gizli_oturum_anahtari',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 1000 * 60 * 60 * 24 }
});
app.use(sessionMiddleware);
// Socket.IO'nun
session verilerine erişebilmesi için köprü kuruyoruz
io.use((socket, next) => {
sessionMiddleware(socket.request, {}, next);
});
const { generateToken, doubleCsrfProtection, invalidCsrfTokenErrorMiddleware } = doubleCsrf({
getSecret: () => "ust_duzey_gizli_csrf_sirri",
cookieName: "x-csrf-token",
getTokenFromRequest: (req) => req.headers["x-csrf-token"]
});
app.get('/api/csrf-token', (req, res) => {
res.json({
csrfToken: generateToken(req, res) });
});
// 3. ANLIK DOĞRULAMA API KAPISI
(Live Validation)
// Bu kapı güvenlik duvarının
(CSRF) önünde olmalıdır çünkü kullanıcı daha formu göndermedi
app.post('/api/kontrol-eposta', async (req, res) => {
try {
const { eposta } = req.body;
const varMi = await Kullanici.findOne({ eposta });
if (varMi) {
return res.json({ durum: "alinmis", mesaj: "Bu
e-posta adresi zaten kullanımda!" });
}
res.json({
durum: "uygun", mesaj: "E-posta adresi kullanılabilir." });
} catch (e) {
res.status(500).json({ hata: "Kontrol
edilemedi." });
}
});
// 4. Standart Kayıt ve Giriş
Rotaları
app.post('/api/kayit-ol', doubleCsrfProtection, async (req, res) => {
try {
const { kullaniciAdi, eposta, sifre } = req.body;
if (!kullaniciAdi || !eposta || !sifre || sifre.length < 6) return res.status(400).json({ hata: "Eksik veri!"
});
const kriptoluSifre = await bcrypt.hash(sifre, 10);
const yeniKullanici = new Kullanici({ kullaniciAdi, eposta,
sifre: kriptoluSifre });
await yeniKullanici.save();
res.status(201).json({ mesaj: "Kayıt
başarıyla tamamlandı!"
});
} catch (e) { res.status(500).json({ hata: "Kayıt
hatası." }); }
});
app.post('/api/giris-yap', doubleCsrfProtection, async (req, res) => {
try {
const { eposta, sifre } = req.body;
const kullanici = await Kullanici.findOne({ eposta });
if (!kullanici || !(await bcrypt.compare(sifre, kullanici.sifre))) {
return res.status(400).json({ hata: "Hatalı
giriş!" });
}
req.session.kullanici
= { id: kullanici._id, ad: kullanici.kullaniciAdi };
res.json({
mesaj: "Giriş başarılı!", kullanici: req.session.kullanici });
} catch (e) { res.status(500).json({ hata: "Giriş
hatası." }); }
});
// 5. TAKİPÇİ SİSTEMİ ROTALARI (Follower System)
// Takip Etme Rotası
app.post('/api/takip-et', doubleCsrfProtection, async (req, res) => {
if (!req.session.kullanici) return res.status(401).json({ hata: "Önce
giriş yapmalısınız!" });
try {
const { takipEdilenEposta } = req.body;
const hedefKullanici = await Kullanici.findOne({ eposta: takipEdilenEposta });
if (!hedefKullanici) return res.status(404).json({ hata: "Takip edilecek kullanıcı bulunamadı!" });
if (hedefKullanici._id.toString()
=== req.session.kullanici.id.toString()) {
return res.status(400).json({ hata: "Kendinizi
takip edemezsiniz!" });
}
const yeniTakip = new Takip({
takipEdenId: req.session.kullanici.id,
takipEdilenId: hedefKullanici._id
});
await yeniTakip.save();
res.json({
mesaj: `${hedefKullanici.kullaniciAdi} başarıyla takip edildi!`
});
} catch (e) { res.status(400).json({ hata: "Zaten
takip ediyorsunuz veya bir hata oluştu." }); }
});
// 6. CANLI SOHBET MOTORU (Live Chat - Socket.IO)
io.on('connection', (socket) => {
// Sokete
bağlanan kişinin oturum verilerine bakıyoruz
const oturum = socket.request.session;
const kullaniciAdi = oturum.kullanici ? oturum.kullanici.ad : "Anonim
Misafir";
console.log(`${kullaniciAdi} canlı sohbete bağlandı.`);
// Bir
kullanıcı mesaj gönderdiğinde tetiklenir
socket.on('yeniMesaj', (mesajMetni) => {
// Gelen
mesajı, gönderenin adıyla birlikte odadaki HERKESE saliseler içinde dağıtıyoruz
io.emit('odaMesaji', {
gonderen: kullaniciAdi,
metin: mesajMetni,
zaman: new Date().toLocaleTimeString()
});
});
socket.on('disconnect', () => {
console.log(`${kullaniciAdi} odadan ayrıldı.`);
});
});
app.use(invalidCsrfTokenErrorMiddleware);
// Sunucuyu app.listen yerine server.listen
ile başlatıyoruz çünkü Socket.IO bunu gerektirir
server.listen(3000, () => console.log("Sosyal SPA Sunucusu port 3000'de çalışıyor..."));
2. Bölüm: Ön Yüz (Front-End) Kodları: public/index.html
Bu sayfada,
harf yazıldığı an tetiklenen canlı doğrulama motoru, takip butonu ve Socket.IO
canlı sohbet arayüzü tek sayfada buluşur.
<!-- public/index.html -->
<!DOCTYPE
html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>Gelişmiş Sosyal SPA Uygulaması</title>
<!-- Socket.IO'nun
tarayıcı kütüphanesini sunucumuzdan otomatik çekiyoruz -->
<script src="/socket.io/socket.io.js"></script>
<style>
body { font-family: Arial, sans-serif; display: flex; gap: 20px; padding: 30px; background: #eef2f3; }
.sol-kolon, .sag-kolon { flex: 1; }
.kart {
background: white; padding:
20px;
border-radius: 8px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
.uyari-yazisi { font-size: 12px; font-weight: bold; margin-top: 5px; }
.uygun { color: green; }
.alinmis
{ color: red; }
#sohbetKutusu { height: 200px; border: 1px solid
#ccc; overflow-y: scroll; padding: 10px; background: #fafafa; margin-bottom:
10px;
}
</style>
</head>
<body>
<div class="sol-kolon">
<!-- Kayıt Kartı (Live Validation İçerir) -->
<div class="kart">
<h3>Kayıt Ol</h3>
<input type="text"
id="kayitAd" placeholder="Kullanıcı Adı"><br><br>
<!-- input
olayı: Kullanıcı her harf yazdığında tetiklenir -->
<input type="email"
id="kayitEposta" placeholder="E-posta" oninput="epostaKontrolEt()"><br>
<div id="epostaUyari" class="uyari-yazisi"></div><br>
<input type="password" id="kayitSifre" placeholder="Şifre"><br><br>
<button onclick="kayitOl()">Hesap Oluştur</button>
</div>
<!-- Giriş ve Takip Kartı -->
<div class="kart">
<h3>Giriş & Etkileşim</h3>
<input type="email"
id="girisEposta" placeholder="E-posta"><br><br>
<input type="password" id="girisSifre" placeholder="Şifre"><br><br>
<button onclick="girisYap()">Giriş Yap</button>
<hr>
<h4>Kullanıcı Takip Et</h4>
<input type="email"
id="takipHedef" placeholder="Takip edilecek e-posta"><br><br>
<button onclick="takipEt()">Takip Et</button>
</div>
</div>
<div class="sag-kolon">
<!-- Canlı Sohbet Odası (Socket.IO)
-->
<div class="kart">
<h3>Küresel Canlı Sohbet (Socket.IO)</h3>
<div id="sohbetKutusu"></div>
<input type="text"
id="mesajGirdisi" placeholder="Mesajınızı yazın..." style="width: 70%;">
<button onclick="mesajGonder()" style="width: 25%;">Gönder</button>
</div>
</div>
<script>
let
aktifCsrfToken = "";
let
socket; // Socket bağlantı
nesnesi
// CSRF Jetonu Alımı
async
function tokenYenile() {
const y = await fetch('/api/csrf-token');
const v = await y.json();
aktifCsrfToken = v.csrfToken;
}
tokenYenile();
// 1. CANLI DOĞRULAMA MOTORU (Live Validation)
async
function epostaKontrolEt()
{
const eposta = document.getElementById('kayitEposta').value;
const uyariKutusu = document.getElementById('epostaUyari');
if (eposta.length < 5) {
uyariKutusu.innerText
= "";
return;
}
// Sayfa yenilenmeden arka planda
e-posta sorgusu fırlatılıyor
const yanit = await fetch('/api/kontrol-eposta', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ eposta })
});
const veri = await yanit.json();
// Gelen cevaba göre anında görsel
sınıf ataması yapılıyor
if (veri.durum === "alinmis") {
uyariKutusu.className
= "uyari-yazisi alinmis";
} else {
uyariKutusu.className
= "uyari-yazisi
uygun";
}
uyariKutusu.innerText
= veri.mesaj;
}
// 2. Standart Kayıt İşlemi
async
function kayitOl() {
const yanit = await fetch('/api/kayit-ol', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': aktifCsrfToken },
body: JSON.stringify({
kullaniciAdi: document.getElementById('kayitAd').value,
eposta:
document.getElementById('kayitEposta').value,
sifre: document.getElementById('kayitSifre').value
})
});
const v = await yanit.json();
alert(v.mesaj
|| v.hata);
}
// 3. Giriş İşlemi ve Sohbet Hattını Ateşleme
async
function girisYap() {
const yanit = await fetch('/api/giris-yap', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': aktifCsrfToken },
body: JSON.stringify({
eposta:
document.getElementById('girisEposta').value,
sifre: document.getElementById('girisSifre').value
})
});
const v = await yanit.json();
alert(v.mesaj
|| v.hata);
if (yanit.ok) {
// Giriş başarılıysa Socket hattını açıyoruz!
sohbetiBaslat();
}
}
// 4. TAKİP ETME İŞLEMİ (Follow
Action)
async
function takipEt() {
const yanit = await fetch('/api/takip-et', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': aktifCsrfToken },
body: JSON.stringify({ takipEdilenEposta:
document.getElementById('takipHedef').value })
});
const v = await yanit.json();
alert(v.mesaj
|| v.hata);
}
// 5. SOCKET.IO CANLI SOHBET YÖNETİMİ (Live Chat)
function
sohbetiBaslat() {
// Sunucu ile sürekli açık kalacak
olan WebSocket köprüsünü kuruyoruz
socket = io();
// Sunucudan yeni bir mesaj odasına
düştüğünde yakala
socket.on('odaMesaji',
(veri) => {
const
kutu = document.getElementById('sohbetKutusu');
kutu.innerHTML += `<p><strong>[${veri.zaman}] ${veri.gonderen}:</strong> ${veri.metin}</p>`;
kutu.scrollTop = kutu.scrollHeight;
// Sohbeti otomatik aşağı kaydır
});
}
function
mesajGonder() {
const girdi = document.getElementById('mesajGirdisi');
if (!socket) {
alert("Önce giriş yapmalısınız!");
return;
}
if (girdi.value.trim() !== "") {
// Mesajı sunucuya fırlatıyoruz
socket.emit('yeniMesaj', girdi.value);
girdi.value = "";
}
}
</script>
</body>
</html>
🔍 Gelişmiş Özelliklerin En Derin Değerlendirilmesi: