Node.js ve Express ile İlk REST API’ni Geliştirmek
Node.js ve Express ile bir görev yönetimi API’si geliştirelim: endpoint tasarımı, veri doğrulama, HTTP durum kodları ve merkezi hata yönetimi.
Bir uygulamanın arayüzü kullanıcıyla iletişim kurar; API ise arka plandaki işlemleri yürütür. Bir görev oluşturmak, ürünleri listelemek veya bir kaydı güncellemek için istemci ile sunucu arasında bu bağlantıya ihtiyaç duyarız.
Bu yazıda Node.js ve Express kullanarak küçük bir görev yönetimi API’si geliştireceğiz. Hedefimiz yalnızca çalışan endpoint’ler yazmak değil; veri doğrulama, HTTP durum kodları ve hata yönetimi gibi temel alışkanlıkları da uygulamak.
Neler geliştireceğiz?
API’miz dört işlemi destekleyecek:
- GET /api/tasks: Görevleri listeleme
- GET /api/tasks/:id: Tek bir görevi getirme
- POST /api/tasks: Yeni görev oluşturma
- PATCH /api/tasks/:id: Görevin tamamlanma durumunu değiştirme
Örneği kolayca çalıştırabilmek için verileri bellekte tutacağız. Sunucu yeniden başladığında kayıtlar silinecek; kalıcı depolama için daha sonra bir veritabanı eklenebilir.
1. Projeyi hazırlamak
Terminalde yeni bir proje oluşturalım:
mkdir task-api
cd task-api
npm init -y
npm install express
Proje klasörüne server.js adında bir dosya ekleyelim.
2. Sunucuyu ve endpoint’leri yazmak
Aşağıdaki kod, örneğin tamamını içeriyor:
const express = require("express");
const { randomUUID } = require("node:crypto");
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json({ limit: "10kb" }));
const tasks = new Map();
// Görevleri listele
app.get("/api/tasks", (req, res) => {
res.json({
data: Array.from(tasks.values()),
});
});
// Tek bir görev getir
app.get("/api/tasks/:id", (req, res) => {
const task = tasks.get(req.params.id);
if (!task) {
return res.status(404).json({
error: { message: "Görev bulunamadı." },
});
}
res.json({ data: task });
});
// Yeni görev oluştur
app.post("/api/tasks", (req, res) => {
const title = req.body?.title;
if (
typeof title !== "string" ||
title.trim().length === 0 ||
title.trim().length > 120
) {
return res.status(400).json({
error: {
message: "Başlık 1–120 karakter arasında olmalıdır.",
},
});
}
const task = {
id: randomUUID(),
title: title.trim(),
completed: false,
createdAt: new Date().toISOString(),
};
tasks.set(task.id, task);
res
.status(201)
.location(`/api/tasks/${task.id}`)
.json({ data: task });
});
// Tamamlanma durumunu güncelle
app.patch("/api/tasks/:id", (req, res) => {
const task = tasks.get(req.params.id);
if (!task) {
return res.status(404).json({
error: { message: "Görev bulunamadı." },
});
}
if (typeof req.body?.completed !== "boolean") {
return res.status(400).json({
error: {
message: "completed alanı true veya false olmalıdır.",
},
});
}
task.completed = req.body.completed;
res.json({ data: task });
});
// Tanımlanmamış endpoint
app.use((req, res) => {
res.status(404).json({
error: { message: "Endpoint bulunamadı." },
});
});
// Merkezi hata yönetimi
app.use((err, req, res, next) => {
if (res.headersSent) {
return next(err);
}
if (err.type === "entity.parse.failed") {
return res.status(400).json({
error: {
message: "Geçerli bir JSON gövdesi gönderilmelidir.",
},
});
}
if (err.type === "entity.too.large") {
return res.status(413).json({
error: {
message: "İstek gövdesi izin verilen boyutu aşıyor.",
},
});
}
console.error(err);
res.status(500).json({
error: {
message: "Beklenmeyen bir sunucu hatası oluştu.",
},
});
});
app.listen(PORT, () => {
console.log(`API http://localhost:${PORT} adresinde çalışıyor.`);
});
Sunucuyu başlatmak için:
node server.js
3. API’yi denemek
Postman veya benzer bir istemciyle http://localhost:3000 adresine aşağıdaki isteği gönderelim:
POST /api/tasks
Content-Type: application/json
{
"title": "İlk API endpoint'imi geliştir"
}
Sunucu 201 Created durum koduyla oluşturulan görevi döndürür:
{
"data": {
"id": "üretilen-benzersiz-kimlik",
"title": "İlk API endpoint'imi geliştir",
"completed": false,
"createdAt": "2026-10-05T10:00:00.000Z"
}
}
Buradaki kimlik ve tarih örnektir; uygulama bunları her kayıt için üretir.
Görevleri listelemek için GET /api/tasks isteğini kullanabiliriz. Bir görevi tamamlamak için ise oluşturma yanıtındaki gerçek kimliği URL’ye ekleyip şu isteği göndeririz:
PATCH /api/tasks/GERCEK_GOREV_KIMLIGI
Content-Type: application/json
{
"completed": true
}
Başlığı boş göndererek doğrulama davranışını da kontrol edebiliriz:
{
"title": " "
}
Bu isteğin sonucu 400 Bad Request olmalıdır.
4. Bu örnekteki tasarım kararları
İstemciden gelen veriyi doğruluyoruz. Arayüzde doğrulama yapılması yeterli değildir; API’ye doğrudan istek gönderilebilir. Bu nedenle başlığın türünü ve uzunluğunu sunucuda kontrol ediyoruz.
Yalnızca izin verdiğimiz alanları güncelliyoruz. İstek gövdesini doğrudan görev nesnesine kopyalamıyoruz. Böylece istemci id veya createdAt gibi alanları değiştiremiyor.
HTTP durum kodlarını anlamlı kullanıyoruz. Başarılı bir okuma için 200, kayıt oluşturma için 201, geçersiz veri için 400, bulunamayan kaynak için 404 dönüyoruz. Bu ayrım, istemcinin yanıtları doğru işlemesini kolaylaştırır.
Hata ayrıntılarını istemciye açmıyoruz. Beklenmeyen hatayı sunucuda kaydederken kullanıcıya genel bir mesaj veriyoruz. Stack trace gibi teknik ayrıntılar API yanıtına taşınmıyor.
5. Uygulama büyüdüğünde
Tek dosya, bu boyuttaki bir örneği takip etmeyi kolaylaştırır. İş kuralları çoğaldığında kodu sorumluluklarına göre ayırabiliriz:
src/
├── app.js
├── server.js
├── routes/
│ └── task.routes.js
├── controllers/
│ └── task.controller.js
├── services/
│ └── task.service.js
└── middleware/
└── error-handler.js
Route katmanı URL’leri tanımlar, controller isteği ve yanıtı yönetir, service katmanı ise iş kurallarını uygular. Veritabanı erişimi de ihtiyaç büyüdükçe ayrı bir katmana taşınabilir.
Üretim ortamına geçerken kalıcı depolama, kimlik doğrulama, kaynak bazında yetkilendirme, sayfalama ve otomatik testler de eklenmelidir. Özellikle birden fazla sunucu çalıştırıldığında, bellekte tutulan verinin her sunucuda ayrı olacağını unutmamak gerekir.
İyi bir API’nin değeri yalnızca veri döndürmesinde değildir. Geçersiz girdilere nasıl yanıt verdiği, hataları nasıl yönettiği ve istemciye ne kadar tutarlı bir sözleşme sunduğu da en az endpoint’lerin kendisi kadar önemlidir.
Related Posts
Node.js Uygulamasını Production’a Hazırlamak: Logging, Error Handling ve Security
Çalışan bir Node.js API'sini işletilebilir hale getirelim: yapılandırma doğrulama, hata sınırları, güvenli loglama, kaynak limitleri ve kontrollü kapanma.
Backend Uygulamalarında Authentication: JWT Nasıl Çalışır?
JWT'nin kodlama, imza ve doğrulama katmanlarını ayıralım; issuer, audience, algoritma sınırı, refresh token ve kaynak yetkilendirmesini birlikte tasarlayalım.
Node.js Uygulamalarında Redis Neden Kullanılır?
Cache-aside örneğinden TTL, invalidation, stampede ve eviction kararlarına; Redis'in performans kazancı ile tutarlılık maliyetini birlikte değerlendirelim.