Takip et

Node.js ve Express ile REST API: Sıfırdan Üretime Tam Kılavuz

Node.js ve Express kullanarak sıfırdan güvenli, ölçeklenebilir bir REST API nasıl oluşturulur? Temel kavramlardan dağıtıma uzanan bu kapsamlı rehberle kendi API’nizi geliştirin ve modern web uygulamalarının gücünü keşfedin.

Günümüz dijital dünyasında, uygulamalarımızın birbirleriyle ve veri kaynaklarıyla etkileşime girmesi temel bir gerekliliktir. Akıllı telefonlarımızdaki mobil uygulamalardan, web tarayıcımızdaki karmaşık tek sayfalık uygulamalara (SPA) kadar her şey, arka planda bir sunucuyla iletişim kurarak çalışır. İşte bu noktada REST API’ler (Representational State Transfer Application Programming Interface) devreye girer. API’ler, farklı yazılım sistemlerinin birbiriyle güvenli ve standart bir şekilde konuşmasını sağlayan köprülerdir. Bir API, uygulamanızın belirli verileri almasını, göndermesini, güncellemesini veya silmesini sağlayan bir dizi kural ve protokol setidir. Örneğin, bir hava durumu uygulaması, hava durumu verilerini almak için üçüncü taraf bir hava durumu API’sine istek gönderir. Sosyal medya uygulamaları ise gönderileri, yorumları veya kullanıcı bilgilerini yönetmek için kendi REST API’lerini kullanır.

Peki, bir API oluşturmak için neden Node.js ve Express’i seçmeliyiz? Node.js, Google Chrome’un V8 JavaScript motoru üzerine kurulu, sunucu tarafı JavaScript çalışma zamanı ortamıdır. Bu, geliştiricilerin hem ön uçta hem de arka uçta aynı dili kullanabilmesini sağlar, bu da öğrenme eğrisini düşürür ve geliştirme süreçlerini hızlandırır. Node.js’in temel avantajlarından biri, olay tabanlı, eşzamansız ve engellemeyen G/Ç modeli sayesinde yüksek performanslı ve ölçeklenebilir uygulamalar geliştirmeye olanak tanımasıdır. Özellikle yoğun veri trafiği olan ve anlık tepki vermesi gereken uygulamalar için idealdir. Bir istek gelir, Node.js bu isteği işler ve başka bir işlemi beklemek yerine hemen bir sonraki isteğe geçer. Bu, sunucunun aynı anda çok sayıda bağlantıyı etkin bir şekilde yönetebileceği anlamına gelir.

Express.js ise Node.js için minimalist ve esnek bir web uygulama çatısıdır (framework). Node.js’in düşük seviyeli API’lerini kullanarak web uygulamaları ve REST API’leri oluşturmayı çok daha kolay ve hızlı hale getirir. Yönlendirme, middleware yönetimi, şablon motoru entegrasyonu gibi temel web işlevlerini basitleştirir. Express’in hafif yapısı, projenizin ihtiyaçlarına göre özelleştirilebilen modüler bir yapı sunar. Büyük ve aktif bir topluluğa sahip olması sayesinde, karşılaşabileceğiniz hemen hemen her sorun için kolayca çözüm bulabilir ve çok sayıda üçüncü taraf kütüphaneden faydalanabilirsiniz. Bu kombinasyon, Node.js’in performansı ile Express’in geliştirme kolaylığını bir araya getirerek, sıfırdan üretime geçiş sürecinde güçlü ve verimli bir çözüm sunar. Örneğin, bir e-ticaret uygulamasında ürün listeleme, sepet yönetimi, ödeme işlemleri gibi kritik adımların her biri arka uçta çalışan güvenilir bir REST API’ye ihtiyaç duyar. Node.js ve Express bu tür bir sistemin bel kemiğini oluşturmak için mükemmel araçlardır.

Uzman İpucu: Node.js’in asenkron yapısı, özellikle I/O yoğun (veritabanı işlemleri, dosya okuma/yazma, ağ istekleri) uygulamalar için büyük bir performans avantajı sağlar. Ancak, CPU yoğun işlemler (karmaşık hesaplamalar) için Node.js’in tek iş parçacıklı yapısı darboğaz oluşturabilir. Bu tür durumlarda Worker Threads veya mikroservis mimarileri düşünülebilir.

Temel Kavramlar: REST API Nedir ve Nasıl Çalışır?

REST API’ler hakkında konuşurken, “REST” kelimesinin tam olarak ne anlama geldiğini anlamak önemlidir. REST, bir mimari stilidir; yani, dağıtık sistemlerin nasıl tasarlanması gerektiğine dair bir dizi ilke ve kısıtlamadır. Roy Fielding tarafından 2000 yılında doktora tezinde tanımlanmıştır. REST API’lerin temel amacı, istemci-sunucu arasındaki iletişimi basit, standartlaştırılmış ve durumsal olmayan (stateless) bir şekilde sağlamaktır. Bu mimarinin temel ilkeleri şunlardır:

  1. İstemci-Sunucu (Client-Server): İstemci (tarayıcı, mobil uygulama vb.) ve sunucu birbirinden bağımsızdır. Her ikisi de kendi sorumluluk alanına sahiptir. İstemci sadece kullanıcı arayüzü ve sunucuyla etkileşimden sorumluyken, sunucu veriyi depolama, işleme ve istemciye sunmaktan sorumludur. Bu ayrım, farklı istemcilerin aynı API’yi kullanmasına olanak tanır.
  2. Durumsuzluk (Statelessness): Her istek, sunucuya tüm gerekli bilgiyi içermelidir. Sunucu, istemciden gelen önceki hiçbir isteğin durumunu hatırlamaz. Bu, sunucunun her isteği bağımsız olarak ele almasını sağlar ve ölçeklenebilirliği artırır, çünkü sunucu tarafında oturum bilgisi tutulmasına gerek kalmaz.
  3. Önbelleklenebilirlik (Cacheability): İstemci tarafında önbelleğe alınabilecek yanıtlar açıkça belirtilmelidir. Bu, ağ trafiğini azaltır ve performansı artırır, çünkü sık talep edilen veriler sunucudan tekrar tekrar istenmez.
  4. Katmanlı Sistem (Layered System): İstemci, son sunucuya mı yoksa bir ara katmandaki (proxy, yük dengeleyici vb.) bir sunucuya mı bağlı olduğunu bilmez. Bu, sistemin esnekliğini ve ölçeklenebilirliğini artırır.
  5. Birleşik Arayüz (Uniform Interface): Bu en kritik ilkedir ve REST’i diğer mimarilerden ayıran ana faktördür.
    • Kaynakların Tanımlanması (Identification of Resources): Her kaynağın (örneğin, bir kullanıcı, bir ürün) benzersiz bir URI (Uniform Resource Identifier) ile tanımlanması gerekir. Örneğin, /api/urunler/123.
    • Kaynakların Temsili Aracılığıyla Manipülasyon (Manipulation of Resources Through Representations): İstemci, bir kaynağın temsilini (genellikle JSON veya XML formatında) alarak veya göndererek o kaynağı manipüle eder.
    • Kendi Kendini Açıklayıcı Mesajlar (Self-descriptive Messages): Her mesaj, istemcinin isteği veya yanıtı nasıl işleyeceğini anlayabilmesi için yeterli bilgiyi içermelidir. HTTP başlıkları bu konuda önemli bir rol oynar.
    • Uygulama Durumunun Motoru Olarak HATEOAS (Hypermedia As The Engine Of Application State): Bu, API’nin yanıtlarında ilgili eylemler veya kaynaklara bağlantılar içermesi gerektiği anlamına gelir. İstemci, bu bağlantıları izleyerek uygulamanın durumunu keşfeder ve değiştirir. Bu prensip genellikle en az uygulananıdır ama REST’in “seviyelerini” belirleyen önemli bir kriterdir.

REST API’ler genellikle HTTP protokolü üzerinde çalışır ve HTTP metodlarını (fiillerini) kaynaklar üzerinde gerçekleştirebileceğiniz eylemleri belirtmek için kullanır:

  • GET: Bir veya daha fazla kaynak almak için kullanılır. Sunucu üzerinde herhangi bir değişiklik yapmaz. (Örnek: GET /api/urunler, GET /api/urunler/123)
  • POST: Yeni bir kaynak oluşturmak için kullanılır. (Örnek: POST /api/urunler, istek gövdesinde yeni ürün bilgileri ile)
  • PUT: Var olan bir kaynağı tamamen güncellemek için kullanılır. (Örnek: PUT /api/urunler/123, istek gövdesinde ürünün güncel hali ile)
  • PATCH: Var olan bir kaynağın sadece belirli bir kısmını güncellemek için kullanılır. (Örnek: PATCH /api/urunler/123, istek gövdesinde sadece değişecek alanlar ile)
  • DELETE: Bir kaynağı silmek için kullanılır. (Örnek: DELETE /api/urunler/123)

API istekleri genellikle JSON (JavaScript Object Notation) formatında veri taşır. JSON, hem insanlar tarafından okunabilir hem de makineler tarafından kolayca ayrıştırılabilir olduğu için web API’lerinde en yaygın veri değişim formatıdır. Her isteğe karşılık sunucu bir yanıt döner ve bu yanıt bir HTTP durum kodu (örneğin, 200 OK, 404 Not Found, 500 Internal Server Error) ile birlikte gelir. Bu kodlar, isteğin başarılı olup olmadığını veya hangi tür bir hatanın oluştuğunu gösterir. Bir e-ticaret uygulamasının ürün kataloğu API’si üzerinden örneklendirecek olursak: bir mobil uygulama yeni ürünleri listelemek için GET /api/products isteği gönderir, bir yönetici yeni bir ürün eklemek için POST /api/products isteği kullanır. Bu süreç, REST’in sağladığı standartlar sayesinde sorunsuz bir şekilde ilerler. Bu temel kavramları anlamak, sağlam ve sürdürülebilir bir REST API oluşturmanın ilk adımıdır.

Uzman İpucu: REST API’leri tasarlarken kaynak adlarının (endpoint’lerin) çoğul isimler kullanmasına dikkat edin (örneğin, /api/users yerine /api/user). Bu, API’nin daha tutarlı ve anlaşılır olmasını sağlar. Ayrıca, kaynaklar arasında ilişkileri belirtmek için iç içe URI’lar kullanmaktan kaçının (örneğin, /api/users/1/posts yerine /api/posts?userId=1).

Başlangıç: Node.js ve Express Ortamını Kurulumu ve İlk API’mızı Oluşturma

Sıfırdan bir REST API oluşturmaya başlamadan önce, geliştirme ortamımızı kurmamız gerekiyor. Node.js ve Express ile çalışmak için öncelikle Node.js’in sistemimizde yüklü olması şart. Node.js’i resmi web sitesinden indirebilir veya daha esnek bir yönetim için NVM (Node Version Manager) kullanabilirsiniz. NVM, farklı Node.js sürümleri arasında kolayca geçiş yapmanızı sağlar, bu da farklı projelerle çalışırken oldukça faydalıdır. Kurulum tamamlandıktan sonra, terminalinizde node -v ve npm -v komutlarını çalıştırarak Node.js ve npm’in (Node Package Manager) başarılı bir şekilde yüklendiğini doğrulayabilirsiniz.

Proje Klasörü Oluşturma ve Express’i Kurma

İlk adım, projeniz için bir klasör oluşturmak ve terminalde bu klasöre gitmek olmalıdır:


mkdir my-first-api
cd my-first-api

Ardından, npm kullanarak yeni bir Node.js projesi başlatacağız. Bu komut, projenizin metadata'sını içeren bir package.json dosyası oluşturacaktır. Soruları varsayılan değerlerle geçmek için -y bayrağını kullanabilirsiniz:


npm init -y

Şimdi projemize Express.js'i kurabiliriz:


npm install express

Ayrıca, geliştirme sürecimizi hızlandırmak için nodemon adlı bir aracı kurmanızı şiddetle tavsiye ederim. Nodemon, kodunuzda yaptığınız değişiklikleri algılar ve sunucuyu otomatik olarak yeniden başlatır, böylece her değişiklikten sonra sunucuyu manuel olarak kapatıp açmak zorunda kalmazsınız:


npm install --save-dev nodemon

package.json dosyanıza, sunucuyu nodemon ile başlatmak için bir "start" veya "dev" komutu ekleyelim:


{
  "name": "my-first-api",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

İlk API'mızı Oluşturma: Hello World!

Proje klasörünüzde app.js adında yeni bir dosya oluşturun ve aşağıdaki kodu içine yapıştırın:


// Gerekli modülleri içeri aktarıyoruz
const express = require('express'); // Express framework'ünü dahil ediyoruz
const app = express(); // Express uygulamasını oluşturuyoruz
const port = process.env.PORT || 3000; // Sunucunun çalışacağı portu belirliyoruz, eğer ortam değişkeni varsa onu kullan, yoksa 3000

// Gelen JSON isteklerini ayrıştırmak için Express'in yerleşik middleware'ini kullanıyoruz
app.use(express.json());

// Kök dizin için basit bir GET isteği yanıtı tanımlıyoruz
app.get('/', (req, res) => {
  res.send('Node.js ve Express ile REST API Rehberine Hoş Geldiniz!');
});

// "/api/selam" adında yeni bir GET endpoint'i oluşturuyoruz
app.get('/api/selam', (req, res) => {
  res.json({ message: 'Merhaba dünya, ilk REST API endpoint\'iniz çalışıyor!' });
});

// Parametre alan bir GET endpoint'i. Örneğin: /api/kullanici/Ahmet
app.get('/api/kullanici/:isim', (req, res) => {
  const isim = req.params.isim; // URL'deki parametreyi alıyoruz
  res.json({ message: Merhaba, ${isim}! Kullanıcı profilinize hoş geldiniz. });
});

// POST isteği ile veri gönderme örneği
app.post('/api/kayit', (req, res) => {
  const { kullaniciAdi, sifre } = req.body; // İstek gövdesinden verileri alıyoruz
  // Gerçek bir uygulamada burada veritabanına kayıt işlemi yapılır
  if (kullaniciAdi && sifre) {
    res.status(201).json({ message: Kullanıcı ${kullaniciAdi} başarıyla kaydedildi!, data: { kullaniciAdi } });
  } else {
    res.status(400).json({ message: 'Kullanıcı adı ve şifre gerekli.' });
  }
});

// Sunucuyu belirtilen port üzerinden dinlemeye başlıyoruz
app.listen(port, () => {
  console.log(Sunucu http://localhost:${port} adresinde çalışıyor...);
});

Bu kodda neler olduğuna bir göz atalım:

  • const express = require('express');: Express modülünü projemize dahil ediyoruz.
  • const app = express();: Express uygulamasının bir örneğini oluşturuyoruz. Tüm API rotalarımız ve middleware'lerimiz bu app objesi üzerinden tanımlanacak.
  • const port = process.env.PORT || 3000;: Sunucumuzun çalışacağı portu belirliyoruz. Eğer bir ortam değişkeni (PORT) tanımlıysa onu kullanacak, yoksa varsayılan olarak 3000 portunu seçecek.
  • app.use(express.json());: Bu satır, Express'e gelen tüm isteklerin gövdesindeki JSON verilerini otomatik olarak ayrıştırmasını söyler. Böylece req.body üzerinden gönderilen verilere kolayca erişebiliriz.
  • app.get('/', ...): Kök URL'e (/) yapılan bir GET isteğine nasıl yanıt verileceğini tanımlıyoruz. res.send() metodu ile basit bir metin yanıtı gönderiyoruz.
  • app.get('/api/selam', ...): /api/selam yoluna yapılan GET isteği için bir endpoint tanımlıyoruz. res.json() metodu ile bir JSON objesi yanıt olarak gönderiyoruz.
  • app.get('/api/kullanici/:isim', ...): URL parametrelerini nasıl yakalayacağımızı gösteren bir örnek. :isim kısmı bir placeholder'dır ve URL'de bu kısma gelen değer req.params.isim ile erişilebilir.
  • app.post('/api/kayit', ...): /api/kayit yoluna yapılan POST isteği için bir endpoint tanımlıyoruz. Bu endpoint, kullanıcı adı ve şifre gibi verilerin istek gövdesinde nasıl alındığını gösterir. Gerçek bir uygulamada burada bir veritabanı işlemi gerçekleştirilirdi.
  • app.listen(port, ...): Sunucuyu belirtilen port üzerinden dinlemeye başlatır. Sunucu başarıyla başladığında konsola bir mesaj yazdırır.

Şimdi projemizi çalıştırmak için terminalde şu komutu yazın:


npm run dev

Konsolda "Sunucu http://localhost:3000 adresinde çalışıyor..." mesajını görmelisiniz. Artık tarayıcınızdan veya Postman gibi bir API test aracı kullanarak http://localhost:3000, http://localhost:3000/api/selam veya http://localhost:3000/api/kullanici/Ali adreslerini ziyaret ederek API'nizin çalıştığını görebilirsiniz. POST /api/kayit endpoint'ini test etmek için Postman gibi bir araç kullanmanız gerekecektir. İstek gövdesinde (Body) Raw JSON olarak {"kullaniciAdi": "testuser", "sifre": "password123"} gönderebilirsiniz.

Bu temel kurulum ve ilk API endpoint'lerinin oluşturulmasıyla, Node.js ve Express dünyasına sağlam bir adım atmış oluyorsunuz. Bir sonraki bölümde, bu API'ye kalıcı veri depolama yeteneği kazandırmak için bir veritabanını nasıl entegre edeceğimizi inceleyeceğiz.

Veritabanı Entegrasyonu: MongoDB ile Kalıcı Veri Depolama Nasıl Sağlanır?

Bir REST API, genellikle verileri depolamak, almak ve manipüle etmek için bir veritabanına ihtiyaç duyar. Önceki bölümde oluşturduğumuz "Hello World" API'si sadece statik yanıtlar döndürüyordu. Ancak gerçek dünya uygulamalarında, kullanıcılar, ürünler, gönderiler gibi dinamik verileri yönetmemiz gerekir. Bu verilerin kalıcı olması, yani sunucu yeniden başlatıldığında bile kaybolmaması için bir veritabanı şarttır. Piyasada birçok farklı veritabanı türü bulunsa da, Node.js ekosistemiyle harika bir uyum sağlayan ve esnek yapısıyla dikkat çeken MongoDB (NoSQL bir veritabanı) ve onun Node.js sürücüsü olan Mongoose'u kullanacağız. MongoDB, JSON benzeri BSON (Binary JSON) formatında belge tabanlı veri depolayan, şemasız bir veritabanıdır, bu da hızlı geliştirme ve değişen veri yapılarına kolay uyum sağlar.

MongoDB ve Mongoose Kurulumu

Öncelikle, sisteminizde MongoDB'nin kurulu ve çalışır durumda olması gerekir. Yerel olarak kurabilir veya MongoDB Atlas gibi bulut tabanlı bir hizmet kullanabilirsiniz. Basitlik açısından, bu rehberde yerel bir kurulum varsayılacaktır. Eğer yerel kurulumunuz yoksa, MongoDB'nin resmi belgelerini takip ederek kurabilirsiniz.

MongoDB çalışır durumdayken, projemize Mongoose'u kurmalıyız:


npm install mongoose

Veritabanı Bağlantısı ve Model Tanımı

app.js dosyamızda veya ayrı bir db.js dosyasında veritabanı bağlantımızı kuralım. Kolaylık sağlaması açısından şimdilik app.js içine ekleyelim:


// app.js dosyanızın en üstüne veya bağlantı kurulacak yere ekleyin
const mongoose = require('mongoose');

// MongoDB bağlantı URL'si. Kendi veritabanı adınızı belirtebilirsiniz.
const dbURI = 'mongodb://localhost:27017/myapi_db'; 

mongoose.connect(dbURI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => console.log('MongoDB bağlantısı başarılı.'))
.catch(err => console.error('MongoDB bağlantı hatası:', err));

// Bir örnek veri modeli tanımlayalım: "Item" (Öğe)
// Her öğenin bir adı (String) ve bir açıklaması (String) olsun.
const itemSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true
  },
  description: {
    type: String,
    required: false,
    trim: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

// Şemadan bir model oluşturuyoruz. Mongoose otomatik olarak 'items' koleksiyonunu oluşturacaktır.
const Item = mongoose.model('Item', itemSchema);

// ... mevcut Express uygulamanızın diğer kodları ...

Yukarıdaki kodda:

  • mongoose.connect() ile MongoDB'ye bağlanıyoruz. Bağlantı dizesinde veritabanı sunucunuzun adresi (localhost:27017 varsayılan) ve kullanmak istediğiniz veritabanının adı (myapi_db) yer alır.
  • itemSchema, veritabanındaki "Item" koleksiyonunda saklanacak belgelerin yapısını tanımlar. name alanı zorunlu ve metin tipi iken, description alanı isteğe bağlıdır. createdAt alanı ise otomatik olarak oluşturulma tarihini tutar.
  • mongoose.model('Item', itemSchema); ile bu şemadan bir Mongoose modeli oluşturuyoruz. Bu model, koleksiyonumuzdaki belgelerle etkileşim kurmamızı sağlayacak arayüzdür.

CRUD İşlemleri için API Endpoint'leri Oluşturma

Şimdi, Item modelini kullanarak CRUD (Create, Read, Update, Delete) operasyonları için REST API endpoint'leri oluşturalım. Bu endpoint'leri app.js dosyanızdaki mevcut Express kodlarınızın altına ekleyebilirsiniz:


// ... mevcut app.js kodları ve Item modeli tanımından sonra ...

// 1. Tüm öğeleri getir (READ all)
app.get('/api/items', async (req, res) => {
  try {
    const items = await Item.find(); // Tüm öğeleri veritabanından çekiyoruz
    res.json(items);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 2. Belirli bir öğeyi ID'ye göre getir (READ one)
app.get('/api/items/:id', async (req, res) => {
  try {
    const item = await Item.findById(req.params.id); // ID'ye göre öğeyi bul
    if (item == null) {
      return res.status(404).json({ message: 'Öğe bulunamadı.' });
    }
    res.json(item);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 3. Yeni bir öğe oluştur (CREATE)
app.post('/api/items', async (req, res) => {
  const item = new Item({
    name: req.body.name,
    description: req.body.description
  });

  try {
    const newItem = await item.save(); // Yeni öğeyi veritabanına kaydet
    res.status(201).json(newItem); // Başarılı oluşturma (201 Created) ve yeni öğeyi döndür
  } catch (err) {
    res.status(400).json({ message: err.message }); // Hatalı istek (400 Bad Request)
  }
});

// 4. Belirli bir öğeyi güncelle (UPDATE)
app.put('/api/items/:id', async (req, res) => {
  try {
    const item = await Item.findById(req.params.id);
    if (item == null) {
      return res.status(404).json({ message: 'Öğe bulunamadı.' });
    }

    if (req.body.name != null) {
      item.name = req.body.name;
    }
    if (req.body.description != null) {
      item.description = req.body.description;
    }
    
    const updatedItem = await item.save(); // Güncellenmiş öğeyi kaydet
    res.json(updatedItem);
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

// 5. Belirli bir öğeyi sil (DELETE)
app.delete('/api/items/:id', async (req, res) => {
  try {
    const item = await Item.findById(req.params.id);
    if (item == null) {
      return res.status(404).json({ message: 'Öğe bulunamadı.' });
    }

    await item.deleteOne(); // Öğeyi sil
    res.json({ message: 'Öğe başarıyla silindi.' });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// ... app.listen kısmı ...

Bu kod bloğunda, her bir HTTP metodunu (GET, POST, PUT, DELETE) belirli bir CRUD operasyonuyla eşleştirdik. Async/await yapısını kullanarak asenkron veritabanı işlemlerini daha okunabilir hale getirdik. Her endpoint, olası hataları (örneğin, öğe bulunamadı, geçersiz istek) uygun HTTP durum kodları ile ele alacak bir try-catch bloğu içerir. Artık npm run dev ile sunucunuzu çalıştırdığınızda, Postman veya benzeri bir araçla bu endpoint'leri test edebilir, kendi verilerinizi ekleyip yönetebilirsiniz. Bu, API'nizin dinamik ve veritabanıyla etkileşimli hale gelmesi için önemli bir adımdır. Örneğin, bir görev listesi uygulaması için bu Item modelini Todo olarak adlandırabilir ve her bir görev için bir başlık ve tamamlama durumu tutabiliriz. Böylece, kullanıcılar yeni görevler ekleyebilir, mevcut görevleri güncelleyebilir veya tamamlanan görevleri silebilirler. Bu, Node.js ve Express ile dinamik bir veri yönetim sistemi oluşturmanın temelini oluşturur.

Güvenlik ve Kimlik Doğrulama: API'mizi Yetkisiz Erişimden Nasıl Koruruz?

Bir REST API geliştirmek sadece veri akışını sağlamakla bitmez; aynı zamanda bu veriyi yetkisiz erişime karşı korumak da kritik öneme sahiptir. Kullanıcıların kimliğini doğrulamak (Authentication) ve neye erişebileceklerini belirlemek (Authorization), modern API'lerin olmazsa olmazlarındandır. Bu bölümde, kullanıcı kaydı, girişi ve JSON Web Tokens (JWT) kullanarak API'mizi nasıl güvenli hale getireceğimizi inceleyeceğiz.

JSON Web Tokens (JWT) Nedir ve Neden Kullanırız?

JWT, JSON formatında bilgi içeren, imzalı ve isteğe bağlı olarak şifrelenmiş bir standarttır. Genellikle istemci ile sunucu arasında güvenli bir şekilde bilgi alışverişi yapmak için kullanılır. Bir kullanıcı giriş yaptığında, sunucu bir JWT oluşturur ve bu token'ı istemciye geri gönderir. İstemci, sonraki her istekte bu token'ı isteğin başlık kısmına (genellikle Authorization başlığı altında "Bearer" şemasıyla) ekler. Sunucu, gelen her istekte token'ı doğrulayarak kullanıcının kimliğini ve yetkisini anlar. JWT'nin avantajları arasında şunlar bulunur:

  • Durumsuzluk: Sunucuda oturum bilgisi tutmaya gerek kalmaz, bu da API'nin ölçeklenebilirliğini artırır.
  • Verimli: Küçük ve hafiftirler, ağ trafiğini azaltır.
  • Güvenli: Dijital olarak imzalandıkları için değiştirilemedikleri garanti edilir (ancak içerdikleri veriler şifrelenmediği sürece okunabilir).

Kullanıcı Modeli ve Şifreleme (Bcrypt)

Kullanıcı verilerini veritabanında saklamadan önce şifrelerini güvenli bir şekilde hash'lemeliyiz. Bunun için bcrypt.js kütüphanesini kullanacağız:


npm install bcryptjs jsonwebtoken

Şimdi bir User modeli tanımlayalım ve şifre hash'leme logic'ini ekleyelim:


// models/User.js (ayrı bir dosya oluşturmak daha düzenli olacaktır)
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs'); // Şifreleri hashlemek için

const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true,
    trim: true
  },
  email: {
    type: String,
    required: true,
    unique: true,
    trim: true,
    lowercase: true
  },
  password: {
    type: String,
    required: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

// Şifreyi kaydetmeden veya güncellemeden önce hash'le
UserSchema.pre('save', async function(next) {
  if (!this.isModified('password')) { // Şifre değişmediyse, hash'lemeye gerek yok
    return next();
  }
  const salt = await bcrypt.genSalt(10); // Tuz oluştur
  this.password = await bcrypt.hash(this.password, salt); // Şifreyi hash'le
  next();
});

// Şifre karşılaştırma metodu
UserSchema.methods.comparePassword = async function(candidatePassword) {
  return bcrypt.compare(candidatePassword, this.password);
};

module.exports = mongoose.model('User', UserSchema);

Bu User.js dosyasını ana app.js dosyanıza const User = require('./models/User'); şeklinde dahil etmeniz gerekecek. pre('save') hook'u sayesinde, her kullanıcı kaydolduğunda veya şifresi güncellendiğinde, şifre otomatik olarak hash'lenecektir. comparePassword metodu ise giriş sırasında şifreleri karşılaştırmak için kullanılacaktır.

Kayıt ve Giriş Endpoint'leri Oluşturma

Şimdi kullanıcıların kayıt olabileceği ve giriş yapabileceği endpoint'leri oluşturalım. Bu endpoint'lerde JWT oluşturulacak ve döndürülecektir. app.js dosyanıza ekleyin:


// app.js dosyanızda veya ayrı bir 'auth.js' yönlendirici dosyasında
const jwt = require('jsonwebtoken'); // JWT oluşturmak ve doğrulamak için
const User = require('./models/User'); // Daha önce tanımladığımız User modelini dahil ediyoruz

// JWT gizli anahtarınız, güçlü ve rastgele bir anahtar olmalı
const JWT_SECRET = process.env.JWT_SECRET || 'cokgizli_bir_anahtar'; 

// Yeni kullanıcı kaydı
app.post('/api/auth/register', async (req, res) => {
  try {
    const { username, email, password } = req.body;

    // Kullanıcı adı veya e-posta zaten mevcut mu kontrol et
    let user = await User.findOne({ $or: [{ username }, { email }] });
    if (user) {
      return res.status(400).json({ message: 'Bu kullanıcı adı veya e-posta zaten kullanımda.' });
    }

    user = new User({ username, email, password });
    await user.save(); // Şifre otomatik olarak hash'lenecek

    // JWT oluştur
    const token = jwt.sign({ userId: user._id }, JWT_SECRET, { expiresIn: '1h' });

    res.status(201).json({ message: 'Kullanıcı başarıyla kaydedildi.', token });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// Kullanıcı girişi
app.post('/api/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;

    // E-posta ile kullanıcıyı bul
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(400).json({ message: 'Geçersiz kimlik bilgileri.' });
    }

    // Şifreyi karşılaştır
    const isMatch = await user.comparePassword(password);
    if (!isMatch) {
      return res.status(400).json({ message: 'Geçersiz kimlik bilgileri.' });
    }

    // JWT oluştur
    const token = jwt.sign({ userId: user._id }, JWT_SECRET, { expiresIn: '1h' });

    res.json({ message: 'Başarıyla giriş yapıldı.', token });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

JWT Doğrulama Middleware'i Oluşturma

Şimdi, belirli endpoint'leri korumak için bir middleware (ara yazılım) oluşturalım. Bu middleware, her istekte token'ı doğrulayacak ve kullanıcı bilgilerini req objesine ekleyecektir:


// middleware/auth.js (ayrı bir dosya)
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'cokgizli_bir_anahtar'; 

const authMiddleware = (req, res, next) => {
  // Authorization başlığından token'ı al
  const token = req.header('Authorization');

  // Token yoksa yetkisiz erişim döndür
  if (!token) {
    return res.status(401).json({ message: 'Erişim reddedildi, token bulunamadı.' });
  }

  try {
    // "Bearer TOKEN_STRING" formatından token'ı ayır
    const actualToken = token.split(' ')[1]; 
    if (!actualToken) {
        return res.status(401).json({ message: 'Geçersiz token formatı.' });
    }

    // Token'ı doğrula
    const decoded = jwt.verify(actualToken, JWT_SECRET);
    req.userId = decoded.userId; // Kullanıcı ID'sini req objesine ekle
    next(); // Bir sonraki middleware veya route handler'a geç
  } catch (err) {
    res.status(401).json({ message: 'Geçersiz token.' }); // Token geçersizse
  }
};

module.exports = authMiddleware;

Bu middleware'i app.js dosyanıza dahil edin: const auth = require('./middleware/auth');

Korunan Endpoint'ler

Artık herhangi bir endpoint'i bu middleware ile koruyabiliriz. Örneğin, /api/items rotalarımızın sadece giriş yapmış kullanıcılar tarafından erişilebilir olmasını istiyorsak:


// ... Diğer tüm kodların üstünde auth middleware'ını dahil edin ...
const auth = require('./middleware/auth');

// Korunan bir rota örneği: Sadece giriş yapmış kullanıcılar kendi öğelerini görebilir
app.get('/api/protected-items', auth, async (req, res) => {
    try {
        // req.userId sayesinde, bu kullanıcının ID'sine erişebiliriz
        // const userItems = await Item.find({ userId: req.userId }); // Eğer Item modelinde userId alanı olsaydı
        res.json({ message: Hoş geldiniz, kullanıcı ID'niz: ${req.userId}. Burası korunan bir alan. });
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
});

// Mevcut GET /api/items rotasını korumak için
app.get('/api/items', auth, async (req, res) => {
    try {
        const items = await Item.find(); 
        res.json(items);
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
});
// Aynı şekilde POST, PUT, DELETE rotalarını da koruyabilirsiniz:
app.post('/api/items', auth, async (req, res) => { /* ... */ });
app.put('/api/items/:id', auth, async (req, res) => { /* ... */ });
app.delete('/api/items/:id', auth, async (req, res) => { /* ... */ });

// ... app.listen kısmı ...

Bu şekilde, API'mize yetkisiz erişimi engelleyebilir ve sadece kimliği doğrulanmış kullanıcıların belirli kaynaklara erişmesine izin verebiliriz. JWT_SECRET gibi hassas bilgileri doğrudan kod içine yazmak yerine ortam değişkenleri olarak tutmak önemlidir, bu konuya "Dağıtım ve Ölçeklendirme" bölümünde değineceğiz. Bu yapı, hem güvenlik hem de ölçeklenebilirlik açısından modern bir API'nin temelini oluşturur. Örneğin, bir blog platformunda, sadece giriş yapmış kullanıcılar yeni bir makale yayınlayabilir veya mevcut makaleleri güncelleyebilirken, herkes makaleleri okuyabilir. Bu ayrım, JWT ve middleware'ler aracılığıyla kolayca sağlanır.

Hata Yönetimi ve Giriş Doğrulama: Sağlam Bir API İçin Olmazsa Olmazlar

Bir API geliştirirken, beklenmedik durumları (örneğin, geçersiz kullanıcı girişi, veritabanı bağlantı sorunları, bulunamayan kaynaklar) doğru bir şekilde ele almak hayati önem taşır. Etkili hata yönetimi ve giriş doğrulama, hem API'nizin kullanıcı dostu olmasını sağlar hem de geliştiricilerin hataları daha kolay tespit etmesine yardımcı olur. Yanlış veya eksik veri girişi, API'nin istikrarsız çalışmasına veya güvenlik açıklarına yol açabilir. Bu nedenle, kullanıcıdan gelen her veriyi dikkatlice doğrulamak ve sistemin belirli bir şekilde yanıt vermesini sağlamak önemlidir.

Giriş Doğrulama (Input Validation)

Kullanıcıdan gelen verilerin (req.body, req.params, req.query) beklediğimiz formatta ve içerikte olup olmadığını kontrol etmek, API'mizi güvende tutmanın ilk adımıdır. Express ekosisteminde bunun için kullanabileceğimiz popüler kütüphanelerden biri express-validator'dır. Bu kütüphane, verileri doğrulamak ve temizlemek için esnek ve güçlü bir araç seti sunar.


npm install express-validator

Şimdi, /api/auth/register endpoint'imizi express-validator ile güçlendirelim. app.js dosyanızdaki ilgili yere aşağıdaki gibi eklemeler yapabilirsiniz:


// app.js dosyanızın başında veya ayrı bir validation middleware dosyasında
const { body, validationResult } = require('express-validator');

// ...

// Yeni kullanıcı kaydı (giriş doğrulama ile)
app.post('/api/auth/register', [
  body('username').trim().isLength({ min: 3 }).withMessage('Kullanıcı adı en az 3 karakter olmalıdır.'),
  body('email').isEmail().withMessage('Geçerli bir e-posta adresi girin.'),
  body('password').isLength({ min: 6 }).withMessage('Şifre en az 6 karakter olmalıdır.')
], async (req, res) => {
  // Doğrulama hatalarını kontrol et
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() }); // Hataları JSON olarak döndür
  }

  try {
    const { username, email, password } = req.body;
    let user = await User.findOne({ $or: [{ username }, { email }] });
    if (user) {
      return res.status(400).json({ message: 'Bu kullanıcı adı veya e-posta zaten kullanımda.' });
    }

    user = new User({ username, email, password });
    await user.save(); 

    const token = jwt.sign({ userId: user._id }, JWT_SECRET, { expiresIn: '1h' });
    res.status(201).json({ message: 'Kullanıcı başarıyla kaydedildi.', token });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// ...

Bu örnekte, body() fonksiyonları ile her bir alan için doğrulama kuralları tanımladık (trim, isLength, isEmail). Eğer doğrulama başarısız olursa, validationResult(req) ile hataları yakalayıp istemciye 400 Bad Request durumuyla birlikte döneriz. Bu, istemcinin hangi alanda hata yaptığını anlamasına yardımcı olur ve gereksiz veritabanı işlemlerini önler.

Uzman İpucu: Karmaşık doğrulama kuralları için özel bir middleware dosyası oluşturarak kodunuzu daha okunabilir hale getirebilirsiniz. Örneğin, middleware/validation.js içinde tüm doğrulama mantığını toplayıp router'larınızda kullanabilirsiniz.

Etkili Hata Yönetimi (Error Handling)

Express'te hata yönetimi için özel bir tür middleware kullanılır: dört argüman alan bir fonksiyon ((err, req, res, next) => { ... }). Bu middleware'ler, bir route handler'ı veya başka bir middleware içinde bir hata oluştuğunda tetiklenir ve hatayı merkezi bir noktadan yakalayıp işleyebiliriz. Böylece, her route içinde tekrar tekrar try-catch blokları yazmaktan kurtuluruz ve tutarlı hata yanıtları sağlayabiliriz.

app.js dosyanızın en altına, app.listen() çağrısından hemen önce aşağıdaki hata yakalama middleware'ini ekleyin:


// app.js dosyasının en altında, tüm rotalardan sonra ekleyin

// Hata yakalama middleware'i
app.use((err, req, res, next) => {
  console.error(err.stack); // Hata detaylarını sunucu konsolunda logla

  // Özel hata sınıfları veya belirli hata türleri için farklı yanıtlar verebiliriz
  if (err.name === 'ValidationError') { // Mongoose doğrulama hataları için
    return res.status(400).json({ 
      message: 'Doğrulama hatası', 
      errors: Object.values(err.errors).map(e => e.message) 
    });
  }
  if (err.name === 'CastError' && err.kind === 'ObjectId') { // Geçersiz MongoDB ID'leri için
    return res.status(400).json({ message: 'Geçersiz ID formatı.' });
  }
  
  // Varsayılan hata yanıtı
  res.status(500).json({ message: 'Sunucu hatası oluştu.' });
});

// Bilinmeyen rotaları ele almak için (404 Not Found)
app.use((req, res, next) => {
  res.status(404).json({ message: 'Aradığınız rota bulunamadı.' });
});

// ... app.listen kısmı ...

Bu hata yakalama middleware'i şunları yapar:

  • console.error(err.stack);: Sunucu tarafında hatanın tam izini (stack trace) loglar, bu da debugging için çok önemlidir. Bu logları üretimde bir loglama servisine (örneğin, Sentry, ELK Stack) göndermek isteyebilirsiniz.
  • Mongoose'un otomatik olarak fırlattığı ValidationError veya CastError gibi spesifik hataları tanıyarak, istemciye daha anlamlı yanıtlar döndürür. Örneğin, bir kullanıcının geçersiz bir ID ile bir öğeyi aramasını engeller ve daha açıklayıcı bir mesaj sunar.
  • Diğer tüm beklenmeyen hatalar için genel bir 500 Internal Server Error yanıtı döndürür. Bu, hassas sunucu bilgilerinin istemciye sızmasını engeller.
  • En son app.use((req, res, next) => { ... }); bloğu, Express'in hiçbir önceki rotayı eşleştiremediği durumlarda devreye girer ve 404 Not Found yanıtı döndürür. Bu, API'nizin bilinmeyen URL'lere karşı da tutarlı yanıtlar vermesini sağlar.

Bu iki bileşeni (giriş doğrulama ve merkezi hata yönetimi) API'nize entegre ederek, hem geliştirme sürecinde hataları daha kolay yakalar hem de son kullanıcıya daha güvenilir ve tahmin edilebilir bir deneyim sunarsınız. Örneğin, bir bankacılık uygulamasında hatalı bir para transferi isteği geldiğinde, API'nin hem istemciyi doğru hata kodu ve mesajıyla uyarması hem de sunucu tarafında bu hatayı güvenli bir şekilde kaydetmesi hayati öneme sahiptir.

API Belgelendirme ve Test Etme: Geliştirici Deneyimini Nasıl İyileştiririz?

Bir REST API'yi başarılı bir şekilde oluşturmak kadar, onu başkaları (veya gelecekteki siz) tarafından anlaşılır, kullanılabilir ve bakımı yapılabilir kılmak da önemlidir. Bu noktada API belgelendirme ve test etme devreye girer. İyi belgelenmiş bir API, geliştiricilerin onu kolayca entegre etmesini sağlarken, kapsamlı testler API'nin doğru ve hatasız çalıştığını garanti eder.

API Belgelendirme: Swagger/OpenAPI ile Kolaylaştırma

API belgelendirmesi, endpoint'lerin ne iş yaptığını, hangi parametreleri beklediğini, hangi yanıtları döndürebileceğini ve hangi HTTP durum kodlarını kullanabileceğini açıklayan bir kılavuzdur. Manuel olarak belge yazmak zahmetli ve güncel tutması zorken, Swagger (şimdiki adıyla OpenAPI Specification) bu süreci otomatikleştiren popüler bir çözümdür. OpenAPI, API'nizin yapısını standart, dilden bağımsız bir formatta tanımlamanızı sağlar ve Swagger UI gibi araçlar bu tanımı etkileşimli, web tabanlı bir belgeye dönüştürür.

Express projenizde Swagger'ı kullanmak için swagger-ui-express ve swagger-jsdoc gibi kütüphaneleri kullanabiliriz:


npm install swagger-ui-express swagger-jsdoc

app.js dosyanızda veya ayrı bir swagger.js dosyasında Swagger yapılandırmasını yapalım:


// app.js içinde veya ayrı bir swaggerConfig.js dosyasında
const swaggerUi = require('swagger-ui-express');
const swaggerJsdoc = require('swagger-jsdoc');

// Swagger/OpenAPI tanımlaması
const swaggerOptions = {
  definition: {
    openapi: '3.0.0', // OpenAPI sürümü
    info: {
      title: 'Node.js Express API Dokümantasyonu',
      version: '1.0.0',
      description: 'Sıfırdan Üretime Node.js ve Express API rehberi için örnek API dokümantasyonu.',
      contact: {
        name: 'Geliştirici Ekibi',
        email: 'developer@example.com'
      }
    },
    servers: [
      {
        url: 'http://localhost:3000', // API'nizin çalıştığı temel URL
        description: 'Yerel Geliştirme Sunucusu'
      }
    ],
    components: {
        securitySchemes: {
            bearerAuth: { // JWT için bir güvenlik şeması tanımlıyoruz
                type: 'http',
                scheme: 'bearer',
                bearerFormat: 'JWT'
            }
        }
    },
    security: [{
        bearerAuth: [] // Varsayılan olarak tüm rotaların JWT ile korunacağını belirtiriz
    }]
  },
  // API belgelerini oluşturmak için hangi dosyaları tarayacağını belirtiyoruz
  apis: ['./app.js', './routes/*.js'], // app.js ve routes klasöründeki tüm JS dosyaları
};

const swaggerSpec = swaggerJsdoc(swaggerOptions);

// Swagger UI'ı /api-docs adresinde aktif ediyoruz
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

// Rotanızın başına Swagger yorumlarını ekleyebilirsiniz. Örneğin, /api/items POST için:
/**
 * @swagger
 * /api/items:
 *   post:
 *     summary: Yeni bir öğe oluşturur
 *     tags: [Items]
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - name
 *             properties:
 *               name:
 *                 type: string
 *                 description: Öğenin adı
 *               description:
 *                 type: string
 *                 description: Öğenin açıklaması
 *     responses:
 *       201:
 *         description: Öğe başarıyla oluşturuldu
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 _id:
 *                   type: string
 *                 name:
 *                   type: string
 *                 description:
 *                   type: string
 *       400:
 *         description: Geçersiz istek
 */
// Yukarıdaki yorum bloğunu app.js'deki app.post('/api/items', ...) rotanızın hemen üstüne ekleyin.

Sunucuyu çalıştırdığınızda (npm run dev), tarayıcınızdan http://localhost:3000/api-docs adresine giderek etkileşimli API belgelerinizi görebilirsiniz. Bu, API'nizi kullanan diğer geliştiriciler için paha biçilmez bir kaynaktır.

API Test Etme: Postman ve Otomatik Testler (Jest, Supertest)

API'nizi geliştirdikçe ve yeni özellikler ekledikçe, her şeyin beklendiği gibi çalıştığından emin olmak için testler yapmanız gerekir. İki ana test türü vardır:

1. Manuel Testler (Postman, Insomnia)

Geliştirme sırasında endpoint'leri hızlıca test etmek için Postman veya Insomnia gibi araçlar kullanışlıdır. Bu araçlar, HTTP istekleri oluşturmanıza, farklı HTTP metodlarını (GET, POST, PUT, DELETE) kullanmanıza, istek gövdesine veri eklemenize, başlıkları ayarlamanıza ve yanıtları incelemenize olanak tanır. Özellikle yeni bir endpoint yazarken veya bir hatayı ayıklarken bu araçlar oldukça zaman kazandırır.

Bir Postman örneği için, POST /api/auth/register isteği atarken, "Body" sekmesinden "raw" ve "JSON" seçeneğini işaretleyerek aşağıdaki gibi bir JSON gönderebilirsiniz:


{
  "username": "testuser123",
  "email": "test@example.com",
  "password": "password123"
}

Gönderdiğinizde 201 Created yanıtı ve bir JWT almalısınız. Bu JWT'yi kopyalayıp diğer korunan endpoint'lerde (örneğin GET /api/items) "Authorization" başlığı altında "Bearer TOKEN_STRING" formatında kullanabilirsiniz.

2. Otomatik Testler (Jest ve Supertest)

Manuel testler hızlı olsa da, her değişiklikte tüm API'yi elle test etmek sürdürülebilir değildir. Bu yüzden otomatik testler çok önemlidir. Jest, bir JavaScript test çatısıdır ve Supertest ise HTTP isteklerini kolayca test etmenizi sağlayan bir kütüphanedir. Bu ikilinin kombinasyonu, API endpoint'lerinizin doğru yanıtlar döndürdüğünden ve beklenmedik durumları doğru bir şekilde ele aldığından emin olmanızı sağlar.


npm install --save-dev jest supertest

package.json dosyanıza bir test komutu ekleyin:


{
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js",
    "test": "jest" // Yeni test komutu
  },
  // ...
}

Testlerinizi yazmaya başlamak için projenizin kök dizininde bir __tests__ klasörü oluşturun (veya .test.js uzantılı dosyalar kullanın) ve içine items.test.js gibi bir dosya oluşturun:


// __tests__/items.test.js
const request = require('supertest');
const app = require('../app'); // app.js dosyanızı buraya import ettiğinizden emin olun
const mongoose = require('mongoose');
const Item = require('../models/Item'); // Item modelinizi import edin

// Testler başlamadan önce veritabanı bağlantısını kur ve test verilerini temizle
beforeAll(async () => {
  const dbURI = 'mongodb://localhost:27017/test_db'; // Test için ayrı bir veritabanı kullanın
  await mongoose.connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true });
});

// Her testten sonra veritabanını temizle
afterEach(async () => {
  await Item.deleteMany({});
});

// Tüm testler bittikten sonra veritabanı bağlantısını kapat
afterAll(async () => {
  await mongoose.connection.close();
});

describe('Item API', () => {
  it('GET /api/items - Tüm öğeleri getirmeli', async () => {
    // Örnek olarak birkaç öğe ekleyelim
    await Item.create({ name: 'Test Item 1', description: 'Description 1' });
    await Item.create({ name: 'Test Item 2', description: 'Description 2' });

    const res = await request(app).get('/api/items');
    expect(res.statusCode).toEqual(200);
    expect(res.body).toHaveLength(2);
    expect(res.body[0]).toHaveProperty('name', 'Test Item 1');
  });

  it('POST /api/items - Yeni bir öğe oluşturmalı', async () => {
    const newItem = { name: 'Yeni Öğe', description: 'Yeni öğe açıklaması' };
    const res = await request(app)
      .post('/api/items')
      .send(newItem);

    expect(res.statusCode).toEqual(201);
    expect(res.body).toHaveProperty('_id');
    expect(res.body).toHaveProperty('name', 'Yeni Öğe');

    const itemInDb = await Item.findById(res.body._id);
    expect(itemInDb).not.toBeNull();
    expect(itemInDb.name).toEqual('Yeni Öğe');
  });

  // Diğer CRUD operasyonları için de testler yazılabilir (GET by ID, PUT, DELETE)
});

app.js dosyanızda module.exports = app; satırını ekleyerek Express uygulamanızı test dosyalarınızdan import edilebilir hale getirmelisiniz. Artık terminalde npm test komutunu çalıştırarak testlerinizi otomatik olarak çalıştırabilirsiniz. Bu testler, API'nizin beklenen davranışları sergilemesini ve potansiyel hataları erken aşamada yakalamanızı sağlar. Örneğin, bir rezervasyon sistemi API'sinde, yeni bir rezervasyon oluşturma endpoint'inin, mevcut bir çakışma durumunda doğru hata koduyla yanıt verdiğini ve veritabanına yanlış bilgi kaydetmediğini bu testlerle kolayca doğrulayabilirsiniz.

Dağıtım ve Ölçeklendirme: API'mizi Üretim Ortamına Nasıl Taşırız?

API'nizi geliştirip test ettikten sonra, onu dünya ile paylaşma zamanı gelir. Bir geliştirme ortamında sorunsuz çalışan bir API'nin, üretim ortamında da aynı performansı ve güvenilirliği sergilemesi için doğru dağıtım ve ölçeklendirme stratejileri uygulamak şarttır. Bu bölüm, API'nizi üretim ortamına taşımak için bazı temel adımları ve en iyi uygulamaları ele almaktadır.

1. Ortam Değişkenleri ile Güvenli Yapılandırma (dotenv)

Hassas bilgiler (veritabanı bağlantı dizeleri, JWT gizli anahtarları, API anahtarları vb.) doğrudan kod içine yazılmamalıdır. Bunun yerine, bu bilgileri ortam değişkenleri olarak saklamalı ve uygulamanızın çalıştığı ortama göre dinamik olarak yüklemelisiniz. dotenv kütüphanesi, bu işlemi Node.js uygulamaları için basitleştirir.


npm install dotenv

Projenizin kök dizininde .env adında bir dosya oluşturun ve hassas bilgilerinizi buraya yazın:


PORT=3000
MONGODB_URI=mongodb://localhost:27017/myapi_db_prod
JWT_SECRET=cok_daha_guclu_ve_rastgele_bir_anahtar_buraya

Ardından, app.js dosyanızın en başına (diğer require çağrılarından önce) dotenv'i yükleyin:


require('dotenv').config(); // En üste ekleyin

const express = require('express');
// ...

const port = process.env.PORT || 3000;
const dbURI = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapi_db_dev';
const JWT_SECRET = process.env.JWT_SECRET || 'cokgizli_bir_anahtar'; 

mongoose.connect(dbURI, { /* ... */ });
// ...

Böylece, farklı ortamlarda (geliştirme, test, üretim) farklı yapılandırmalar kullanabilir ve hassas bilgilerinizi versiyon kontrol sistemine (Git) dahil etmezsiniz. .env dosyasını .gitignore dosyanıza eklemeyi unutmayın!

2. Performans İyileştirmeleri ve Güvenlik Güçlendirmeleri

  • Sıkıştırma (Compression): API yanıtlarının boyutunu küçültmek ve ağ trafiğini azaltmak için sıkıştırma middleware'i kullanın. compression paketi bunun için idealdir:
  • 
    npm install compression
        


    const compression = require('compression');
    app.use(compression()); // Tüm yanıtları sıkıştır

  • Güvenlik Başlıkları (Helmet): HTTP başlıklarını ayarlayarak API'nizi yaygın web zayıflıklarına karşı koruyun. helmet kütüphanesi bunun için bir dizi güvenlik middleware'i sağlar:
  • 
    npm install helmet
        


    const helmet = require('helmet');
    app.use(helmet()); // Çeşitli güvenlik başlıklarını ayarlar

  • CORS (Cross-Origin Resource Sharing): Farklı bir alandan (örneğin, frontend.com) gelen isteklerin API'nize erişmesine izin vermek için CORS'u yapılandırmanız gerekebilir.
  • 
    npm install cors
        


    const cors = require('cors');
    app.use(cors()); // Tüm kaynaklardan gelen isteklere izin verir (üretimde daha spesifik olmalı)
    // app.use(cors({ origin: 'https://www.guvenli-alan.com' })); // Sadece belirli bir alana izin ver

3. Süreç Yönetimi (PM2)

Node.js uygulamaları tek iş parçacıklı olduğundan, bir hata oluştuğunda uygulama tamamen çökebilir. Ayrıca, sunucunuzun tüm CPU çekirdeklerinden faydalanmak ve uygulamayı sürekli çalışır durumda tutmak için bir süreç yöneticisine (process manager) ihtiyacınız vardır. PM2, Node.js uygulamaları için popüler bir süreç yöneticisidir:


npm install -g pm2 # Global olarak kur

Uygulamanızı başlatmak için:


pm2 start app.js -i max # Tüm CPU çekirdeklerini kullanacak şekilde başlat

-i max bayrağı, PM2'nin CPU çekirdeği sayınız kadar uygulamanızın kopyalarını (cluster) çalıştırmasını sağlar, böylece uygulamanız daha fazla isteği paralel olarak işleyebilir ve bir örnek çökerse diğerleri çalışmaya devam eder.

4. Bulut Platformları ile Dağıtım (Deployment)

API'nizi üretim ortamına taşımak için çeşitli bulut platformları kullanabilirsiniz:

  • PaaS (Platform as a Service) Çözümleri (Heroku, Vercel, Render): Bu platformlar, uygulamanızı dağıtımı için altyapıyı yönetir. Git deponuza bağlar, otomatik dağıtım yapar ve genellikle ücretsiz katmanlar sunar. Hızlı başlangıçlar ve daha az altyapı yönetimi isteyen ekipler için idealdir.
  • IaaS (Infrastructure as a Service) Çözümleri (AWS EC2, Google Cloud Compute Engine, Azure Virtual Machines): Sanal sunucuları doğrudan kontrol edersiniz. Daha fazla esneklik ve kontrol sağlar ancak altyapı yönetimi (OS güncellemeleri, güvenlik yamaları vb.) tamamen size aittir. PM2 gibi araçları bu sunuculara kurarak uygulamanızı çalıştırabilirsiniz.
  • Konteynerizasyon (Docker): Uygulamanızı ve tüm bağımlılıklarını izole bir konteyner içinde paketlemek, "benim makinemde çalışıyor" sorununu ortadan kaldırır. Docker ile uygulamanız her ortamda tutarlı bir şekilde çalışır. AWS ECS, Google Kubernetes Engine gibi platformlar Docker konteynerlerini ölçekli olarak yönetmek için kullanılır.

Örneğin, bir Heroku dağıtımı için sadece projenizi bir Git deposuna göndermeniz ve Heroku CLI üzerinden git push heroku master komutunu çalıştırmanız yeterli olabilir (Heroku'nun Node.js buildpack'i PM2 veya Procfile'daki komutunuzu otomatik olarak çalıştıracaktır).

5. Mobil Uyumluluk ve API Kullanımı

API'niz herhangi bir istemci (web sitesi, mobil uygulama, masaüstü uygulama) tarafından tüketilebilir. Bu bağlamda, API'nizin kendisi mobil uyumlu olmazken, API'yi tüketen web sitelerinin mobil uyumlu olması önemlidir. Bir web sitesi, API'nizden aldığı veriyi gösterirken, küçük ekranlara ve dokunmatik arayüzlere uygun bir şekilde kendini düzenlemelidir. Bu genellikle CSS medya sorguları ve duyarlı tasarım teknikleri kullanılarak yapılır. Örneğin:





    
    
    Mobil Uyumlu API Tüketicisi
    


    

API Verileri

Öğe 1

Bu bir API öğesidir.

Öğe 2

Bu da başka bir API öğesidir.

Bu HTML yapısı, temel bir meta viewport etiketi ve bir medya sorgusu içerir. Bu sayede, API'nizden gelen veriler, farklı ekran boyutlarına sahip cihazlarda okunabilir ve kullanılabilir bir şekilde gösterilebilir. API'nizin kendisi sadece veriyi sunar, bu verinin istemcide nasıl düzenleneceği istemcinin sorumluluğundadır. Dağıtım ve ölçeklendirme, API'nizin sadece çalışmasını değil, aynı zamanda güvenilir, performanslı ve sürdürülebilir olmasını sağlamak için kritik adımlardır. Örneğin, Black Friday gibi yoğun dönemlerde bir e-ticaret API'sinin trafiği sorunsuz bir şekilde kaldırabilmesi için bu adımlar olmazsa olmazdır.

Sonuç: REST API Yolculuğunuzda Bir Sonraki Adımlarınız Neler Olmalı?

Bu kapsamlı rehber boyunca, Node.js ve Express kullanarak sıfırdan üretime hazır bir REST API oluşturmanın temel adımlarını derinlemesine inceledik. İlk "Hello World" endpoint'inden başlayarak, MongoDB ile veritabanı entegrasyonu, JWT ile güvenlik ve kimlik doğrulama, sağlam hata yönetimi ve giriş doğrulama teknikleri, API belgelendirme ile geliştirici deneyimini iyileştirme ve son olarak API'yi üretim ortamına dağıtma ve ölçeklendirme stratejilerini ele aldık. Artık kendi REST API'lerinizi geliştirmek ve modern web uygulamalarının arka yüzünü oluşturmak için sağlam bir bilgi birikimine ve pratik yeteneklere sahipsiniz.

Ancak, teknoloji dünyası sürekli gelişiyor ve öğrenme yolculuğunuz burada bitmiyor. Bir sonraki adımlarınızda keşfedebileceğiniz bazı ileri düzey konular ve teknolojiler şunlar olabilir:

  • GraphQL: REST API'lere bir alternatif olarak ortaya çıkan GraphQL, istemcilerin tam olarak ihtiyaç duydukları veriyi tek bir istekte almalarını sağlar, bu da aşırı veya eksik veri çekme sorunlarını çözer.
  • Mikroservis Mimarileri: Büyük uygulamaları daha küçük, bağımsız ve birbirleriyle iletişim kuran servisler halinde bölmek, ölçeklenebilirliği ve bakım kolaylığını artırır. Node.js mikroservisler için popüler bir seçimdir.
  • WebSockets: Gerçek zamanlı uygulamalar (chat uygulamaları, canlı bildirimler) geliştirmek için sunucu ile istemci arasında kalıcı, çift yönlü bir iletişim kanalı sağlar.
  • Caching (Önbellekleme): Redis veya Memcached gibi in-memory veritabanları kullanarak sık erişilen verileri önbelleğe almak, API performansını önemli ölçüde artırabilir.
  • API Gateway'ler: Mikroservis mimarilerinde, istemcilerin birden fazla servise doğrudan erişmesini engellemek ve merkezi kimlik doğrulama, yük dengeleme gibi işlevleri sağlamak için kullanılır.
  • Sunucusuz (Serverless) Fonksiyonlar: AWS Lambda, Google Cloud Functions gibi platformlar, API endpoint'lerinizi sunucu yönetimi derdi olmadan çalıştırmanıza olanak tanır ve maliyeti kullanım başına düşürür.

Bu konuları keşfederek, Node.js ve Express ile geliştirme yeteneklerinizi bir üst seviyeye taşıyabilir ve daha karmaşık, ölçeklenebilir ve dayanıklı sistemler inşa edebilirsiniz. Unutmayın, en iyi öğrenme yöntemi pratik yapmaktır. Bu rehberde öğrendiklerinizi kullanarak kendi projelerinizi geliştirmeye devam edin ve yeni teknolojileri deneyimlemekten çekinmeyin. Başarılar dileriz!

Sıkça Sorulan Sorular (SSS)

1. Node.js ve Express kullanmak yerine neden başka bir dil veya framework seçmeyeyim?

Cevap: Node.js ve Express, JavaScript'i hem ön uçta hem de arka uçta kullanabilme avantajı sunar, bu da geliştirme hızını artırır ve ekip içinde dil tutarlılığı sağlar. Node.js'in asenkron, engellemeyen G/Ç modeli, yüksek performanslı ve ölçeklenebilir API'ler için oldukça etkilidir. Ayrıca, geniş ve aktif bir topluluğa ve zengin bir npm paket ekosistemine sahiptir. Ancak, Ruby on Rails, Django (Python), Spring Boot (Java) gibi başka diller ve framework'ler de kendi avantajlarına sahiptir ve projenizin özel gereksinimlerine göre en uygun olanı seçmelisiniz.

2. MongoDB yerine başka bir veritabanı kullanabilir miyim?

Cevap: Kesinlikle! MongoDB, esnek yapısı nedeniyle Node.js ile popüler bir seçim olsa da, projenizin ihtiyaçlarına göre PostgreSQL, MySQL (ilişkisel veritabanları), Redis (bellek içi önbellek/veritabanı) veya Cassandra (geniş sütunlu NoSQL) gibi farklı veritabanlarını tercih edebilirsiniz. Mongoose yerine bu veritabanları için uygun ORM (Object-Relational Mapper) veya ODM (Object-Document Mapper) kütüphanelerini kullanmanız gerekecektir (örneğin, PostgreSQL için Sequelize veya Prisma).

3. API güvenliğini artırmak için başka neler yapabilirim?

Cevap: Bu rehberde JWT ile temel kimlik doğrulamasını ele aldık. Güvenliği daha da artırmak için HTTPS kullanmak (SSL/TLS sertifikaları ile), rate limiting (API'ye yapılan istek sayısını sınırlama), CSRF (Cross-Site Request Forgery) ve XSS (Cross-Site Scripting) saldırılarına karşı önlemler almak, güvenlik açıklarını tarayan araçlar kullanmak ve düzenli güvenlik denetimleri yapmak gibi ek adımlar atabilirsiniz. Ayrıca, hassas verileri şifreleyerek depolamak ve güvenlik yamalarını düzenli olarak uygulamak da önemlidir.

4. API'mi mobil uygulamalarla nasıl entegre ederim?

Cevap: Mobil uygulamalar, API'nizle genellikle HTTP istekleri (GET, POST vb.) yaparak iletişim kurar. Android için Retrofit (Java/Kotlin), iOS için Alamofire (Swift) veya platformlar arası (cross-platform) geliştirme için React Native'de Fetch API/Axios gibi kütüphaneler kullanılır. Mobil uygulamaların API'nizle güvenli bir şekilde iletişim kurabilmesi için HTTPS ve JWT gibi kimlik doğrulama mekanizmalarını doğru bir şekilde uyguladığınızdan emin olun.

5. API'mde ne zaman önbellekleme kullanmalıyım?

Cevap: Önbellekleme, API'nizdeki sık erişilen ancak nadiren değişen veriler için mükemmel bir performans iyileştirme yöntemidir. Örneğin, bir e-ticaret sitesindeki ürün kategorileri, popüler makaleler veya hava durumu verileri gibi statik veya yarı statik veriler önbelleğe alınabilir. Veritabanından her seferinde aynı veriyi çekmek yerine, önbellekten sunarak yanıt sürelerini önemli ölçüde kısaltabilirsiniz. Redis veya Memcached gibi araçlar, Node.js ile kolayca entegre edilebilir.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.