İçeriğe geç
XDD.baby Yazılım Öğrenme
Sözlük Hata rehberi 0/56

Blok 4 · Ders 28

Express ile API

Express ile düzenli bir REST API: route'lar, parametreler, ara katman (middleware), hata yönetimi ve CRUD.

Bu dersin sonunda

  • Express'i bir projeye kurup route yazabilir, res.send, res.json ve res.status ile yanıt verebilirsin
  • Adresten req.params ve req.query, gövdeden express.json() ile req.body okuyup metni sayıya çevirebilirsin
  • Bellekteki bir dizi üzerinde listeleyen, getiren, ekleyen, güncelleyen ve silen uç noktaları doğru durum kodlarıyla (200, 201, 204, 400, 404) yazabilirsin
  • Kendi kayıt ara katmanını yazıp next()'in zincirdeki yerini ve route sırasının neden önemli olduğunu açıklayabilirsin
  • Gelen veriyi doğrulayıp 400 döndürebilir, 404 işleyicisi ve hata yakalayıcıyla her hatayı JSON'a çevirebilirsin
  • express.static ile aynı sunucudan bir web sayfası sunup API'ni curl.exe ve Invoke-RestMethod ile baştan sona test edebilirsin
Bu sayfada
  1. Neden Express?
  2. Kurulum
  3. İlk route
  4. JSON API: res.json ve res.status
  5. Adresten veri: req.params ve req.query
  6. Gövdeden veri: req.body ve express.json()
  7. Ara katman: isteğin geçtiği kapılar
  8. CRUD: güncellemek ve silmek
  9. Doğrulama ve 400
  10. 404 işleyicisi ve hata yakalayıcı
  11. Statik dosyalar: express.static
  12. API'yi terminalden test etmek
  13. Görevler
  14. Kendini sına
  15. Kayıt noktası

Ders 27'deki sunucun 69 satırdı ve her işi elle yapıyordu: yönlendirme tablosunu sen kurdun, gövdeyi parça parça sen topladın, 404'ü sen yazdın. Gerçek projelerde bu işler her seferinde baştan yazılmaz; Node dünyasının en yaygın aracı olan Express kullanılır. Bu derste aynı şampiyon API'sini Express ile yeniden kuruyorsun. Dersin sonunda listeleyen, getiren, ekleyen, güncelleyen ve silen, bozuk istekleri doğru durum koduyla geri çeviren, her isteği deftere yazan ve yanında bir web sayfası da sunan bir API'n olacak; hepsini terminalden test etmeyi de öğreneceksin.

Neden Express?

Ders 27'deki sunucu.js'e dön ve hangi satırların gerçekten şampiyonlarla ilgili olduğuna bak. Çoğu değil. Gövdeyi toplayan govdeyiOku, her yanıta başlık yazan jsonGonder, metotla yoldan anahtar kuran tablo: bunlar her API'de aynıdır. Görev 4'te /esyalar/2'den id'yi çıkarmak bile startsWith, slice ve boş metin denetimi istedi.

Express bu ortak işleri hazır veren bir npm paketidir. Altta yine Ders 27'deki node:http çalışır; Express onun üstüne kurulmuş bir iskelettir. Sen yalnız "şu adrese şu istek gelince ne olsun" kısmını yazarsın.

İşDers 27'de (node:http)Express'te
Yolu seçmekyollar tablosu, "GET /sampiyonlar" anahtarıapp.get("/api/sampiyonlar", ...)
Adresten idstartsWith ve slicereq.params.id
Sorgu (?ara=a)req.url'in içinde, ayırmak sendereq.query.ara
Gövdefor await, Buffer.concat, JSON.parseexpress.json() ve req.body
JSON yanıtjsonGonder(res, 201, veri)res.status(201).json(veri)
Bilinmeyen adreselse dalında 404hazır bir 404, istersen kendi 404'ün

Bu derste tablonun sağ sütununu satır satır kuracaksın.

Kurulum

Yeni bir proje klasörü aç: terminalde cd C:\kod ile kod klasörüne geç, mkdir sampiyon-api ile klasörü oluştur ve cd sampiyon-api ile içine gir. Sonra package.json'ı oluştur ve Express'i kur (internet bağlantısı gerekir):

Terminal — PowerShell
PS C:\kod\sampiyon-api> npm init -yWrote to C:\kod\sampiyon-api\package.json: {  "name": "sampiyon-api",  "version": "1.0.0",  "description": "",  "main": "index.js",  "scripts": {    "test": "echo \"Error: no test specified\" && exit 1"  },  "keywords": [],  "author": "",  "license": "ISC",  "type": "commonjs"} PS C:\kod\sampiyon-api> npm install express added 65 packages, and audited 66 packages in 2s 28 packages are looking for funding  run `npm fund` for details found 0 vulnerabilities

Tek paket istedin, 65 paket geldi: Express'in kendi bağımlılıkları ve onların bağımlılıkları. looking for funding satırı bağış bekleyen paketleri sayar; bir şey yapman gerekmez. node_modules klasörü 65 alt klasörle doldu; Git'e girmemesi için hemen .gitignore adında bir dosya aç ve içine tek satır yaz (Ders 14):

.gitignore
node_modules/

Klasörü VS Code'da aç (File → Open Folder). package.json'da "type" satırını "module" yap ve scripts kısmını Ders 27'deki iki betikle değiştir:

package.json
{  "name": "sampiyon-api",  "version": "1.0.0",  "description": "",  "main": "index.js",  "scripts": {    "start": "node sunucu.js",    "dev": "node --watch sunucu.js"  },  "keywords": [],  "author": "",  "license": "ISC",  "type": "module",  "dependencies": {    "express": "^5.3.0"  }}

En alttaki dependencies bölümünü npm install kendisi ekledi.

İlk route

Proje klasöründe sunucu.js adında bir dosya aç ve şunu yaz. Soldaki dosya listesinde artık node_modules, .gitignore, package-lock.json, package.json ve sunucu.js duruyor; aşağıdaki pencerede kod sığsın diye listeyi gizledik.

sunucu.js — sampiyon-api — Visual Studio Code
sunucu.js
import express from "express" const app = express()const port = 3000 app.get("/", (req, res) => {    res.send("Sihirdar Vadisi'ne hoş geldin")}) app.listen(port, () => {    console.log(`Sunucu çalışıyor: http://localhost:${port}`)})
PROBLEMSOUTPUTDEBUG CONSOLETERMINAL
PS C:\kod\sampiyon-api> npm run dev > sampiyon-api@1.0.0 dev> node --watch sunucu.js Sunucu çalışıyor: http://localhost:3000
Ln 1, Col 1Spaces: 4UTF-8JavaScript

Ders 27'deki gibi npm run dev: kaydettiğin anda sunucu kendini yeniden başlatır.

Satır satır:

  • import express from "express": yolun başında ./ yok, demek ki bu bir paket; Node onu node_modules'ta bulur.
  • const app = express(): Express uygulamasını kurar. app (application, uygulama), bütün route'ların ve ayarların toplandığı obje; Ders 27'deki sunucu'nun yerini alıyor.
  • app.get("/", fonksiyon): "/ adresine GET isteği gelince bu fonksiyonu çalıştır." Express'te her route böyle bir satırdır: metodun adı küçük harfle (get, post, patch, delete), yanında yol ve isteği karşılayan işleyici fonksiyon. İşleyici Ders 27'deki gibi req ve res alır; Express ikisine de yeni yetenekler eklemiştir.
  • res.send(metin): yanıtı gönderir ve bitirir. Durum kodunu, Content-Type'ı ve gövdenin uzunluğunu kendisi yazar.
  • app.listen(port, fonksiyon): kapıyı açar. Express içeride Ders 27'deki http.createServer'ı çağırır.

İkinci bir terminal aç ve isteği at:

İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/HTTP/1.1 200 OKX-Powered-By: ExpressContent-Type: text/html; charset=utf-8Content-Length: 30ETag: W/"1e-D+CzAxorbVcQ0BzxLHrqT1Wjk5k"Date: Sun, 11 Oct 2026 18:00:28 GMTConnection: keep-aliveKeep-Alive: timeout=5 Sihirdar Vadisi'ne hoş geldin

Tek satır başlık yazmadın, ama yanıtın başı dolu geldi:

  • Content-Type: text/html; charset=utf-8: Express metni bir HTML parçası saydı ve kodlamasını da yazdı. Ders 27'deki hoÅŸ sorunu burada yok.
  • Content-Length: 30: gövde 30 bayt. Cümle boşluklarla birlikte 29 karakter; ş UTF-8'de iki bayt tutuyor.
  • ETag: içeriğin parmak izi. Tarayıcı aynı adresi yeniden isterken elindeki parmak izini gönderir; içerik değişmediyse Express gövdeyi tekrar yollamaz, Ders 26'daki 304'ü döner.
  • X-Powered-By: Express: sunucu hangi çatıyla yazıldığını söylüyor. Saldırgan için bu da bir ipucu; sunucunun güvenliğini Ders 31'de ele alacaksın.

Tarayıcıda localhost:3000 adresini açarsan aynı cümle görünür. Şimdi tanımlamadığın bir adresi iste.

Sık hataCannot GET /yok

Ne zaman çıkar? Metot ve yol ikilisine uyan bir route olmadığında. Kendi 404'ünü yazana kadar Express bu küçük HTML sayfasını gönderir.

İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/yokHTTP/1.1 404 Not FoundX-Powered-By: ExpressContent-Security-Policy: default-src 'none'X-Content-Type-Options: nosniffContent-Type: text/html; charset=utf-8Content-Length: 142Date: Sun, 11 Oct 2026 18:00:28 GMTConnection: keep-aliveKeep-Alive: timeout=5 <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Error</title></head><body><pre>Cannot GET /yok</pre></body></html>
localhost:3000/yok
Tarayıcıda aynı yanıt. Sekmenin başlığında Error yazar.

Cannot GET /yok: "/yok adresini GET ile alamıyorum." Metot farklıysa mesaj da değişir: POST attığın bir adresin route'u yoksa Cannot POST /... yazar.

Nasıl düzelir? Önce adresi denetle: yazım, /api öneki, metot. Adres doğruysa o route'u henüz yazmamışsındır. Dersin ilerisinde bu HTML sayfasının yerine JSON döndüren kendi 404 işleyicini yazacaksın.

Sık hataSunucu "çalışıyor" diyor ama istekleri eski bir sunucu karşılıyor

Ne zaman çıkar? 3000 numaralı kapıyı başka bir program, örneğin hâlâ açık duran Ders 27 sunucun dinlerken. Ders 27'de node:http bu durumda EADDRINUSE ile çöküyordu. Express 5 ise hatayı app.listen'a verdiğin fonksiyona iletir. Fonksiyonun hataya bakmadığı için başarı mesajını yazar ve program sessizce biter:

Terminal — PowerShell
PS C:\kod\sampiyon-api> npm run dev > sampiyon-api@1.0.0 dev> node --watch sunucu.js Sunucu çalışıyor: http://localhost:3000Completed running 'sunucu.js'. Waiting for file changes before restarting...

İpucu son satırda: Completed running "çalışması bitti" demek, oysa bir sunucu hiç bitmemeli. İstekleri kapıyı tutan eski sunucu karşılar: curl.exe http://localhost:3000/ beklediğin cümle yerine Ders 27'nin cevabını, {"mesaj":"Sihirdar Vadisi'ne hoş geldin"} JSON'unu döndürür.

Nasıl düzelir? İki adımda. Önce hatayı görünür yap: Express 5'in belgelerinde önerdiği biçimle app.listen'ın fonksiyonu bir hata parametresi alsın ve hata varsa fırlatsın.

sunucu.js
app.listen(port, (hata) => {    if (hata) {        throw hata    }    console.log(`Sunucu çalışıyor: http://localhost:${port}`)})

Kaydet, npm run dev'i Ctrl+C ile durdur ve sunucuyu bir kez node sunucu.js ile başlat. Kapı hâlâ doluyken artık hata ortada:

Terminal — PowerShell
PS C:\kod\sampiyon-api> node sunucu.jsfile:///C:/kod/sampiyon-api/sunucu.js:12        throw hata        ^ Error: listen EADDRINUSE: address already in use :::3000    at Server.setupListenHandle [as _listen2] (node:net:2324:16)    ...    at file:///C:/kod/sampiyon-api/sunucu.js:10:5    ... {  code: 'EADDRINUSE',  errno: -4091,  syscall: 'listen',  address: '::',  port: 3000} Node.js v24.20.0

Sonra kapıyı tutan eski sunucuyu kapat (Ders 27'deki Get-NetTCPConnection yolu) ve npm run dev'i yeniden başlat. Bu dersteki bütün kodlarda app.listen artık bu biçimde.

JSON API: res.json ve res.status

Şampiyon listesini ekle: diziyi const port'un altına, yeni route'u app.get("/")'in altına yaz.

sunucu.js
let sampiyonlar = [    { id: 1, isim: "Ahri", rol: "Büyücü" },    { id: 2, isim: "Garen", rol: "Dövüşçü" },    { id: 3, isim: "Jinx", rol: "Nişancı" }] app.get("/", (req, res) => {    res.send("Sihirdar Vadisi'ne hoş geldin")}) app.get("/api/sampiyonlar", (req, res) => {    res.json(sampiyonlar)})
  • let, çünkü silerken dizinin yerine filter'ın verdiği yeni diziyi koyacağız (Ders 23'teki maç günlüğü gibi).
  • /api/sampiyonlar: API adreslerini /api ile başlatıyoruz. Dersin sonunda aynı sunucu bir web sayfası da sunacak; önek, sayfa adresleriyle API adreslerinin karışmasını önler. Yaygın bir gelenek.
  • res.json(veri): Ders 27'deki jsonGonder'in işini görür. Veriyi JSON.stringify ile metne çevirir, Content-Type: application/json; charset=utf-8 yazar ve gönderir.
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/api/sampiyonlarHTTP/1.1 200 OKX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 128ETag: W/"80-vFMJ8H5lzGH6NdcJX5UCrDd7RM4"Date: Sun, 11 Oct 2026 18:03:39 GMTConnection: keep-aliveKeep-Alive: timeout=5 [{"id":1,"isim":"Ahri","rol":"Büyücü"},{"id":2,"isim":"Garen","rol":"Dövüşçü"},{"id":3,"isim":"Jinx","rol":"Nişancı"}]PS C:\kod\sampiyon-api> Invoke-RestMethod http://localhost:3000/api/sampiyonlar id isim  rol-- ----  --- 1 Ahri  Büyücü 2 Garen Dövüşçü 3 Jinx  Nişancı

Başka bir durum kodu göndermek için yanıttan önce res.status(kod) yazarsın: res.status(404).json({ hata: "..." }). res.status kodu ayarlar ve res'in kendisini geri verir; bu yüzden arkasına hemen .json(...) eklenebilir. Ders 9'daki zincirlemenin aynısı.

Bir fark daha dene: curl.exe http://localhost:3000/API/Sampiyonlar/ da aynı listeyi döndürür. Express varsayılan olarak büyük-küçük harfe ve sondaki /'e bakmaz; Ders 27'deki tablonda ikisi de 404 alıyordu.

Adresten veri: req.params ve req.query

İstemci veriyi sunucuya çoğunlukla üç yerde taşır: ikisi adreste, biri gövdede. Express her birini req'in ayrı bir alanına koyar.

İstekte nerede? Express nereye koyar? GET /api/sampiyonlar/2 route: /api/sampiyonlar/:id req.params.id "2" metin GET /api/sampiyonlar?ara=a ? işaretinden sonrası: sorgu dizesi req.query.ara "a" metin POST /api/sampiyonlar gövde, Content-Type: application/json {"isim": "Lux", "rol": "Büyücü"} req.body.isim "Lux" express.json() okur Adresten gelen değer hep metindir; gövdedeki JSON sayıyı sayı bırakır.
Şekil 28.1 Bir isteğin üç veri kaynağı ve Express'in onları koyduğu yer.

Yol parametresi: req.params

Tek bir şampiyonu getirmek için /api/sampiyonlar/1, /api/sampiyonlar/2... diye sonsuz route yazamazsın. Yolun değişen kısmına iki noktayla bir ad verirsin. /api/sampiyonlar route'unun altına ekle; console.log satırları geçici:

sunucu.js
app.get("/api/sampiyonlar/:id", (req, res) => {    console.log(req.params)    console.log(typeof req.params.id)    res.json({ istenen: req.params.id })})
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/2{"istenen":"2"}PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/Ahri{"istenen":"Ahri"}
Sunucunun terminali
[Object: null prototype] { id: '2' }string[Object: null prototype] { id: 'Ahri' }string
  • :id bir yer tutucu. /api/sampiyonlar/ ile başlayıp arkasından tek parça gelen her adres bu route'a uyar; o parça req.params.id'ye konur.
  • [Object: null prototype]: Express 5 bu objeyi "prototipsiz" kurar; bir güvenlik önlemi. Senin için sıradan bir obje gibi çalışır, bu yazıyı görmezden gel.
  • typeof string dedi: adresten gelen her şey metindir. 2 bile "2" olarak gelir.

console.log satırlarını sil ve route'u gerçek işine kavuştur:

sunucu.js
app.get("/api/sampiyonlar/:id", (req, res) => {    const id = Number(req.params.id)    if (!Number.isInteger(id)) {        return res.status(400).json({ hata: "id bir tam sayı olmalı" })    }    const sampiyon = sampiyonlar.find((s) => s.id === id)    if (!sampiyon) {        return res.status(404).json({ hata: `${id} numaralı şampiyon yok` })    }    res.json(sampiyon)})
  • Number metni sayıya çevirir; Number.isInteger (Ders 17) "tam sayı mı?" diye sorar. abc (NaN) ve 2.5 geçemez: 400, çünkü hatalı olan isteğin kendisi.
  • find (Ders 11) şampiyonu arar; bulamazsa undefined döner ve cevap 404 olur.
  • return res.status(...).json(...): yanıtı gönderir ve fonksiyondan aynı satırda çıkar. Ders 27'de jsonGonder(...) ve return'ü ayrı satırlara yazıyordun. return'ü unutursan fonksiyon aşağı devam eder ve aynı isteğe ikinci bir yanıt göndermeye kalkar; bunun bedelini doğrulama bölümünde göreceksin.
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/2{"id":2,"isim":"Garen","rol":"Dövüşçü"}PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/api/sampiyonlar/9HTTP/1.1 404 Not FoundX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 36ETag: W/"24-WSS7mtlwsKE192gyASQV02ol7hQ"Date: Sun, 11 Oct 2026 18:07:36 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"hata":"9 numaralı şampiyon yok"}PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/abc{"hata":"id bir tam sayı olmalı"}PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/2.5{"hata":"id bir tam sayı olmalı"}PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/0{"hata":"0 numaralı şampiyon yok"}
Sık hataVar olan şampiyon da 404 alıyor: metni sayıyla karşılaştırmak

Ne zaman çıkar? req.params.id'yi sayıya çevirmeden === ile karşılaştırınca. Hata mesajı yok; sunucu "çalışıyor", ama hiçbir id'yi bulamıyor.

sunucu.js✗ Yanlış
    const sampiyon = sampiyonlar.find((s) => s.id === req.params.id)
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/api/sampiyonlar/2HTTP/1.1 404 Not FoundX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 36ETag: W/"24-OewpNrLFB8l54siHjh9en0QmY1g"Date: Sun, 11 Oct 2026 18:22:14 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"hata":"2 numaralı şampiyon yok"}

Dizideki id sayı (2), adresten gelen metin ("2"). === türlere de baktığı için (Ders 2) eşleşme hiç olmaz.

Nasıl düzelir? Adresten geleni önce sayıya çevir: const id = Number(req.params.id), sonra s.id === id. Sorgu dizesindeki değerler için de aynı kural geçerli.

Sorgu dizesi: req.query

Listeyi süzmenin REST'teki yeri sorgu dizesidir: /api/sampiyonlar?ara=a. Express sorgu kısmını yoldan ayırıp req.query objesine koyar. Önce neye benzediğine bak; liste route'una geçici bir satır ekle:

sunucu.js
app.get("/api/sampiyonlar", (req, res) => {    console.log(req.query)    res.json(sampiyonlar)})

Üç istek at: ?ara=a, ?ara=a&rol=Tank ve ?ara=a&ara=e. Adresi her seferinde tırnak içinde yaz: curl.exe "http://localhost:3000/api/sampiyonlar?ara=a&rol=Tank". Üçü de şimdilik bütün listeyi döndürür, ama sunucunun terminali farklı:

Sunucunun terminali
[Object: null prototype] { ara: 'a' }[Object: null prototype] { ara: 'a', rol: 'Tank' }[Object: null prototype] { ara: [ 'a', 'e' ] }

Her değer metin. Aynı ad iki kez gelirse dizi olur; hiç gelmezse req.query.ara undefined olur. Ders 27'nin aksine yol hâlâ eşleşti: Express route ararken sorgu kısmına bakmaz. Satırı sil ve gerçek aramayı yaz:

sunucu.js
app.get("/api/sampiyonlar", (req, res) => {    const { ara } = req.query    if (typeof ara !== "string") {        return res.json(sampiyonlar)    }    const aranan = ara.toLowerCase()    res.json(sampiyonlar.filter((s) => s.isim.toLowerCase().includes(aranan)))})
  • const { ara } = req.query: parçalama.
  • typeof ara !== "string": ara hiç yoksa (undefined) ya da iki kez gelip dizi olduysa bütün liste döner. Dışarıdan gelen değerin türünü varsayma, sor.
  • toLowerCase ve includes (Ders 9) büyük-küçük harfe bakmadan arar; filter (Ders 11) uyanları toplar.
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe "http://localhost:3000/api/sampiyonlar?ara=a"[{"id":1,"isim":"Ahri","rol":"Büyücü"},{"id":2,"isim":"Garen","rol":"Dövüşçü"}]PS C:\kod\sampiyon-api> curl.exe "http://localhost:3000/api/sampiyonlar?ara=X"[{"id":3,"isim":"Jinx","rol":"Nişancı"}]PS C:\kod\sampiyon-api> curl.exe "http://localhost:3000/api/sampiyonlar?ara=zz"[]PS C:\kod\sampiyon-api> curl.exe "http://localhost:3000/api/sampiyonlar?ara="[{"id":1,"isim":"Ahri","rol":"Büyücü"},{"id":2,"isim":"Garen","rol":"Dövüşçü"},{"id":3,"isim":"Jinx","rol":"Nişancı"}]

Adresi tırnak içine aldık, çünkü PowerShell'de & özel bir işarettir (Ders 26). Hiçbir şey bulunamayınca 404 değil, boş dizi döndü: liste adresi var, yalnızca içi boş. Boş arama (?ara=) her şeyi getirdi, çünkü boş metin her adın içinde geçer.

Gövdeden veri: req.body ve express.json()

Yeni şampiyon eklemek için istemci veriyi gövdede gönderir. Ders 27'de gövdeyi for await ile parça parça toplamıştın. Express'te gövdenin req.body'de durması gerekiyor; önce gerçekten durup durmadığına bak. :id route'unun altına yaz:

sunucu.js
app.post("/api/sampiyonlar", (req, res) => {    const yeni = { id: sampiyonlar.length + 1, isim: req.body.isim, rol: req.body.rol }    sampiyonlar.push(yeni)    res.status(201).json(yeni)})

Yeni kayıt için 201 Created (Ders 26). İsteği at:

Sık hataTypeError: Cannot read properties of undefined (reading 'isim')

Ne zaman çıkar? express.json() olmadan req.body'den bir alan okumaya kalkınca. Express gövdeyi kendiliğinden okumaz; okuyacak ara katmanı eklemediysen req.body undefined kalır.

İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i --json "{}" http://localhost:3000/api/sampiyonlarHTTP/1.1 500 Internal Server ErrorX-Powered-By: ExpressContent-Security-Policy: default-src 'none'X-Content-Type-Options: nosniffContent-Type: text/html; charset=utf-8... <!DOCTYPE html>...<pre>TypeError: Cannot read properties of undefined (reading &#39;isim&#39;)<br> &nbsp; &nbsp;at file:///C:/kod/sampiyon-api/sunucu.js:38:63<br> ...</pre>...
Sunucunun terminali
TypeError: Cannot read properties of undefined (reading 'isim')    at file:///C:/kod/sampiyon-api/sunucu.js:38:63    at Layer.handleRequest (C:\kod\sampiyon-api\node_modules\router\lib\layer.js:152:17)    ...

sunucu.js:38:63 tam olarak req.body.isim'i gösteriyor: undefined'ın isim'i okunamaz. Sunucu çökmedi. Express hatayı yakaladı, terminale yazdı ve istemciye 500 ile yığın izli bir HTML sayfası gönderdi. Aynı hata Ders 27'deki sunucunu düşürürdü.

Nasıl düzelir? Gövdeyi okuyan ara katmanı ekle: app.use(express.json()).

Dizinin hemen altına, route'ların üstüne tek satır ekle:

sunucu.js
] app.use(express.json()) app.get("/", (req, res) => {

express.json(), isteğin Content-Type'ı application/json ise gövdeyi okur, JSON.parse eder ve sonucu req.body'ye koyar: Ders 27'deki govdeyiOku ile JSON.parse'ın işi tek satırda. app.use "bunu her istekte çalıştır" demek; ne olduğunu bir sonraki bölümde açacağız. Kaydet ve yeniden dene:

İkinci terminal
PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body '{"isim": "Lux", "rol": "Büyücü"}' id isim rol-- ---- --- 4 Lux  Büyücü PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/4{"id":4,"isim":"Lux","rol":"Büyücü"}

Çalışıyor. Peki çalışmaması gereken bir istek? Boş bir obje gönder:

İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i --json "{}" http://localhost:3000/api/sampiyonlarHTTP/1.1 201 CreatedX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 8ETag: W/"8-4U72Xq+/t/P6Xscj7oQblSpGEbo"Date: Sun, 11 Oct 2026 18:08:11 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"id":5}PS C:\kod\sampiyon-api> Invoke-RestMethod http://localhost:3000/api/sampiyonlar id isim  rol-- ----  --- 1 Ahri  Büyücü 2 Garen Dövüşçü 3 Jinx  Nişancı 4 Lux   Büyücü 5

Sunucu adı ve rolü olmayan bir şampiyonu 201 ile kabul etti. isim ve rol undefined olduğu için JSON.stringify onları yazmadı; geriye yalnız {"id":5} kaldı. Kod çalışıyor ama doğru değil. Bunu doğrulama bölümünde düzelteceksin.

Bozuk bir JSON gönderirsen (curl.exe -i --json "Lux" http://localhost:3000/api/sampiyonlar) express.json() onu okuyamaz ve Express 400 Bad Request ile yine yığın izli bir HTML sayfası döndürür. Sunucu çökmez; bu sayfayı da birazdan JSON'a çevireceksin.

Ara katman: isteğin geçtiği kapılar

app.use(express.json()) satırını açalım. Express'te gelen her istek, sırayla dizilmiş fonksiyonlardan geçer. Her fonksiyon isteğe bakar ve iki şeyden birini yapar: ya yanıtı gönderip işi bitirir ya da next() ile isteği sıradakine devreder. Bu fonksiyonlara ara katman (middleware) denir. express.json() gövdeyi okuyup req.body'ye koyan ve next() diyen bir ara katman; route'ların işleyicileri ise yanıtı gönderen son halkalar.

Kendi ara katmanını yaz: her isteği terminale yazan bir kayıt (log) tutucu. app.use(express.json())'ın hemen üstüne:

sunucu.js
app.use((req, res, next) => {    console.log(`${req.method} ${req.url}`)    next()})app.use(express.json())

next, Express'in fonksiyonuna verdiği üçüncü parametre: çağırınca istek bir sonraki halkaya geçer. app.use'a yol vermediğin için bu ara katman her metotta ve her adreste çalışır. Dört istek at (/api/sampiyonlar, /api/sampiyonlar/2, /api/sampiyonlar?ara=x ve /yok) ve sunucunun terminaline bak:

Sunucunun terminali
GET /api/sampiyonlarGET /api/sampiyonlar/2GET /api/sampiyonlar?ara=xGET /yok

Var olmayan /yok bile deftere girdi: kayıt tutucu zincirin başında, route'lardan önce çalışıyor. Express ara katmanları ve route'ları yazıldıkları sırayla dener. İsteğin dersin sonundaki sunucuda izleyeceği yol:

POST /api/sampiyonlar 1. kayıt tutucu her isteği deftere yazar next() 2. express.json() gövdeyi req.body'ye koyar next() 3. express.static dosya varsa onu gönderir next() 4. route'lar app.get, app.post, app.delete next() 5. 404 işleyicisi kimse yanıtlamadıysa hata yakalayıcı (hata, req, res, next) hatayı JSON'a çevirir bozuk JSON fırlayan hata eşleşen route yanıt verir 404 JSON döner
Şekil 28.2 Ara katman zinciri: istek yukarıdan aşağı ilerler. Her halka ya yanıtı gönderir ya da next() der. Hatalar zincirin dışına çıkıp hata yakalayıcıya gider.

Şekildeki 3. ve 5. halkayı, hata yakalayıcıyla birlikte dersin ilerisinde ekleyeceksin.

Kayda durum kodunu eklemek

Bu kaydın bir eksiği var: istek geldiği anda yanıtın ne olacağı henüz belli değil. Yanıt bittiğinde yazmak için res.on("finish", fonksiyon) kullan. Ders 22'deki addEventListener'ın Node'daki karşılığı: "yanıt gönderilip bitince bunu çalıştır". Kayıt tutucuyu şöyle değiştir:

sunucu.js
app.use((req, res, next) => {    const baslangic = Date.now()    res.on("finish", () => {        const sure = Date.now() - baslangic        console.log(`${req.method} ${req.url} ${res.statusCode} ${sure} ms`)    })    next()})
Sunucunun terminali
GET /api/sampiyonlar 200 5 msGET /api/sampiyonlar/9 404 1 msGET /api/sampiyonlar/abc 400 1 msPOST /api/sampiyonlar 201 13 msGET /yok 404 1 ms

Date.now() (Ders 23) ile istek gelince saati aldık, yanıt bitince farkı yazdık; milisaniyeler sende farklı çıkar. Gerçek sunucuların kayıtları da böyle satırlardan oluşur: hangi istek, hangi sonuç, ne kadar sürdü. Bir sorun çıktığında ilk bakacağın yer burası.

Sık hataİstek hiç bitmiyor: ara katmanda next() unutuldu

Ne zaman çıkar? Bir ara katman ne yanıt gönderir ne de next() derse. İstek o halkada takılı kalır; arkasındaki route'lar hiç çalışmaz.

sunucu.js✗ Yanlış
app.use((req, res, next) => {    console.log(`${req.method} ${req.url}`)})
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe --max-time 5 http://localhost:3000/api/sampiyonlarcurl: (28) Operation timed out after 5014 milliseconds with 0 bytes receivedPS C:\kod\sampiyon-api> Invoke-RestMethod http://localhost:3000/api/sampiyonlar -TimeoutSec 5Invoke-RestMethod : The operation has timed out....

İki mesaj da Türkçe Windows'ta da İngilizce çıkar: "işlem zaman aşımına uğradı". Sunucunun terminalinde hata yok, yalnız GET /api/sampiyonlar satırları var: istek geldi, deftere yazıldı ve orada kaldı. Belirti, Ders 27'de res.end'i unuttuğundakinin aynısı.

Nasıl düzelir? Her ara katman ya bir yanıt göndermeli ya da next() demeli. Yanıt gönderen bir dalda next() yazma; ikisini birden yaparsan aynı isteğe iki yanıt gider.

CRUD: güncellemek ve silmek

Elindeki API bir kaynağı listeliyor, getiriyor ve ekliyor. Eksik iki iş var: güncellemek ve silmek. Bu dört işin ortak bir adı var.

İşMetot ve adresBaşarıdaOlası hatalar
ListeleGET /api/sampiyonlar200 ve diziyok; bulunamayınca boş dizi
GetirGET /api/sampiyonlar/:id200 ve kayıt400 id tam sayı değil, 404 yok
EklePOST /api/sampiyonlar201 ve yeni kayıt400 gövde geçersiz
GüncellePATCH /api/sampiyonlar/:id200 ve güncel kayıt400, 404
SilDELETE /api/sampiyonlar/:id204, gövde yok400, 404

Güncellemek ve silmek de tek bir şampiyonla çalışır: id'yi sayıya çevirmek, şampiyonu aramak, yoksa 404... GET /:id'deki satırların aynısı. Üç route'a üç kez yazmak yerine bu işi bir ara katmana taşı. GET /:id route'unu sil ve yerine şunu yaz:

sunucu.js
function sampiyonuBul(req, res, next) {    const id = Number(req.params.id)    if (!Number.isInteger(id)) {        return res.status(400).json({ hata: "id bir tam sayı olmalı" })    }    const sampiyon = sampiyonlar.find((s) => s.id === id)    if (!sampiyon) {        return res.status(404).json({ hata: `${id} numaralı şampiyon yok` })    }    req.sampiyon = sampiyon    next()} app.get("/api/sampiyonlar/:id", sampiyonuBul, (req, res) => {    res.json(req.sampiyon)})

sampiyonuBul, eski route'un gövdesinin neredeyse aynısı; değişen tek şey sonu. Yanıt göndermek yerine bulduğu şampiyonu req.sampiyon'a koyup next() diyor. express.json()'ın req.body'yi doldurması gibi, bu ara katman da req.sampiyon'u dolduruyor. app.get(yol, sampiyonuBul, işleyici) "önce sampiyonuBul, o next() derse işleyici" demek; 400 ya da 404 durumunda işleyici hiç çalışmaz. Şimdi POST'un altına güncellemeyi ve silmeyi ekle:

sunucu.js
app.patch("/api/sampiyonlar/:id", sampiyonuBul, (req, res) => {    req.sampiyon.isim = req.body?.isim ?? req.sampiyon.isim    req.sampiyon.rol = req.body?.rol ?? req.sampiyon.rol    res.json(req.sampiyon)}) app.delete("/api/sampiyonlar/:id", sampiyonuBul, (req, res) => {    sampiyonlar = sampiyonlar.filter((s) => s.id !== req.sampiyon.id)    res.status(204).end()})
  • req.body?.isim ?? req.sampiyon.isim: gövdede isim varsa onu, yoksa eskisini al (Ders 12). Gövde hiç yoksa ?. çökmeyi önler. PATCH yalnız gönderilen alanları değiştirir (Ders 26).
  • req.sampiyon dizideki objenin kendisi, bir kopyası değil (Ders 12). Onu değiştirince dizideki kayıt da değişir.
  • res.status(204).end(): 204 No Content, gövdesiz yanıt. end() gönderip bitirir.
İkinci terminal
PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Patch -Uri http://localhost:3000/api/sampiyonlar/1 -ContentType "application/json; charset=utf-8" -Body '{"rol": "Destek"}' id isim rol-- ---- --- 1 Ahri Destek PS C:\kod\sampiyon-api> curl.exe -i -X DELETE http://localhost:3000/api/sampiyonlar/2HTTP/1.1 204 No ContentX-Powered-By: ExpressDate: Sun, 11 Oct 2026 18:10:16 GMTConnection: keep-aliveKeep-Alive: timeout=5 PS C:\kod\sampiyon-api> curl.exe -X DELETE http://localhost:3000/api/sampiyonlar/2{"hata":"2 numaralı şampiyon yok"}

204'te Content-Type ve Content-Length bile yok; gönderilecek bir şey yok. Aynı silme ikinci kez 404 aldı: Garen artık yok.

Çalışıyor, ama doğru mu?

Garen'i sildikten sonra bir şampiyon ekle ve listeye bak:

İkinci terminal
PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body '{"isim": "Lux", "rol": "Büyücü"}' id isim rol-- ---- --- 3 Lux  Büyücü PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar[{"id":1,"isim":"Ahri","rol":"Destek"},{"id":3,"isim":"Jinx","rol":"Nişancı"},{"id":3,"isim":"Lux","rol":"Büyücü"}]PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/3{"id":3,"isim":"Jinx","rol":"Nişancı"}PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Delete -Uri http://localhost:3000/api/sampiyonlar/3 PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar[{"id":1,"isim":"Ahri","rol":"Destek"}]

İki şampiyonun kimliği aynı: 3. Dizide iki eleman kalmıştı, sampiyonlar.length + 1 Lux'a 3 verdi; ama 3 Jinx'indi. find ilk bulduğunu döndürdüğü için Lux'a artık ulaşılamıyor. Jinx'i silmek isteyen ise ikisini birden sildi, çünkü filter id'si 3 olan herkesi eler.

Çare, son verilen kimliği ayrı bir kutuda tutmak. Dizinin altına let sonId = 3 satırını ekle ve POST'u şöyle değiştir:

sunucu.js
app.post("/api/sampiyonlar", (req, res) => {    sonId = sonId + 1    const yeni = { id: sonId, isim: req.body.isim, rol: req.body.rol }    sampiyonlar.push(yeni)    res.status(201).json(yeni)})
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -X DELETE http://localhost:3000/api/sampiyonlar/2PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body '{"isim": "Lux", "rol": "Büyücü"}' id isim rol-- ---- --- 4 Lux  Büyücü PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar[{"id":1,"isim":"Ahri","rol":"Büyücü"},{"id":3,"isim":"Jinx","rol":"Nişancı"},{"id":4,"isim":"Lux","rol":"Büyücü"}]

Kural: bir kimlik bir kez verilir; sahibi silinse bile yeniden kullanılmaz. Blok 5'teki veritabanları da kimlikleri böyle sayar.

Sık hataGET /api/sampiyonlar/yeni 400 alıyor: route sırası

Ne zaman çıkar? Sabit bir yolu (/yeni), aynı yeri tutan bir yol parametresinin (/:id) altına yazınca. Diyelim ki son eklenen şampiyonu getiren bir uç nokta yazdın ve onu dosyanın sonuna, app.listen'ın üstüne ekledin:

sunucu.js✗ Yanlış
app.get("/api/sampiyonlar/yeni", (req, res) => {    if (sampiyonlar.length === 0) {        return res.status(404).json({ hata: "Hiç şampiyon yok" })    }    res.json(sampiyonlar[sampiyonlar.length - 1])})
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/api/sampiyonlar/yeniHTTP/1.1 400 Bad RequestX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 35ETag: W/"23-gtdj5v6Jadv3BG5qf5783lhsjWw"Date: Sun, 11 Oct 2026 18:10:38 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"hata":"id bir tam sayı olmalı"}

Yanıt /yeni route'undan değil, sampiyonuBul'dan geldi. Express route'ları yukarıdan aşağı dener ve ilk uyanı çalıştırır. /api/sampiyonlar/:id daha yukarıda ve yeni sözcüğü de :id'ye uyuyor: req.params.id "yeni" oldu, sayıya çevrilemedi.

Yanlış sıra Doğru sıra GET /api/sampiyonlar/yeni GET /api/sampiyonlar/yeni /api/sampiyonlar uymaz /api/sampiyonlar/:id uyar: 400 /api/sampiyonlar/yeni sıra gelmez /api/sampiyonlar uymaz /api/sampiyonlar/yeni uyar: 200 /api/sampiyonlar/:id sıra gelmez Yukarıdan aşağı denenir; ilk uyan route çalışır, ötekilere sıra gelmez.
Şekil 28.3 Express route'ları yazıldığı sırayla dener. Sabit yol, aynı yeri tutan yol parametresinden önce yazılmalı.

Nasıl düzelir? /yeni route'unu /api/sampiyonlar/:id'nin üstüne taşı; en iyisi liste route'unun hemen altı. Sabit yollar önce, yol parametreli yollar sonra. Kaydedince sunucu yeniden başlar ve liste ilk üç şampiyona döner; bu yüzden aynı istek şimdi {"id":3,"isim":"Jinx","rol":"Nişancı"} döndürür. Lux'u yeniden eklersen {"id":4,"isim":"Lux","rol":"Büyücü"} gelir.

Doğrulama ve 400

Kimlik sorunu çözüldü, ama {} hâlâ adsız bir şampiyon ekliyor. Ders 23'te formdaki required niteliğinin F12 ile birkaç saniyede silinebildiğini görmüştün. Sunucu tarayıcıdaki denetime güvenemez: kuralları kendisi yeniden denetler ve uymayan isteği 400 ile geri çevirir. Bunun adını Ders 13'ten biliyorsun: girdi doğrulama.

Kuralları route'un içine gömmek yerine ayrı bir modüle koy; hem POST hem PATCH kullanacak. Proje klasöründe dogrula.js aç. Altına dört deneme satırı ekledik; çalıştırıp sonucu gör:

dogrula.js
const roller = ["Büyücü", "Dövüşçü", "Nişancı", "Suikastçı", "Destek", "Tank"] export function sampiyonuDogrula(veri) {    if (typeof veri !== "object" || veri === null) {        return ["Gövde bir JSON objesi olmalı"]    }    const hatalar = []    if (typeof veri.isim !== "string" || veri.isim.trim() === "") {        hatalar.push("isim boş olmayan bir metin olmalı")    } else if (veri.isim.trim().length > 20) {        hatalar.push("isim en çok 20 karakter olabilir")    }    if (!roller.includes(veri.rol)) {        hatalar.push(`rol şunlardan biri olmalı: ${roller.join(", ")}`)    }    return hatalar} console.log(sampiyonuDogrula({ isim: "Lux", rol: "Büyücü" }))console.log(sampiyonuDogrula({ isim: "   ", rol: "Aşçı" }))console.log(sampiyonuDogrula({ isim: "Yirmi Bir Karakterlik", rol: "Tank" }))console.log(sampiyonuDogrula(undefined))
Çıktı
[][  'isim boş olmayan bir metin olmalı',  'rol şunlardan biri olmalı: Büyücü, Dövüşçü, Nişancı, Suikastçı, Destek, Tank'][ 'isim en çok 20 karakter olabilir' ][ 'Gövde bir JSON objesi olmalı' ]
  • Fonksiyon hata mesajlarından oluşan bir dizi döndürüyor; boş dizi "sorun yok" demek. Bütün sorunları birden döndürmek, istemcinin eksiklerini tek istekte görmesini sağlar.
  • İlk denetim gövdenin bir obje olduğundan emin olur; gövde yoksa (undefined) ya da null ise ötekilere hiç geçmez. Ötekiler veri.isim okuduğu için bu sıra önemli.
  • trim (Ders 9) yalnız boşluktan oluşan adı da boş sayar. Rol, izin verilen listeden biri olmalı.

node dogrula.js ile çalıştır, sonra alttaki dört console.log satırını sil. sunucu.js'in en üstüne içe aktarmayı ekle:

sunucu.js
import express from "express"import { sampiyonuDogrula } from "./dogrula.js"

POST ve PATCH'i fonksiyonu kullanacak biçimde değiştir:

sunucu.js
app.post("/api/sampiyonlar", (req, res) => {    const hatalar = sampiyonuDogrula(req.body)    if (hatalar.length > 0) {        return res.status(400).json({ hata: "Geçersiz şampiyon", ayrintilar: hatalar })    }    sonId = sonId + 1    const yeni = { id: sonId, isim: req.body.isim.trim(), rol: req.body.rol }    sampiyonlar.push(yeni)    res.status(201).json(yeni)}) app.patch("/api/sampiyonlar/:id", sampiyonuBul, (req, res) => {    const guncel = {        isim: req.body?.isim ?? req.sampiyon.isim,        rol: req.body?.rol ?? req.sampiyon.rol    }    const hatalar = sampiyonuDogrula(guncel)    if (hatalar.length > 0) {        return res.status(400).json({ hata: "Geçersiz şampiyon", ayrintilar: hatalar })    }    req.sampiyon.isim = guncel.isim.trim()    req.sampiyon.rol = guncel.rol    res.json(req.sampiyon)})

PATCH önce şampiyonun güncellenmiş hâlini guncel adlı yeni bir objede kurar, onu POST'un kurallarıyla denetler ve ancak geçerse kaydı değiştirir. Böylece tek bir alanı bozuk bir istek, kaydın yarısını değiştirip yarısında kalamaz. Gövdeden yalnız isim ve rol alınıyor; başka alanlar, örneğin bir id, sessizce yok sayılır.

İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i --json "{}" http://localhost:3000/api/sampiyonlarHTTP/1.1 400 Bad RequestX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 176ETag: W/"b0-4RGaJgG80pjWgiwh0u/qBPFK8d4"Date: Sun, 11 Oct 2026 18:28:25 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"hata":"Geçersiz şampiyon","ayrintilar":["isim boş olmayan bir metin olmalı","rol şunlardan biri olmalı: Büyücü, Dövüşçü, Nişancı, Suikastçı, Destek, Tank"]}PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -Body '{"isim": "Teemo", "rol": "Nişancı"}'Invoke-RestMethod : {"hata":"Geçersiz şampiyon","ayrintilar":["Gövde bir JSON objesi olmalı"]}...PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body '{"isim": "Yirmi Karakterlik Ad", "rol": "Tank"}' id isim                 rol-- ----                 --- 4 Yirmi Karakterlik Ad Tank PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body '{"isim": "Yirmi Bir Karakterlik", "rol": "Tank"}'Invoke-RestMethod : {"hata":"Geçersiz şampiyon","ayrintilar":["isim en çok 20 karakter olabilir"]}...PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Patch -Uri http://localhost:3000/api/sampiyonlar/1 -ContentType "application/json; charset=utf-8" -Body '{"isim": ""}'Invoke-RestMethod : {"hata":"Geçersiz şampiyon","ayrintilar":["isim boş olmayan bir metin olmalı"]}...PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Patch -Uri http://localhost:3000/api/sampiyonlar/1 -ContentType "application/json; charset=utf-8" -Body '{"id": 99, "rol": "Destek"}' id isim rol-- ---- --- 1 Ahri Destek

Sınırlar yerinde: 20 karakter geçti, 21 geçmedi. -ContentType'ı unutan istek artık 500 değil, ne yapması gerektiğini söyleyen bir 400 aldı. Invoke-RestMethod hata yanıtlarında gövdeyi ilk satıra yazar (Ders 26). Express yanıtları Content-Length taşıdığı için, Ders 27'deki sunucunun aksine, bu gövde burada görünüyor.

Sık hataError [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

Ne zaman çıkar? Bir isteğe yanıt gönderdikten sonra ikinci kez göndermeye kalkınca. En sık sebep: 400'ü gönderen satırdaki return'ü unutmak.

sunucu.js✗ Yanlış
app.post("/api/sampiyonlar", (req, res) => {    const hatalar = sampiyonuDogrula(req.body)    if (hatalar.length > 0) {        res.status(400).json({ hata: "Geçersiz şampiyon", ayrintilar: hatalar })    }    sonId = sonId + 1    const yeni = { id: sonId, isim: req.body.isim.trim(), rol: req.body.rol }    sampiyonlar.push(yeni)    res.status(201).json(yeni)})
İkinci terminal
PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body '{"isim": "Vi", "rol": "Aşçı"}'Invoke-RestMethod : {"hata":"Geçersiz şampiyon","ayrintilar":["rol şunlardan biri olmalı: Büyücü, Dövüşçü, Nişancı, Suikastçı, Destek, Tank"]}...PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar[{"id":1,"isim":"Ahri","rol":"Büyücü"},{"id":2,"isim":"Garen","rol":"Dövüşçü"},{"id":3,"isim":"Jinx","rol":"Nişancı"},{"id":4,"isim":"Vi","rol":"Aşçı"}]
Sunucunun terminali
POST /api/sampiyonlar 201 19 msError [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client    at ServerResponse.setHeader (node:_http_outgoing:684:11)    ...    at file:///C:/kod/sampiyon-api/sunucu.js:69:21    ... {  code: 'ERR_HTTP_HEADERS_SENT'}

İstemci 400 aldı; her şey yolunda görünüyor. Ama fonksiyon durmadı: geçersiz rollü Vi listeye eklendi ve sunucu.js:69'daki ikinci yanıt "başlıklar gönderildikten sonra yeniden yazılamaz" hatasına çarptı. Kayıt satırı bile 201 diyor, çünkü ikinci res.status(201) durum kodunu değiştirmişti. Sunucu bu kez çökmedi (Express hatayı yakaladı), ama veri bozuldu. Hatanın en kötüsü, kimsenin fark etmediğidir.

Nasıl düzelir? Yanıt gönderen her dalın başına return koy: return res.status(400).json(...). Kural Ders 27'dekiyle aynı: bir istek, bir yanıt.

404 işleyicisi ve hata yakalayıcı

İki tür yanıtın hâlâ JSON değil. Bilinmeyen adreslere Cannot GET sayfası gidiyor; bozuk gövdeye ve koddaki hatalara yığın izli HTML. API'ni kullanan program HTML beklemiyor; üstelik yığın izi, sunucundaki dosya yollarını isteyen herkese gösteriyor. İkisini de zincirin sonuna iki ara katman koyarak düzeltirsin.

404 işleyicisi

Bütün route'ların altına, app.listen'ın üstüne yaz:

sunucu.js
app.use((req, res) => {    res.status(404).json({ hata: `${req.method} ${req.url} diye bir yol yok` })})

Yol vermediğin için her isteğe uyar, ama zincirin sonunda durduğu için yalnız hiçbir route'un yanıtlamadığı istekler buraya ulaşır. next parametresine gerek yok: işi her zaman yanıt göndermek.

İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/yokHTTP/1.1 404 Not FoundX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 36ETag: W/"24-Yk1YQavXOxV3xkZq9K3h0LCf0JE"Date: Sun, 11 Oct 2026 18:11:51 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"hata":"GET /yok diye bir yol yok"}PS C:\kod\sampiyon-api> curl.exe -X DELETE http://localhost:3000/api/sampiyonlar{"hata":"DELETE /api/sampiyonlar diye bir yol yok"}
Sık hataPathError [TypeError]: Missing parameter name at index 1: *

Ne zaman çıkar? İnternetteki Express 4 örneklerinde 404 için sık görülen app.get("*", ...), app.all("*", ...) ya da app.use("*", ...) satırını Express 5'e yazınca. Sunucu daha açılmadan çöker:

sunucu.js✗ Yanlış
app.all("*", (req, res) => {    res.status(404).json({ hata: `${req.method} ${req.url} diye bir yol yok` })})
Terminal — PowerShell
PS C:\kod\sampiyon-api> node sunucu.jsC:\kod\sampiyon-api\node_modules\path-to-regexp\dist\index.js:108                    throw new PathError(`Missing parameter name at index ${index}`, str);                          ^ PathError [TypeError]: Missing parameter name at index 1: *; visit https://git.new/pathToRegexpError for info    at consumeUntil (C:\kod\sampiyon-api\node_modules\path-to-regexp\dist\index.js:108:27)    at parse (C:\kod\sampiyon-api\node_modules\path-to-regexp\dist\index.js:140:26)    ...    at app.all (C:\kod\sampiyon-api\node_modules\express\lib\application.js:495:20) {  originalPath: '*'} Node.js v24.20.0

Express 5 yol yazımını değiştirdi: * artık tek başına kullanılamaz, bir adı olmalı. Yığın izinde kendi dosyan görünmüyor; Node yalnız ilk on satırı gösterir ve hepsi Express'in içinde. Hangi satırı yazdığını sondaki originalPath: '*' söylüyor. Aynı değişiklik isteğe bağlı parametreleri de etkiledi: Express 4'teki /api/sampiyonlar/:id? artık Unexpected ? at index 20 hatası verir; Express 5'teki yazımı /api/sampiyonlar{/:id}.

Nasıl düzelir? 404 için yola hiç gerek yok: app.use((req, res) => { ... }), yukarıdaki gibi. Gerçekten "her şeye uyan" bir route gerekirse Express 5'te *'a ad verilir: app.get("/*yol", ...). Bu yazım kök adrese (/) uymaz; onu da kapsasın istersen süslü paranteze al: app.get("/{*yol}", ...).

Hata yakalayıcı

Bozuk bir gövde gönder (curl.exe --json "Lux" http://localhost:3000/api/sampiyonlar) ve sunucunun terminaline bak:

Sunucunun terminali
POST /api/sampiyonlar 400 2 msSyntaxError: Unexpected token 'L', "Lux" is not valid JSON    at JSON.parse (<anonymous>)    at createStrictSyntaxError (C:\kod\sampiyon-api\node_modules\body-parser\lib\types\json.js:137:10)    ...    at endReadableNT (node:internal/streams/readable:1757:12) {  expose: true,  statusCode: 400,  status: 400,  body: 'Lux',  type: 'entity.parse.failed'}

express.json() gövdeyi okuyamadı ve bir hata fırlattı. Hatanın altındaki alanlara bak: status: 400 "suç istemcide" demek. expose: true ise "bu mesajı istemciye göstermek güvenli" (expose, İngilizcede "açığa çıkarmak"). Express'in kendi ara katmanları, istemcinin bozuk isteğinden doğan hatalara bu iki alanı koyar. Fırlayan her hata normal zincirden çıkar ve bir hata yakalayıcı arar; sen yazmadıysan Express'in varsayılanı HTML sayfasını gönderir. 404 işleyicisinin altına kendininkini yaz:

sunucu.js
app.use((hata, req, res, next) => {    if (hata.expose) {        return res.status(hata.status).json({ hata: "İstek okunamadı", ayrinti: hata.message })    }    console.error(hata)    res.status(500).json({ hata: "Sunucuda bir hata oluştu" })})
  • Dört parametre şart. Express, fonksiyonun kaç parametre aldığına bakarak hata yakalayıcıyı tanır. next'i kullanmasan da silme; üç parametreyle yazılan fonksiyon sıradan bir ara katman sayılır ve hatalar onun yanından geçip yine Express'in HTML sayfasına düşer.
  • hata.expose doğruysa hata istemcinin isteğinden doğdu ve mesajında sunucuna dair bir sır yok: hatanın kendi kodunu (hata.status, ör. 400) ve mesajını istemciye ver.
  • Yoksa hata senin kodundan çıktı: bir TypeError, yanlış yazılmış bir ad. Ayrıntısını console.error ile terminale yaz; istemciye yalnız genel bir cümle ve 500 gönder. Dosya yollarını ve kod satırlarını dışarı vermek, saldırgana sunucunun haritasını vermektir (Ders 31).
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i --json "Lux" http://localhost:3000/api/sampiyonlarHTTP/1.1 400 Bad RequestX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 88ETag: W/"58-MrUthV+q7X5yvoqfPTkr+RzAh4Q"Date: Sun, 11 Oct 2026 18:11:52 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"hata":"İstek okunamadı","ayrinti":"Unexpected token 'L', \"Lux\" is not valid JSON"}PS C:\kod\sampiyon-api> (Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body ('{"isim": "' + ('A' * 200000) + '"}')).idInvoke-RestMethod : {"hata":"İstek okunamadı","ayrinti":"request entity too large"}...

İkinci istek Ders 27'deki 200.000 harflik gövde. express.json() varsayılan olarak 100 kB'tan büyük gövdeyi okumaz ve 413 ile reddeder: kötü niyetli biri sunucunun belleğini dev bir gövdeyle dolduramasın diye. Kayıt satırı POST /api/sampiyonlar 413 1 ms yazar.

async route'larda hata

Blok 5'te route'ların içinde veritabanını await ile bekleyeceksin. async bir route'ta fırlayan hataya ne olduğunu şimdiden gör. 404 işleyicisinin üstüne geçici bir deneme route'u ekle:

sunucu.js
app.get("/api/hata-deneme", async (req, res) => {    throw new Error("Bilerek fırlatılan hata")})
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/api/hata-denemeHTTP/1.1 500 Internal Server ErrorX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 36ETag: W/"24-TsKPxtO6xJFSG+mdyLejsJ81Lpo"Date: Sun, 11 Oct 2026 18:11:52 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"hata":"Sunucuda bir hata oluştu"}
Sunucunun terminali
Error: Bilerek fırlatılan hata    at file:///C:/kod/sampiyon-api/sunucu.js:92:11    at Layer.handleRequest (C:\kod\sampiyon-api\node_modules\router\lib\layer.js:152:17)    ...GET /api/hata-deneme 500 1 ms

İstemci genel cümleyi aldı, ayrıntı terminale düştü, sunucu ayakta. Express 5, async bir fonksiyonun reddedilen Promise'ini (Ders 15) kendisi yakalayıp hata yakalayıcıya iletir. Express 4'ten farkı: aynı kodu Express 4 ile denediğimizde sunucu Error: Bilerek fırlatılan hata yazıp kapandı, curl.exe de Recv failure: Connection was reset aldı. İnternetteki eski örneklerde her async route'u saran try/catch ve next(hata) satırlarının sebebi bu; Express 5'te onlara gerek yok. Denedikten sonra deneme route'unu sil.

Statik dosyalar: express.static

API'nin yanında bir web sayfası da sunmak için Express'in hazır ara katmanı yeter. Proje klasöründe public adında bir klasör aç ve içine iki dosya koy. Sayfa Ders 19 ve 22'de öğrendiklerinden ibaret:

public/index.html
<!doctype html><html lang="tr"><head>    <meta charset="utf-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <title>Şampiyonlar</title>    <style>        body { font-family: system-ui, sans-serif; max-width: 420px; margin: 24px auto; padding: 0 16px; }        li { padding: 6px 0; border-bottom: 1px solid #ddd; }    </style></head><body>    <h1>Şampiyonlar</h1>    <p id="durum">Yükleniyor...</p>    <ul id="liste"></ul>    <script src="uygulama.js"></script></body></html>
public/uygulama.js
const durum = document.querySelector("#durum")const liste = document.querySelector("#liste") async function yukle() {    try {        const yanit = await fetch("/api/sampiyonlar")        if (!yanit.ok) {            throw new Error(`İstek başarısız: ${yanit.status}`)        }        const sampiyonlar = await yanit.json()        durum.textContent = `${sampiyonlar.length} şampiyon`        for (const s of sampiyonlar) {            const satir = document.createElement("li")            satir.textContent = `${s.isim} (${s.rol})`            liste.append(satir)        }    } catch (hata) {        durum.textContent = "Liste yüklenemedi"    }} yukle()

Bu dosya tarayıcıda çalışacak, Node'da değil; yapısı Ders 24'teki fetch kodunun aynısı. sunucu.js'te app.use(express.json())'ın altına tek satır ekle ve artık gereksiz olan app.get("/") route'unu sil:

sunucu.js
app.use(express.json())app.use(express.static("public"))

express.static("public") bir ara katman üretir: istenen yolu public klasöründe arar, dosya varsa türünü bulup gönderir, yoksa next() der. / istenince klasördeki index.html'i verir.

Tarayıcıda localhost:3000'i aç:

localhost:3000
Tarayıcıda Şampiyonlar başlıklı sayfa: altında 3 şampiyon yazısı ve Ahri (Büyücü), Garen (Dövüşçü), Jinx (Nişancı) satırlarından oluşan liste.
Gerçek ekran görüntüsü: liste, sayfa açıldıktan sonra API'den geldi.
Sunucunun terminali
GET / 200 15 msGET /uygulama.js 200 2 msGET /api/sampiyonlar 200 3 ms

Tek sayfa, üç istek: sayfanın kendisi, betiği ve betiğin istediği JSON. Hepsi aynı zincirden geçti; ilk ikisini express.static, üçüncüyü route'un yanıtladı. Tarayıcın sekme simgesi için /favicon.ico da isterse o istek 404 işleyicisine düşer. Sayfa ve API aynı kökenden (localhost:3000) geldiği için Ders 24'teki CORS engeli de yok. Ders 25'teki statik site gibi, bu dosyalar olduğu gibi gönderiliyor; değişen tek şey, artık onları kendi sunucunun vermesi.

İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i http://localhost:3000/HTTP/1.1 200 OKX-Powered-By: ExpressAccept-Ranges: bytesCache-Control: public, max-age=0Last-Modified: Sun, 11 Oct 2026 18:13:51 GMTETag: W/"20d-1a12c2bd226"Content-Type: text/html; charset=utf-8Content-Length: 525Date: Sun, 11 Oct 2026 18:14:39 GMTConnection: keep-aliveKeep-Alive: timeout=5 <!doctype html>...

Content-Type'ı dosyanın uzantısından buldu; Last-Modified dosyanın son kaydedilme zamanı. Tarih ve uzunluklar sende farklı çıkar.

API'yi terminalden test etmek

Bir API'yi "çalışıyor" saymadan önce her uç noktasını hem doğru hem yanlış isteklerle denersin. Bunun için bir test planı yaz: istek ve beklenen durum kodu. Sonra sırayla at ve gerçek kodu beklenenle karşılaştır.

İstekBeklenen
GET /api/sampiyonlar200, dizi
GET /api/sampiyonlar?ara=a200, süzülmüş dizi
GET /api/sampiyonlar/2 · /9 · /abc200 · 404 · 400
POST geçerli gövde · {} · bozuk JSON201 · 400 · 400
PATCH /api/sampiyonlar/1 geçerli · geçersiz rol200 · 400
DELETE /api/sampiyonlar/4 · aynısı bir daha204 · 404
GET /yok · PUT /api/sampiyonlar/1404 · 404

Bir CRUD turu, Invoke-RestMethod ile:

İkinci terminal
PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body '{"isim": "Lux", "rol": "Büyücü"}' id isim rol-- ---- --- 4 Lux  Büyücü PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Patch -Uri http://localhost:3000/api/sampiyonlar/4 -ContentType "application/json; charset=utf-8" -Body '{"rol": "Destek"}' id isim rol-- ---- --- 4 Lux  Destek PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Delete -Uri http://localhost:3000/api/sampiyonlar/4 PS C:\kod\sampiyon-api> Invoke-RestMethod http://localhost:3000/api/sampiyonlar/4Invoke-RestMethod : {"hata":"4 numaralı şampiyon yok"}...

Invoke-RestMethod yalnız gövdeyi gösterir; 201 ile 200'ü ayırt edemezsin. Durum kodunu görmek için iki yol:

İkinci terminal
PS C:\kod\sampiyon-api> (Invoke-WebRequest -Method Post -Uri http://localhost:3000/api/sampiyonlar -ContentType "application/json; charset=utf-8" -Body '{"isim": "Teemo", "rol": "Nişancı"}' -UseBasicParsing).StatusCode201PS C:\kod\sampiyon-api> (Invoke-WebRequest -Method Delete -Uri http://localhost:3000/api/sampiyonlar/5 -UseBasicParsing).StatusCode204PS C:\kod\sampiyon-api> curl.exe -s -o NUL -w "%{http_code}" http://localhost:3000/api/sampiyonlar/5404
  • Invoke-WebRequest (Ders 26) bütün yanıtı verir; .StatusCode yalnız kodu alır. 4xx ya da 5xx gelince o da hata fırlatır.
  • curl.exe -s -o NUL -w "%{http_code}": -s ilerleme yazılarını susturur, -o NUL gövdeyi Windows'un "hiçbir yer" dosyasına atar, -w yalnız durum kodunu yazar. Test planındaki her satırı tek kodla denetlemenin en kısa yolu.

Geçerli JSON'u curl.exe ile göndermek için Ders 27'deki gibi bir dosya kullan. Proje klasöründe lux.json aç, içine { "isim": "Lux", "rol": "Büyücü" } yaz:

İkinci terminal
PS C:\kod\sampiyon-api> curl.exe -i --json "@lux.json" http://localhost:3000/api/sampiyonlarHTTP/1.1 201 CreatedX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 39ETag: W/"27-TkW3sOdzMBr5oo1/GjtbgYR4+m0"Date: Sun, 11 Oct 2026 18:28:44 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"id":6,"isim":"Lux","rol":"Büyücü"}

id 6 oldu: 4 ve 5 silinmiş olsa da yeniden verilmedi. Sunucunun son hâli, karşılaştırmak için:

sunucu.js
import express from "express"import { sampiyonuDogrula } from "./dogrula.js" const app = express()const port = 3000 let sampiyonlar = [    { id: 1, isim: "Ahri", rol: "Büyücü" },    { id: 2, isim: "Garen", rol: "Dövüşçü" },    { id: 3, isim: "Jinx", rol: "Nişancı" }]let sonId = 3 app.use((req, res, next) => {    const baslangic = Date.now()    res.on("finish", () => {        const sure = Date.now() - baslangic        console.log(`${req.method} ${req.url} ${res.statusCode} ${sure} ms`)    })    next()})app.use(express.json())app.use(express.static("public")) app.get("/api/sampiyonlar", (req, res) => {    const { ara } = req.query    if (typeof ara !== "string") {        return res.json(sampiyonlar)    }    const aranan = ara.toLowerCase()    res.json(sampiyonlar.filter((s) => s.isim.toLowerCase().includes(aranan)))}) app.get("/api/sampiyonlar/yeni", (req, res) => {    if (sampiyonlar.length === 0) {        return res.status(404).json({ hata: "Hiç şampiyon yok" })    }    res.json(sampiyonlar[sampiyonlar.length - 1])}) function sampiyonuBul(req, res, next) {    const id = Number(req.params.id)    if (!Number.isInteger(id)) {        return res.status(400).json({ hata: "id bir tam sayı olmalı" })    }    const sampiyon = sampiyonlar.find((s) => s.id === id)    if (!sampiyon) {        return res.status(404).json({ hata: `${id} numaralı şampiyon yok` })    }    req.sampiyon = sampiyon    next()} app.get("/api/sampiyonlar/:id", sampiyonuBul, (req, res) => {    res.json(req.sampiyon)}) app.post("/api/sampiyonlar", (req, res) => {    const hatalar = sampiyonuDogrula(req.body)    if (hatalar.length > 0) {        return res.status(400).json({ hata: "Geçersiz şampiyon", ayrintilar: hatalar })    }    sonId = sonId + 1    const yeni = { id: sonId, isim: req.body.isim.trim(), rol: req.body.rol }    sampiyonlar.push(yeni)    res.status(201).json(yeni)}) app.patch("/api/sampiyonlar/:id", sampiyonuBul, (req, res) => {    const guncel = {        isim: req.body?.isim ?? req.sampiyon.isim,        rol: req.body?.rol ?? req.sampiyon.rol    }    const hatalar = sampiyonuDogrula(guncel)    if (hatalar.length > 0) {        return res.status(400).json({ hata: "Geçersiz şampiyon", ayrintilar: hatalar })    }    req.sampiyon.isim = guncel.isim.trim()    req.sampiyon.rol = guncel.rol    res.json(req.sampiyon)}) app.delete("/api/sampiyonlar/:id", sampiyonuBul, (req, res) => {    sampiyonlar = sampiyonlar.filter((s) => s.id !== req.sampiyon.id)    res.status(204).end()}) app.use((req, res) => {    res.status(404).json({ hata: `${req.method} ${req.url} diye bir yol yok` })}) app.use((hata, req, res, next) => {    if (hata.expose) {        return res.status(hata.status).json({ hata: "İstek okunamadı", ayrinti: hata.message })    }    console.error(hata)    res.status(500).json({ hata: "Sunucuda bir hata oluştu" })}) app.listen(port, (hata) => {    if (hata) {        throw hata    }    console.log(`Sunucu çalışıyor: http://localhost:${port}`)})

105 satır: Ders 27'deki 69 satırın yaptığından çok daha fazlasını yapıyor ve satırların çoğu şampiyonlarla ilgili. Gövdeyi okumak, adresi parçalarına ayırmak, başlıkları yazmak gibi her API'de aynı olan işleri Express üstlendi.

Görevler

Görevleri aynı projede yap; sunucu npm run dev ile açık kalsın. Şampiyon route'larına dokunmayacaksın: görevlerin kaynağı dükkândaki eşyalar. Yeni route'ları 404 işleyicisinin üstüne yaz, yoksa onlara hiç sıra gelmez. Her adımdan sonra istekleri curl.exe ile at ve durum kodunu denetle.

Görev 1

Eşya listesi, tek eşya ve kaldırmak

Şu üç eşyayla bir esyalar dizisi kur ve üç uç nokta yaz: GET /api/esyalar hepsini, GET /api/esyalar/:id tek eşyayı döndürsün, DELETE /api/esyalar/:id eşyayı dükkândan kaldırıp gövdesiz 204 dönsün. Tam sayı olmayan id'ye 400, olmayan eşyaya 404 dönsün.

Metin
1, Uzun Kılıç, 350 altın2, Büyü Kitabı, 435 altın3, Çevik Botlar, 300 altın
İkinci terminal
PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/esyalar/3{"id":3,"isim":"Çevik Botlar","fiyat":300}PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/esyalar/4{"hata":"4 numaralı eşya yok"}PS C:\kod\sampiyon-api> curl.exe -s -o NUL -w "%{http_code}" -X DELETE http://localhost:3000/api/esyalar/3204PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/esyalar/3{"hata":"3 numaralı eşya yok"}
İpucu 1

Her eşya bir obje: { id: 1, isim: "Uzun Kılıç", fiyat: 350 }. Silerken diziyi filter'ın verdiği yeni diziyle değiştireceğin için dizi let ile açılsın; şampiyon dizisinin yanında ya da eşya route'larının hemen üstünde dursun.

İpucu 2

sampiyonuBul olduğu gibi işe yaramaz, çünkü sampiyonlar dizisinde arıyor. Onun ikizini yaz: esyayiBul, bulduğunu req.esya'ya koysun. Tek eşyayı getiren ve silen iki route da onu kullanır.

Çözümü göster
JavaScript
let esyalar = [    { id: 1, isim: "Uzun Kılıç", fiyat: 350 },    { id: 2, isim: "Büyü Kitabı", fiyat: 435 },    { id: 3, isim: "Çevik Botlar", fiyat: 300 }] function esyayiBul(req, res, next) {    const id = Number(req.params.id)    if (!Number.isInteger(id)) {        return res.status(400).json({ hata: "id bir tam sayı olmalı" })    }    const esya = esyalar.find((e) => e.id === id)    if (!esya) {        return res.status(404).json({ hata: `${id} numaralı eşya yok` })    }    req.esya = esya    next()} app.get("/api/esyalar", (req, res) => {    res.json(esyalar)}) app.get("/api/esyalar/:id", esyayiBul, (req, res) => {    res.json(req.esya)}) app.delete("/api/esyalar/:id", esyayiBul, (req, res) => {    esyalar = esyalar.filter((e) => e.id !== req.esya.id)    res.status(204).end()})

Sınır değerleriyle sonuçlar (sunucu yeni başlamışken, sırayla):

  • GET /api/esyalar → 200, üç eşya
  • GET /api/esyalar/3 → 200, {"id":3,"isim":"Çevik Botlar","fiyat":300} (son eşya)
  • GET /api/esyalar/4 → 404, {"hata":"4 numaralı eşya yok"} (sonun bir fazlası)
  • GET /api/esyalar/0 → 404, {"hata":"0 numaralı eşya yok"}
  • GET /api/esyalar/abc ve GET /api/esyalar/2.5 → 400, {"hata":"id bir tam sayı olmalı"}
  • DELETE /api/esyalar/3 → 204; aynısı bir daha → 404; DELETE /api/esyalar/abc → 400
  • Silmeden sonra GET /api/esyalar → [{"id":1,"isim":"Uzun Kılıç","fiyat":350},{"id":2,"isim":"Büyü Kitabı","fiyat":435}]
Görev 2

Dükkân vitrini

public klasörüne dukkan.html ve dukkan.js ekle. Tarayıcıda localhost:3000/dukkan.html açılınca sayfa /api/esyalar'dan eşyaları çekip Uzun Kılıç: 350 altın biçiminde listelesin, üstte kaç eşya olduğunu yazsın. Eşya hiç yoksa liste yerine Dükkân boş yazsın.

İpucu 1

Şampiyon sayfasının iki dosyası iyi bir başlangıç: adresi, metinleri ve öğe adlarını değiştir. sunucu.js'e dokunman gerekmiyor; express.static public'teki her dosyayı zaten veriyor.

İpucu 2

Boş durumu esyalar.length === 0 ile yakala, yazıyı koy ve return ile fonksiyondan çık. Denemek için esyalar dizisini geçici olarak [] yap.

Çözümü göster
public/dukkan.html
<!doctype html><html lang="tr"><head>    <meta charset="utf-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <title>Dükkân</title></head><body>    <h1>Dükkân</h1>    <p id="durum">Yükleniyor...</p>    <ul id="esyalar"></ul>    <script src="dukkan.js"></script></body></html>
public/dukkan.js
const durum = document.querySelector("#durum")const liste = document.querySelector("#esyalar") async function yukle() {    try {        const yanit = await fetch("/api/esyalar")        if (!yanit.ok) {            throw new Error(`İstek başarısız: ${yanit.status}`)        }        const esyalar = await yanit.json()        if (esyalar.length === 0) {            durum.textContent = "Dükkân boş"            return        }        durum.textContent = `${esyalar.length} eşya`        for (const e of esyalar) {            const satir = document.createElement("li")            satir.textContent = `${e.isim}: ${e.fiyat} altın`            liste.append(satir)        }    } catch (hata) {        durum.textContent = "Eşyalar yüklenemedi"    }} yukle()
localhost:3000/dukkan.html
Dükkân başlıklı sayfa: 3 eşya yazısı ve Uzun Kılıç 350 altın, Büyü Kitabı 435 altın, Çevik Botlar 300 altın satırları.
Gerçek ekran görüntüsü. Diziyi [] yapınca başlığın altında yalnız Dükkân boş yazısı kalıyor.

Sunucunun terminalinde üç satır görünür: GET /dukkan.html 200, GET /dukkan.js 200, GET /api/esyalar 200.

Görev 3

Bakım modu

Bir ara katman yaz: const bakimModu = true iken GET dışındaki her istek 503 Service Unavailable ve şu gövdeyi alsın; GET istekleri eskisi gibi çalışsın. bakimModu false olunca her şey normale dönsün.

Metin
{"hata":"Bakım var, şimdilik yalnız okuma yapılabilir"}

Sonra şu soruyu cevapla: bakım varken POST /yok isteği 404 mü alır, 503 mü? Neden?

İpucu 1

Ara katman req.method'a bakar. Koşul tutarsa return res.status(503).json(...), tutmazsa next().

İpucu 2

Yeri önemli: route'lardan önce çalışmalı. Kayıt tutucunun hemen altı iyi bir yer; böylece reddedilen istekler de deftere girer.

Çözümü göster
JavaScript
const bakimModu = true app.use((req, res, next) => {    if (bakimModu && req.method !== "GET") {        return res.status(503).json({ hata: "Bakım var, şimdilik yalnız okuma yapılabilir" })    }    next()})

const bakimModu dosyanın üstünde, ara katman kayıt tutucunun altında. Bakım açıkken:

Terminal — PowerShell
PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/sampiyonlar/1{"id":1,"isim":"Ahri","rol":"Büyücü"}PS C:\kod\sampiyon-api> curl.exe -i --json "@lux.json" http://localhost:3000/api/sampiyonlarHTTP/1.1 503 Service UnavailableX-Powered-By: ExpressContent-Type: application/json; charset=utf-8Content-Length: 59ETag: W/"3b-ri54xRF/BTrkighCBs2JJQwDxSM"Date: Sun, 11 Oct 2026 18:18:24 GMTConnection: keep-aliveKeep-Alive: timeout=5 {"hata":"Bakım var, şimdilik yalnız okuma yapılabilir"}PS C:\kod\sampiyon-api> curl.exe -X DELETE http://localhost:3000/api/sampiyonlar/1{"hata":"Bakım var, şimdilik yalnız okuma yapılabilir"}PS C:\kod\sampiyon-api> curl.exe -X POST http://localhost:3000/yok{"hata":"Bakım var, şimdilik yalnız okuma yapılabilir"}PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/yok{"hata":"GET /yok diye bir yol yok"}

POST /yok 503 aldı: bakım ara katmanı zincirde 404 işleyicisinden çok önce duruyor ve isteği orada yanıtlıyor. GET /yok ise ondan geçti ve 404'e ulaştı. Bozuk JSON'lu bir POST da 400 değil 503 alır, çünkü express.json()'dan önce reddediliyor. bakimModu'nu false yapıp kaydedince POST yine 201 döner.

Görev 4

Eşya ekle ve güncelle

İki uç nokta daha yaz:

  1. POST /api/esyalar: gövdedeki isim ve fiyat ile yeni bir eşya oluşturup 201 ile döndürsün. Kurallar: isim boş olmayan bir metin; fiyat 0 ile 5000 arasında (ikisi dahil) bir tam sayı. Kurallara uymayan istek, şampiyonlardaki gibi { hata, ayrintilar } gövdesiyle 400 alsın. Kimliği dizinin uzunluğundan değil, ayrı bir sayaçtan ver: Görev 1'de silmeyi eklediğin için esyalar.length + 1 kaldırılmış bir eşyanın kimliğini yeniden verebilir.
  2. PATCH /api/esyalar/:id: gövdede gelen alanları (isim, fiyat ya da ikisi) değiştirip güncel eşyayı 200 ile döndürsün. Güncel hâl de aynı kurallara uymalı; uymuyorsa eşya hiç değişmeden 400 dönsün. Fiyatı 0 yapmak, yani eşyayı bedava yapmak da çalışmalı.
İpucu 1

dogrula.js'e esyayiDogrula(veri) adında ikinci bir fonksiyon ekle ve export et. İçe aktarırken iki adı birden yazabilirsin: import { sampiyonuDogrula, esyayiDogrula } from "./dogrula.js".

İpucu 2

Number.isInteger(veri.fiyat) hem metni ("500") hem küsuratı (12.5) reddeder; Number'a çevirmeden doğrudan sor. Bedava eşya (0) geçmeli: !veri.fiyat yazarsan 0'ı da reddedersin (Ders 12).

İpucu 3

Kimlik için sonId'nin ikizi: let sonEsyaId = 3. PATCH için şampiyonların PATCH'ini örnek al: önce guncel objesini kur, esyayiDogrula ile denetle, sonra eşyayı değiştir. Eski değeri korurken ?? kullan, || değil: || bedava eşyanın 0'ını "yok" sayar ve eski fiyatı geri koyar.

Çözümü göster

Önce fonksiyonu tek başına dene. dogrula.js'in sonuna, bir boş satır bırakıp ekle; deneme satırlarını sonra sil:

dogrula.js
export function esyayiDogrula(veri) {    if (typeof veri !== "object" || veri === null) {        return ["Gövde bir JSON objesi olmalı"]    }    const hatalar = []    if (typeof veri.isim !== "string" || veri.isim.trim() === "") {        hatalar.push("isim boş olmayan bir metin olmalı")    }    if (!Number.isInteger(veri.fiyat) || veri.fiyat < 0 || veri.fiyat > 5000) {        hatalar.push("fiyat 0 ile 5000 arasında bir tam sayı olmalı")    }    return hatalar} console.log(esyayiDogrula({ isim: "Bedava Bilet", fiyat: 0 }))console.log(esyayiDogrula({ isim: "Ölüm Şapkası", fiyat: 5000 }))console.log(esyayiDogrula({ isim: "Altın Kaşık", fiyat: 5001 }))console.log(esyayiDogrula({ isim: "", fiyat: "500" }))
Çıktı
[][][ 'fiyat 0 ile 5000 arasında bir tam sayı olmalı' ][  'isim boş olmayan bir metin olmalı',  'fiyat 0 ile 5000 arasında bir tam sayı olmalı']

Sonra sunucu.js'te eşya dizisinin altına let sonEsyaId = 3, eşya route'larının arasına da şu ikisi:

JavaScript
app.post("/api/esyalar", (req, res) => {    const hatalar = esyayiDogrula(req.body)    if (hatalar.length > 0) {        return res.status(400).json({ hata: "Geçersiz eşya", ayrintilar: hatalar })    }    sonEsyaId = sonEsyaId + 1    const yeni = { id: sonEsyaId, isim: req.body.isim.trim(), fiyat: req.body.fiyat }    esyalar.push(yeni)    res.status(201).json(yeni)}) app.patch("/api/esyalar/:id", esyayiBul, (req, res) => {    const guncel = {        isim: req.body?.isim ?? req.esya.isim,        fiyat: req.body?.fiyat ?? req.esya.fiyat    }    const hatalar = esyayiDogrula(guncel)    if (hatalar.length > 0) {        return res.status(400).json({ hata: "Geçersiz eşya", ayrintilar: hatalar })    }    req.esya.isim = guncel.isim.trim()    req.esya.fiyat = guncel.fiyat    res.json(req.esya)})

Sunucu yeni başlamışken sırayla gönderdiğimiz istekler:

  • DELETE /api/esyalar/3, sonra POST ile {"isim": "Bedava Bilet", "fiyat": 0} → 201, {"id":4,"isim":"Bedava Bilet","fiyat":0}. Kaldırılan Çevik Botlar'ın 3'ü yeniden verilmedi; esyalar.length + 1 olsaydı yeni eşya 3 numarayı alırdı.
  • POST ile {"isim": "Ölüm Şapkası", "fiyat": 5000} → 201, id 5
  • POST ile {"isim": "Altın Kaşık", "fiyat": 5001}, {"isim": "Borçlu Kalkan", "fiyat": -1}, {"isim": "Metin Fiyat", "fiyat": "500"} ve {"isim": "Küsuratlı", "fiyat": 12.5} → 400, ["fiyat 0 ile 5000 arasında bir tam sayı olmalı"]
  • POST ile {"fiyat": 100} → 400, ["isim boş olmayan bir metin olmalı"]
  • PATCH /api/esyalar/1 ile {"fiyat": 0} → 200, {"id":1,"isim":"Uzun Kılıç","fiyat":0}
  • PATCH /api/esyalar/1 ile {"isim": " Kısa Kılıç "} → 200, {"id":1,"isim":"Kısa Kılıç","fiyat":0}: boşluklar kırpıldı, fiyat korundu
  • PATCH /api/esyalar/1 ile {"fiyat": 5001}, {"fiyat": "300"} ya da {"isim": " "} → 400; PATCH /api/esyalar/9 → 404

Sınırlar tam yerinde: 0 ve 5000 geçiyor, -1 ve 5001 geçmiyor. ?? yerine || yazsaydın {"fiyat": 0} isteği hata vermeden {"id":1,"isim":"Uzun Kılıç","fiyat":350} döndürürdü: çalışıyor görünen ama yanlış bir güncelleme.

Görev 5

Ucuz eşyalar ve en ucuzu

İki özellik ekle:

  1. GET /api/esyalar?enFazla=400 yalnız fiyatı 400 ya da daha az olan eşyaları döndürsün. enFazla yoksa hepsi gelsin; 0 ya da daha büyük bir tam sayı değilse 400 dönsün.
  2. GET /api/esyalar/en-ucuz en ucuz eşyayı döndürsün; dükkânda eşya yoksa 404.
Terminal — PowerShell
PS C:\kod\sampiyon-api> curl.exe "http://localhost:3000/api/esyalar?enFazla=300"[{"id":3,"isim":"Çevik Botlar","fiyat":300}]PS C:\kod\sampiyon-api> curl.exe http://localhost:3000/api/esyalar/en-ucuz{"id":3,"isim":"Çevik Botlar","fiyat":300}
İpucu 1

req.query.enFazla metin olarak gelir. Number ile çevir, Number.isInteger ile denetle. Dikkat: Number("") sonucu 0; ?enFazla= boş geldiğinde bunu ayrıca yakala.

İpucu 2

En ucuzu bulmak Ders 5'teki "kemer" yöntemi: ilk eşyayı aday say, ötekileri tek tek onunla karşılaştır, daha ucuzsa aday o olsun.

İpucu 3

/en-ucuz 400 alıyorsa route sırasına bak: /api/esyalar/:id'nin üstünde olmalı.

Çözümü göster
JavaScript
app.get("/api/esyalar", (req, res) => {    const { enFazla } = req.query    if (enFazla === undefined) {        return res.json(esyalar)    }    const sinir = Number(enFazla)    if (enFazla === "" || !Number.isInteger(sinir) || sinir < 0) {        return res.status(400).json({ hata: "enFazla 0 ya da daha büyük bir tam sayı olmalı" })    }    res.json(esyalar.filter((e) => e.fiyat <= sinir))}) app.get("/api/esyalar/en-ucuz", (req, res) => {    if (esyalar.length === 0) {        return res.status(404).json({ hata: "Dükkânda eşya yok" })    }    let enUcuz = esyalar[0]    for (const e of esyalar) {        if (e.fiyat < enUcuz.fiyat) {            enUcuz = e        }    }    res.json(enUcuz)})

Liste route'unu Görev 1'deki yerinde değiştirdik; /en-ucuz onun altında, /api/esyalar/:id'nin üstünde. Sınır değerleriyle:

  • ?enFazla=300 → Çevik Botlar (300 dahil); ?enFazla=299 → []; ?enFazla=0 → []
  • ?enFazla=435 → üç eşya
  • ?enFazla=-5, ?enFazla=abc, ?enFazla= ve ?enFazla=2.5 → 400, {"hata":"enFazla 0 ya da daha büyük bir tam sayı olmalı"}
  • /api/esyalar/en-ucuz → Çevik Botlar. Görev 4'teki Bedava Bilet eklendikten sonra ise {"id":4,"isim":"Bedava Bilet","fiyat":0}.
Görev 6

Hata avı: dükkân sunucusu

Aşağıdaki sunucunun dört hatası ve bir eksiği var. Proje klasöründe dukkan-sunucu.js adıyla kaydet. VS Code'un terminal panelindeki + düğmesiyle üçüncü bir terminal aç ve orada node --watch dukkan-sunucu.js ile çalıştır; istekleri her zamanki ikinci terminalden at. 3001 numaralı kapıyı kullandığı için ana sunucunla çakışmaz. Hataları gözle aramak yerine belirtilerden yola çık.

dukkan-sunucu.js✗ Yanlış
import express from "express" const app = express()const port = 3001 let altin = 500const esyalar = [    { id: 1, isim: "Uzun Kılıç", fiyat: 350 },    { id: 2, isim: "Büyü Kitabı", fiyat: 435 }] app.get("/api/altin", (req, res) => {    res.json({ altin: altin })}) app.get("/api/esyalar/:id", (req, res) => {    const esya = esyalar.find((e) => e.id === req.params.id)    if (!esya) {        return res.status(404).json({ hata: "Böyle bir eşya yok" })    }    res.json(esya)}) app.post("/api/satin-al", (req, res) => {    const esya = esyalar.find((e) => e.id === req.body.id)    if (!esya) {        return res.status(404).json({ hata: "Böyle bir eşya yok" })    }    if (altin < esya.fiyat) {        res.status(400).json({ hata: "Altın yetmiyor" })    }    altin = altin - esya.fiyat    res.json({ alinan: esya.isim, kalanAltin: altin })}) app.all("*", (req, res) => {    res.status(404).json({ hata: "Böyle bir yol yok" })}) app.listen(port, (hata) => {    if (hata) {        throw hata    }    console.log(`Dükkân çalışıyor: http://localhost:${port}`)})

Belirtiler, bulunuş sırasıyla:

  1. Sunucu açılmıyor: PathError [TypeError]: Missing parameter name at index 1: *.
  2. curl.exe http://localhost:3001/api/esyalar/1 şunu döndürüyor: {"hata":"Böyle bir eşya yok"}.
  3. Invoke-RestMethod -Method Post -Uri http://localhost:3001/api/satin-al -ContentType "application/json; charset=utf-8" -Body '{"id": 1}' 500 alıyor; sunucunun terminalinde TypeError: Cannot read properties of undefined (reading 'id').
  4. Kılıcı aldıktan sonra (150 altın kalır) kitabı almaya çalışınca istemci {"hata":"Altın yetmiyor"} alıyor, ama terminalde ERR_HTTP_HEADERS_SENT var ve /api/altin {"altin":-285} diyor.

Düzeltince 500 altınla kılıç alınabilmeli, kitap alınamamalı ve altın asla eksiye düşmemeli. Son olarak eksiği tamamla: dükkânın hata yakalayıcısı yok. Bozuk gövdeli bir istek (curl.exe --json "Lux" http://localhost:3001/api/satin-al) ve gövdesiz bir istek (curl.exe -X POST http://localhost:3001/api/satin-al) yığın izli HTML yerine JSON almalı.

İpucu 1

Her belirti bu derste bir hata kartına karşılık geliyor: eski yol yazımı, metin ile sayı, eksik ara katman ve unutulan return.

İpucu 2

1: app.all("*", ...) yerine yolsuz app.use. 2: req.params.id metin. 3: req.body neden undefined? 4: 400'den sonra fonksiyon devam ediyor.

İpucu 3

Hata yakalayıcıyı sunucu.js'teki gibi yaz: dört parametre, 404 işleyicisinin altında. Bozuk JSON'un hatası expose taşır ve 400 olur. Gövdesiz istekte ise req.body undefined kalır; req.body.id bir TypeError fırlatır ve o 500 olur.

Çözümü göster

Dört düzeltme ve bir ekleme: const app = express()'in altına app.use(express.json()); e.id === Number(req.params.id); return res.status(400).json({ hata: "Altın yetmiyor" }); 404 için yolsuz app.use((req, res) => { ... }); ve onun altına hata yakalayıcı. Düzeltilmiş dosya:

dukkan-sunucu.js✓ Doğru
import express from "express" const app = express()const port = 3001 app.use(express.json()) let altin = 500const esyalar = [    { id: 1, isim: "Uzun Kılıç", fiyat: 350 },    { id: 2, isim: "Büyü Kitabı", fiyat: 435 }] app.get("/api/altin", (req, res) => {    res.json({ altin: altin })}) app.get("/api/esyalar/:id", (req, res) => {    const esya = esyalar.find((e) => e.id === Number(req.params.id))    if (!esya) {        return res.status(404).json({ hata: "Böyle bir eşya yok" })    }    res.json(esya)}) app.post("/api/satin-al", (req, res) => {    const esya = esyalar.find((e) => e.id === req.body.id)    if (!esya) {        return res.status(404).json({ hata: "Böyle bir eşya yok" })    }    if (altin < esya.fiyat) {        return res.status(400).json({ hata: "Altın yetmiyor" })    }    altin = altin - esya.fiyat    res.json({ alinan: esya.isim, kalanAltin: altin })}) app.use((req, res) => {    res.status(404).json({ hata: "Böyle bir yol yok" })}) app.use((hata, req, res, next) => {    if (hata.expose) {        return res.status(hata.status).json({ hata: "İstek okunamadı", ayrinti: hata.message })    }    console.error(hata)    res.status(500).json({ hata: "Sunucuda bir hata oluştu" })}) app.listen(port, (hata) => {    if (hata) {        throw hata    }    console.log(`Dükkân çalışıyor: http://localhost:${port}`)})
İkinci terminal
PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3001/api/satin-al -ContentType "application/json; charset=utf-8" -Body '{"id": 1}' alinan     kalanAltin------     ----------Uzun Kılıç        150 PS C:\kod\sampiyon-api> Invoke-RestMethod -Method Post -Uri http://localhost:3001/api/satin-al -ContentType "application/json; charset=utf-8" -Body '{"id": 2}'Invoke-RestMethod : {"hata":"Altın yetmiyor"}...PS C:\kod\sampiyon-api> curl.exe http://localhost:3001/api/altin{"altin":150}PS C:\kod\sampiyon-api> curl.exe http://localhost:3001/api/esyalar/1{"id":1,"isim":"Uzun Kılıç","fiyat":350}PS C:\kod\sampiyon-api> curl.exe http://localhost:3001/yok{"hata":"Böyle bir yol yok"}PS C:\kod\sampiyon-api> curl.exe --json "Lux" http://localhost:3001/api/satin-al{"hata":"İstek okunamadı","ayrinti":"Unexpected token 'L', \"Lux\" is not valid JSON"}PS C:\kod\sampiyon-api> curl.exe -X POST http://localhost:3001/api/satin-al{"hata":"Sunucuda bir hata oluştu"}

Sınır: altın tam yetiyorsa alım kabul edilmeli, çünkü koşul altin < esya.fiyat. Başlangıç altınını 785 yapınca (350 + 435) iki eşya da alınır ve {"altin":0} kalır. Bir tuzak daha: -Body '{"id": "1"}' 404 alır, çünkü JSON'daki tırnaklı "1" metindir. Gövdedeki JSON türleri korur; sayı bekliyorsan sayı gönderilmeli.

Son istekteki 500 aslında hâlâ bir eksik: hatalı olan istemci, sunucu değil. req.body?.id yazarsan aynı istek 404 alır; en doğrusu gövdeyi şampiyonlardaki gibi doğrulayıp 400 döndürmek.

Kendini sına

Soru 1

GET /api/sampiyonlar/7 isteğinde bu satır ne yazar?

JavaScript
console.log(typeof req.params.id, req.params.id === 7)
Cevabı göster

string false. Adresten gelen her değer metindir: req.params.id "7", sayı 7 değil. === türlere de baktığı için karşılaştırma false olur. Önce Number(req.params.id) ile çevirmek gerekir.

Soru 2

Kayıt tutucu bu sırayla yazıldı. GET /api/sampiyonlar ve GET /yok isteklerinden hangisi terminale yazılır?

JavaScript
app.get("/api/sampiyonlar", (req, res) => {    res.json(sampiyonlar)}) app.use((req, res, next) => {    console.log(req.method, req.url)    next()})
Cevabı göster

Yalnız GET /yok. Express zinciri yazıldığı sırayla dener. /api/sampiyonlar isteğini yukarıdaki route yanıtladı ve next() demedi; istek kayıt tutucuya hiç ulaşmadı. /yok'a uyan route olmadığı için istek aşağı indi ve deftere yazıldı. Her isteği görmesi gereken ara katmanlar route'ların üstüne yazılır.

Soru 3

Hangi durum kodunu döndürürsün? (a) Silme başarılı. (b) PATCH'te rol "Aşçı". (c) Olmayan bir şampiyonu silmek. (d) Yeni şampiyon eklendi. (e) Route'un içinde bir TypeError fırladı.

Cevabı göster

(a) 204, gövde yok. (b) 400, istek kurallara uymuyor. (c) 404. (d) 201 ve yeni kayıt. (e) 500: hata istemcinin değil, sunucunun kodunun. Bu derste hata yakalayıcın, expose taşımayan her hatayı 500'e çeviriyor.

Soru 4

Hata yakalayıcıyı app.use((hata, req, res) => { ... }) diye üç parametreyle yazarsan bozuk bir JSON gönderildiğinde ne olur?

Cevabı göster

Express, fonksiyonun parametre sayısına bakarak hata yakalayıcıyı tanır. Üç parametreli fonksiyon sıradan bir ara katman sayılır ve hatalar onun yanından geçer. Bozuk JSON yine Express'in varsayılan yığın izli HTML sayfasıyla 400 alır. next'i kullanmasan da dördüncü parametre yerinde durmalı.

Soru 5

Doğrulamadan önceki sunucuya {} gövdesi gönderince yanıt neden {"id":5} oldu? isim ve rol nereye gitti?

Cevabı göster

Gövde boş bir objeydi; req.body.isim ve req.body.rol undefined geldi ve yeni kayda öyle yazıldı. res.json veriyi JSON.stringify ile metne çevirir ve değeri undefined olan alanları atlar. Geriye yalnız id kaldı. Hata vermeyen bu kayıt, doğrulamanın neden gerektiğinin kanıtı.

Soru 6

Mesajı oku: ne yanlış, nasıl düzelir?

Hata çıktısı
PathError [TypeError]: Unexpected ? at index 20: /api/sampiyonlar/:id?; visit https://git.new/pathToRegexpError for info
Cevabı göster

Yolun 20. konumunda beklenmeyen bir ? var: /api/sampiyonlar/:id?. Bu, Express 4'te "isteğe bağlı parametre" demekti; Express 5'te bu yazım kaldırıldı ve sunucu açılırken çöküyor. Express 5'te isteğe bağlı kısım süslü paranteze alınır: /api/sampiyonlar{/:id}. Çoğu zaman en temizi iki ayrı route yazmak: biri listeye, biri tek kayda.

Soru 7

public/uygulama.js sayfadan fetch("/api/sampiyonlar") ile istek atıyor, ama Ders 24'teki CORS hatası çıkmıyor. Neden?

Cevabı göster

Sayfa da API de aynı kökenden geliyor: http://localhost:3000. CORS yalnız bir sayfa başka bir kökene istek attığında devreye girer. fetch'e verilen /api/sampiyonlar adresi / ile başladığı için sayfanın geldiği sunucuya gider.

Kayıt noktası

Projeni Git'e kaydet. sampiyon-api yeni bir proje klasörü olduğu için önce git init gerekiyor; komutları ikinci terminalde yaz:

API'n şu an her yeniden başlatmada ilk üç şampiyona dönüyor; bir sonraki derste veriyi dosyaya yazıp kalıcı yapacak, port gibi ayarları ve şifreleri koddan çıkarıp ortam değişkenlerine taşıyacaksın.

İşaret yalnızca bu tarayıcıda saklanır, kimseye gönderilmez.