REST API’ler, modern uygulamalarda frontend ile backend arasındaki veri alışverişini düzenlemek için kullanılır. Klasik web uygulamalarında sunucu genellikle HTML sayfaları üretir ve tarayıcıya hazır bir kullanıcı arayüzü gönderir. Ancak her uygulama HTML sayfasına ihtiyaç duymaz.
Örneğin mobil uygulamaları düşünelim. Bir iOS veya Android uygulaması arayüzünü sunucudan gelen HTML ile oluşturmaz. Bunun yerine Swift, Kotlin, Java veya benzeri teknolojilerle kendi arayüzünü uygulama içinde oluşturur. Bu uygulamanın sunucudan istediği şey HTML değil, veridir. Kullanıcı bilgileri, ürün listesi, siparişler, mesajlar veya ödeme sonucu gibi bilgiler backend’den alınır ve mobil uygulama bu verileri kendi arayüzünde gösterir.
Aynı durum tek sayfalı web uygulamaları için de geçerlidir. React, Angular veya Vue gibi teknolojilerle geliştirilen uygulamalarda sayfa çoğu zaman tamamen yenilenmez. Kullanıcı farklı bölümlere tıkladığında tarayıcıdaki JavaScript kodu backend’e istek gönderir, gerekli verileri alır ve ekrandaki ilgili alanı günceller. Böylece kullanıcı daha akıcı, mobil uygulamaya benzeyen bir deneyim yaşar.
İşte REST API’nin temel fikri burada ortaya çıkar: Sunucu artık hazır HTML sayfaları göndermek yerine, istemcinin ihtiyaç duyduğu verileri gönderir. Bu veri genellikle JSON formatında olur. Frontend, mobil uygulama veya başka bir servis bu veriyi alır ve kendi ihtiyacına göre işler.
REST, “Representational State Transfer” ifadesinin kısaltmasıdır. Basitçe söylemek gerekirse REST API, kullanıcı arayüzü yerine veri aktarımı yapmamızı sağlayan bir backend yapısıdır.
Bu noktada önemli bir ayrımı görmek gerekir. REST API kullanıyor olmamız, backend tarafındaki tüm mantığın değiştiği anlamına gelmez. Doğrulama işlemleri, veritabanına erişim, dosya işlemleri, kimlik doğrulama ve iş kuralları yine sunucu tarafında yapılır. Değişen temel şey, sunucunun verdiği cevaptır. Klasik uygulamada sunucu HTML dönerken, REST API’de veri döner.
Örneğin klasik bir web uygulamasında sunucu şöyle bir cevap verebilir:
Ürünler sayfasının hazır HTML çıktısı
REST API’de ise sunucu genellikle şöyle bir veri döner:
{
"products": [
{
"id": "p1",
"title": "Telefon",
"price": 12000
},
{
"id": "p2",
"title": "Kulaklık",
"price": 1500
}
]
}Bu verinin nasıl gösterileceğine frontend karar verir. Mobil uygulama bunu kendi tasarımına göre gösterebilir, React uygulaması ürün kartlarına dönüştürebilir veya başka bir servis bu veriyi farklı bir işlem için kullanabilir.
Özetle REST API’ler, backend’i kullanıcı arayüzünden ayırır. Backend veri sağlar, frontend ise bu veriyi kullanıcıya uygun şekilde sunar. Bu yapı özellikle mobil uygulamalar, tek sayfalı web uygulamaları ve farklı servislerin birbirleriyle haberleştiği sistemlerde çok yaygın olarak kullanılır.
REST API Nasıl Çalışır?
REST API’de temel yapı yine istemci ve sunucu arasındaki iletişime dayanır. İstemci bir mobil uygulama, React/Vue/Angular ile geliştirilmiş tek sayfalı bir web uygulaması veya başka bir servis olabilir. Sunucu tarafında ise bu istemcilerin ihtiyaç duyduğu verileri sağlayan API bulunur.
REST API kullanmanın önemli avantajlarından biri, aynı backend’in birden fazla istemci tarafından kullanılabilmesidir. Örneğin aynı şirketin hem web uygulaması hem de mobil uygulaması olabilir. Bu iki uygulama kullanıcı arayüzünü farklı şekillerde gösterse de çoğu zaman aynı verilere ihtiyaç duyar:
Web uygulaması -> aynı API -> ürün verileri
Mobil uygulama -> aynı API -> ürün verileri
Backend burada hazır HTML sayfası üretmez. Bunun yerine ürünler, kullanıcılar, siparişler veya ödeme sonuçları gibi verileri gönderir. Bu verinin ekranda nasıl gösterileceğine frontend karar verir.
Bu noktada önemli bir soru ortaya çıkar: Sunucu ve istemci bu verileri hangi formatta paylaşacak?
Klasik web uygulamalarında sunucu genellikle HTML döner. HTML hem veriyi hem de kullanıcı arayüzünün yapısını içerir. Örneğin bir ürün adı HTML etiketlerinin içinde yer alır ve aynı zamanda sayfanın nasıl görüneceği de bu yapının parçasıdır. Ancak REST API’de amacımız arayüz göndermek değil, sadece veri göndermektir. Bu yüzden HTML, API iletişimi için ideal değildir.
Düz metin göndermek de mümkündür, fakat bu da iyi bir çözüm değildir. İnsanlar düz metni okuyabilir ama makineler için bu metni anlamlandırmak zordur. Verinin nerede başladığı, nerede bittiği, hangi alanın ne anlama geldiği açık bir yapıya bağlı değildir.
XML de veri taşımak için kullanılabilir. HTML’e benzeyen etiketli bir yapısı vardır ve düz metne göre daha düzenlidir. Ancak XML daha uzun, daha fazla ek metin içeren ve ayrıştırması daha zahmetli bir formattır.
Bu yüzden modern REST API’lerde en yaygın kullanılan format JSON’dır.
JSON, sadece veri taşımak için kullanılan sade ve okunabilir bir formattır. Kullanıcı arayüzüyle ilgili herhangi bir varsayım içermez. Hem insanlar tarafından anlaşılması kolaydır hem de makineler tarafından kolayca işlenebilir.
Örneğin bir REST API ürünleri şu şekilde dönebilir:
{
"products": [
{
"id": "p1",
"title": "Telefon",
"price": 12000
},
{
"id": "p2",
"title": "Kulaklık",
"price": 1500
}
]
}Bu cevapta HTML yoktur, CSS yoktur, sayfa tasarımı yoktur. Sadece veri vardır. Mobil uygulama bu veriyi liste olarak gösterebilir, web uygulaması kart yapısına çevirebilir veya başka bir servis bu veriyi kendi işleminde kullanabilir.
JSON’un özellikle JavaScript dünyasında çok kullanışlı olmasının nedeni, JavaScript objelerine çok benzemesidir. Node.js ile backend geliştirirken de tarayıcı tarafında JavaScript çalıştırırken de JSON verisiyle çalışmak oldukça kolaydır.
Özetle REST API’lerde genellikle HTML değil, JSON gönderilir. Çünkü REST API’nin amacı kullanıcı arayüzü üretmek değil, farklı istemcilerin kullanabileceği temiz ve yapılandırılmış veri sağlamaktır.
REST API’de Routing ve Endpoint Mantığı
REST API’de client ile server arasındaki iletişim yine HTTP istekleri üzerinden gerçekleşir. Yani klasik web uygulamalarında olduğu gibi bir istek gönderilir, server bu isteği işler ve bir cevap döner.
Klasik uygulamalarda kullanıcı bir linke tıkladığında veya bir form gönderdiğinde server’a istek giderdi. REST API’de de mantık benzerdir; fakat artık amaç HTML sayfası almak değil, veri alışverişi yapmaktır.
Bir REST API isteği temelde iki parçadan oluşur:
HTTP method + path
GET /products
POST /orders
DELETE /products/p1
REST API dünyasında bu method ve path birleşimine genellikle endpoint denir. Yani bir endpoint, API üzerinde belirli bir iş yapan adrestir.
Server tarafında biz bu endpoint’leri tanımlarız. Bir istek bu endpoint’e ulaştığında hangi kodun çalışacağını belirleriz. Örneğin /products yoluna gelen GET isteği ürünleri veritabanından çekebilir, aynı yola gelen POST isteği ise yeni bir ürün oluşturabilir.
REST API’lerde en sık kullanılan HTTP methodları şunlardır:
GET -> veri almak için kullanılır
POST -> yeni veri oluşturmak için kullanılır
PUT -> bir kaynağı tamamen oluşturmak veya üzerine yazmak için kullanılır
PATCH -> mevcut bir kaynağın belirli alanlarını güncellemek için kullanılır
DELETE -> bir kaynağı silmek için kullanılır
OPTIONS -> tarayıcının izinleri kontrol etmek için otomatik gönderebildiği istektir
Örneğin ürünler için şöyle bir yapı kurulabilir:
GET /products
Tüm ürünleri getirir.
GET /products/p1
Belirli bir ürünü getirir.
POST /products
Yeni bir ürün oluşturur.
PUT /products/p1
Belirli bir ürünü tamamen değiştirir.
PATCH /products/p1
Belirli bir ürünün bazı alanlarını günceller.
DELETE /products/p1
Belirli bir ürünü siler.
Burada önemli olan nokta şudur: Teknik olarak server tarafında istediğimiz method-path kombinasyonunda istediğimiz kodu çalıştırabiliriz. Yani istersek GET /products isteğinde veri silebiliriz. Ancak bu REST mantığına uygun olmaz.
REST API’nin Temel İlkeleri
Bir API’nin HTTP yöntemlerini kullanması ve JSON verisi döndürmesi, tek başına REST API olması için yeterli değildir. REST yaklaşımı, API tasarlanırken izlenmesi gereken bazı temel ilkeler tanımlar.
Bu ilkelerin en önemlileri tek tip arayüz ve durumsuz etkileşim ilkeleridir.
Tek Tip Arayüz
REST API’nin endpoint’leri açık, tutarlı ve öngörülebilir olmalıdır. API’yi kullanan geliştiriciler her endpoint için şu bilgilere ulaşabilmelidir:
Kullanılacak HTTP yöntemi ve yol
Gönderilmesi gereken veriler
Döndürülecek cevabın yapısı
İstek başarılı veya başarısız olduğunda ne olacağı
Örneğin aşağıdaki endpoint’in yeni bir ürün oluşturacağı kolayca anlaşılabilir:
POST /products
Endpoint şu veriyi bekleyebilir:
{
"title": "Telefon",
"price": 12000
}Başarılı olduğunda ise oluşturulan ürünü döndürebilir:
{
"message": "Ürün oluşturuldu.",
"product": {
"id": "p1",
"title": "Telefon",
"price": 12000
}
}API’nin davranışları zaman içinde rastgele değişmemelidir. Özellikle herkese açık API’lerin iyi şekilde belgelenmesi gerekir.
Durumsuz Etkileşim
REST API’lerde client ve server birbirinden bağımsız çalışır. Server, önceki isteklerle ilgili bir bağlantı geçmişi veya oturum bilgisi saklamaz. Her istek, daha önce başka hiçbir istek gönderilmemiş gibi tek başına değerlendirilir.
Örneğin client önce giriş isteği gönderip ardından sipariş oluşturmak isterse server, kullanıcının önceki istekte giriş yaptığını otomatik olarak hatırlamaz. Sipariş isteği, kullanıcının kimliğini doğrulamak için ihtiyaç duyulan bilgileri kendi içinde taşımalıdır.
Client -> Gerekli kimlik doğrulama bilgilerini içeren istek -> Server
Bu yapı sayesinde aynı REST API; web uygulaması, mobil uygulama veya başka servisler tarafından bağımsız şekilde kullanılabilir.
Durumsuz yapı, kimlik doğrulama sürecini de etkiler. Kullanıcının giriş durumunu klasik bir oturumla takip etmek yerine, genellikle her istekle birlikte gönderilen bir kimlik doğrulama token’ı kullanılır. Bu konu kimlik doğrulama bölümünde daha ayrıntılı ele alınmıştır.
Diğer REST İlkeleri
REST yaklaşımında dikkate alınabilecek başka ilkeler de bulunur:
Önbelleğe alınabilirlik: Server, döndürdüğü cevabın client tarafından ne kadar süreyle önbellekte tutulabileceğini belirtebilir.
Client-server ayrımı: Client kullanıcı arayüzünden, server ise iş mantığı ve kalıcı veri depolamadan sorumludur.
Katmanlı sistem: Client’ın gönderdiği istek doğrudan asıl server tarafından işlenmeyebilir. İstek; proxy, yük dengeleyici veya farklı servisler üzerinden geçirilebilir.
İsteğe bağlı kod: Server gerektiğinde client’a çalıştırılabilir kod gönderebilir. Ancak bu özellik modern REST API’lerde çok sık kullanılmaz.
REST API geliştirirken özellikle şu iki noktayı akılda tutmak önemlidir:
1. API öngörülebilir ve tutarlı olmalıdır.
2. Her istek, önceki isteklerden bağımsız çalışmalıdır.
Bu ilkeler, API’nin farklı istemciler tarafından kolayca kullanılmasını ve sistem büyüdükçe daha rahat yönetilmesini sağlar.
Node.js ve Express ile İlk REST API Nasıl Oluşturulur?
Yukarıda REST API’lerin neden kullanıldığını, istemci ve sunucu arasında nasıl veri aktarıldığını, endpoint kavramını ve temel REST ilkelerini inceledik.
Şimdi teoriden pratiğe geçerek Node.js ve Express kullanacağımız ilk REST API projesini oluşturacağız. Hazırlayacağımız API, basit bir sosyal medya uygulamasındaki gönderileri yönetecek.
Projenin Oluşturulması
İlk olarak proje için yeni bir klasör oluşturalım ve terminal üzerinden bu klasöre geçelim:
mkdir first-rest-api
cd first-rest-api
Ardından yeni bir Node.js projesi oluşturalım:
npm init -y
Bu komut, proje bilgilerini ve bağımlılıklarını yöneteceğimiz package.json dosyasını oluşturur.
Gerekli Paketlerin Kurulması
REST API sunucumuzu oluşturmak için Express kullanacağız:
npm install express
Express kullanmak zorunlu değildir. Yalnızca Node.js kullanarak da bir REST API oluşturabiliriz. Ancak bu durumda routing, istek gövdelerini okuma ve cevap gönderme gibi birçok işlemi kendimiz yönetmemiz gerekir.
Express, bu işlemleri daha kolay gerçekleştirmemizi sağlar.
Geliştirme sırasında yaptığımız her değişiklikten sonra sunucuyu manuel olarak yeniden başlatmamak için nodemon paketini de yükleyelim:
npm install --save-dev nodemon
package.json dosyasındaki scripts alanına başlangıç komutumuzu ekleyebiliriz:
{
"scripts": {
"start": "nodemon app.js"
}
}Artık uygulamamızı aşağıdaki komutla çalıştırabiliriz:
npm start
Express Sunucusunun Oluşturulması
Projenin ana dizininde app.js dosyasını oluşturalım:
const express = require('express');
const app = express();
app.listen(8080);Bu kod oldukça basit bir Express sunucusu oluşturur ve sunucunun 8080 portundan gelen istekleri dinlemesini sağlar.
Şu anda uygulamamız çalışabilir fakat henüz herhangi bir endpoint tanımlamadığımız için gelen isteklere anlamlı bir cevap vermez.
JSON İsteklerinin Okunması
REST API’mize gönderilen veriler genellikle JSON formatında olacaktır. Express’in gelen JSON verilerini okuyabilmesi için aşağıdaki middleware’i ekleyelim:
app.use(express.json());Eskiden bu işlem için ayrıca body-parser paketi kullanılıyordu. Güncel Express sürümlerinde JSON verilerini okumak için yerleşik express.json() middleware’i yeterlidir.
Route ve Controller Yapısı
API büyüdükçe bütün kodu app.js dosyasında tutmak uygulamanın yönetilmesini zorlaştırır. Bu nedenle route ve controller işlemlerini ayrı dosyalara böleceğiz.
Proje yapımız şu şekilde olacak:
first-rest-api/
├── controllers/
│ └── feed.js
├── routes/
│ └── feed.js
├── app.js
└── package.json
Route dosyası, API’nin hangi endpoint’lere sahip olduğunu tanımlar. Controller dosyası ise bu endpoint’lere istek geldiğinde çalıştırılacak kodları içerir.
Klasik bir Express uygulamasından farklı olarak views klasörüne ihtiyacımız yoktur. Çünkü REST API’miz HTML sayfası oluşturmayacak, yalnızca veri döndürecektir.
İlk Controller’ın Oluşturulması
controllers/feed.js dosyasında gönderileri döndürecek controller fonksiyonunu oluşturalım:
exports.getPosts = (req, res, next) => {
res.status(200).json({
message: 'Gönderiler başarıyla getirildi.',
posts: [
{
id: 'p1',
title: 'İlk gönderim',
content: 'Bu, REST API üzerinden gelen ilk gönderidir.'
}
]
});
};Burada res.json() kullanarak istemciye JSON formatında cevap gönderiyoruz.
Ayrıca cevabın HTTP durum kodunu 200 olarak belirtiyoruz. 200 OK, isteğin başarıyla işlendiğini ifade eder.
İlk Route’un Oluşturulması
Şimdi routes/feed.js dosyasında ilk endpoint’imizi tanımlayalım:
const express = require('express');
const feedController = require('../controllers/feed');
const router = express.Router();
router.get('/posts', feedController.getPosts);
module.exports = router;Bu route, /posts yoluna gönderilen GET isteklerini karşılar ve getPosts controller fonksiyonunu çalıştırır.
Route’un Uygulamaya Eklenmesi
Oluşturduğumuz route’u kullanabilmek için app.js dosyasına kaydetmemiz gerekir:
const express = require('express');
const feedRoutes = require('./routes/feed');
const app = express();
app.use(express.json());
app.use('/feed', feedRoutes);
app.listen(8080);Route dosyasında /posts, app.js içerisinde ise /feed yolunu tanımladık. Bu iki yol birleştiğinde endpoint’in tam adresi ortaya çıkar:
GET /feed/posts
İstek bu endpoint’e ulaştığında gerçekleşen akış şöyledir:
GET /feed/posts isteği
↓
app.js içerisindeki /feed middleware’i
↓
routes/feed.js içerisindeki /posts route’u
↓
controllers/feed.js içerisindeki getPosts fonksiyonu
↓
JSON cevap
API çalışırken aşağıdaki adrese bir GET isteği göndererek endpoint’i deneyebiliriz:
Alacağımız cevap şu şekilde olacaktır:
{
"message": "Gönderiler başarıyla getirildi.",
"posts": [
{
"id": "p1",
"title": "İlk gönderim",
"content": "Bu, REST API üzerinden gelen ilk gönderidir."
}
]
}Farklı Kaynaklardan Gelen İstekler ve CORS
Frontend ve REST API her zaman aynı adres üzerinde çalışmayabilir. Örneğin frontend localhost:3000, API ise localhost:8080 üzerinde çalışabilir.
Protokol, alan adı veya port bilgilerinden biri farklı olduğunda tarayıcı bunları farklı origin olarak değerlendirir. Tarayıcılar güvenlik nedeniyle farklı origin’ler arasındaki isteklere varsayılan olarak izin vermeyebilir. Bu durumda CORS hatasıyla karşılaşılır.
CORS, Cross-Origin Resource Sharing ifadesinin kısaltmasıdır. API’ye hangi origin’lerin, HTTP yöntemlerinin ve başlıkların erişebileceğini sunucu tarafında belirlememizi sağlar.
Express uygulamasında CORS başlıkları manuel olarak şöyle eklenebilir:
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader(
'Access-Control-Allow-Methods',
'GET, POST, PUT, PATCH, DELETE, OPTIONS'
);
res.setHeader(
'Access-Control-Allow-Headers',
'Content-Type, Authorization'
);
next();
});Buradaki *, API’ye tüm origin’lerden erişilebileceğini belirtir. Gerçek projelerde API yalnızca belirli frontend uygulamaları tarafından kullanılacaksa izin verilen origin açıkça belirtilmelidir:
res.setHeader(
'Access-Control-Allow-Origin',
'https://www.example.com'
);CORS hatası frontend JavaScript kodundan çözülemez. Gerekli izinlerin API’yi sunan server tarafından gönderilmesi gerekir.
Sonuç
Bu yazıda REST API’lerin neden kullanıldığını, istemci ve sunucu arasında nasıl veri alışverişi sağladığını ve REST yaklaşımının temel ilkelerini inceledik.
REST API’lerin kullanıcı arayüzü yerine veri sunduğunu, bu verilerin çoğunlukla JSON formatında aktarıldığını ve endpoint’lerin HTTP yöntemleriyle birlikte belirli işlemleri temsil ettiğini gördük. Ayrıca tutarlı bir arayüz ve durumsuz iletişim gibi REST ilkelerinin API tasarımındaki önemini ele aldık.
Teorik bilgileri uygulamaya dönüştürmek için Node.js ve Express kullanarak ilk REST API sunucumuzu oluşturduk. Route ve controller yapılarını ayırdık, bir GET endpoint’i tanımladık ve istemciye JSON formatında cevap gönderdik. Son olarak frontend ile API farklı origin’lerde çalıştığında karşılaşılabilecek CORS problemini ve bu problemin sunucu tarafında nasıl yönetilebileceğini öğrendik.
Bu temel yapı üzerine yeni kaynaklar oluşturan POST endpoint’leri, veri doğrulama, hata yönetimi, kimlik doğrulama ve veritabanı bağlantısı gibi özellikler eklenerek daha kapsamlı REST API’ler geliştirilebilir.