Mert Özen Her Satırda Daha İleri
Backend Node.js Temelleri — #17

Node.js 22'den 26'ya Gelenler: require(esm), Type Stripping, Permission Model, Temporal ve Yeni Release Takvimi

Mert Özen 14 Kas 2026 14 dk 19 görüntülenme
Node.js 22'den 26'ya Gelenler: require(esm), Type Stripping, Permission Model, Temporal ve Yeni Release Takvimi

Node.js 22'den 26'ya require(esm), type stripping, Permission Model, global URLPattern ve Web Storage geldi; 26 Temporal'ı varsayılan olarak açtı, _stream_* module'lerini ve writeHeader'ı kaldırdı, NODE_MODULE_VERSION'ı 147'ye çıkardı. Gerçek çıktılarla Date ile Temporal'ın farklı teslimat tarihi, --throw-deprecation ile CI'da yakalanan deprecation'lar, Node 24'te derlenip 26'da yüklenemeyen bir addon ve Node 27 ile başlayan yıllık Alpha → Current → LTS takvimi.

Sürüm notu: Bu yazıdaki örnekler Node.js 26.13.1 (LTS) ile çalıştırılmıştır; karşılaştırmalar Node.js 24.22.3 ile, TypeScript kontrolü TypeScript 7.0.2 ve @types/node 26.6.6 ile yapılmıştır.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'da (bir online kitapçı) sipariş sayfası, teslimat tarihini siparişten üç gün sonrası olarak gösteriyor. Ada siparişini İstanbul saatiyle 15 Kasım gece 01.30'da veriyor; sunucu bu anı UTC (eşgüdümlü evrensel saat) olarak tutuyor. Aynı dosyayı Node 24 ve Node 26 ile çalıştırıyoruz:

// Ada'nın sipariş sayfası teslimatı sipariş gününden üç gün sonrası olarak gösteriyor, İstanbul saatiyle
const placedAt = "2026-11-14T22:30:00Z";

// Date yalnız UTC'yi ve makinenin kendi saat dilimini bilir
const viaDate = new Date(placedAt);
viaDate.setUTCDate(viaDate.getUTCDate() + 3);
console.log("Date:    ", viaDate.toISOString().slice(0, 10));

// Temporal: saat dilimi hesabın bir parçası
const viaTemporal = Temporal.Instant.from(placedAt).toZonedDateTimeISO("Europe/Istanbul").toPlainDate().add({ days: 3 });
console.log("Temporal:", viaTemporal.toString());

Node 24'te kaç satır yazar? Node 26'da iki satırın tarihi aynı mı?

fnm exec --using=24.22.3 node labs/17/guess.ts
node labs/17/guess.ts
Date:     2026-11-17
file:///home/user/bookstore-api/labs/17/guess.ts:10
const viaTemporal = Temporal.Instant.from(placedAt).toZonedDateTimeISO("Europe/Istanbul").toPlainDate().add({ days: 3 });
                    ^

ReferenceError: Temporal is not defined
    at file:///home/user/bookstore-api/labs/17/guess.ts:10:21
    ...

Node.js v24.22.3

Date:     2026-11-17
Temporal: 2026-11-18

Node 24'te Temporal yok; process ilk satırdan sonra çöküyor. Node 26'da iki satır farklı: Date UTC'de 14 Kasım'dan saydı, Temporal ise siparişin İstanbul'daki tarihinden, 15 Kasım'dan. Ada'ya gösterilecek doğru tarih 18 Kasım. (İki sürümü ayrı kurulumlarla çalıştırdım; ilk satırdaki fnm exec, 02'de kurduğumuz fnm'de aynı işi yapan komut.) Serinin bu son yazısında Node 22'den 26'ya gelen değişiklikleri topluyoruz: seride kullandığımız özelliklerin hangi sürümde geldiğini, 26'nın yeniliklerini ve kaldırdıklarını, bir yükseltmede nelere bakman gerektiğini ve Node 27 ile başlayan yeni release takvimini.

22'den 26'ya Sürüm Sürüm

Dört major sürümde değişen her şeyi saymak yerine bu seride işimize yarayanları ve bir yükseltmede karşına çıkacakları topladım. Tarihler ve maddeler nodejs.org'daki sürüm notlarından; her major sürüm V8'i (Node'un JavaScript engine'i) de yükseltiyor:

SürümÇıkışÖne çıkanlar
22.0.024 Nisan 2024V8 12.4, --watch stable, node --run, fs.glob, global WebSocket client
22.12.03 Aralık 2024require(esm) bayraksız (LTS hattına geri taşındı)
23.0.016 Ekim 2024require(esm) varsayılan, node --run stable, 32 bit Windows desteği kaldırıldı
24.0.06 Mayıs 2025V8 13.6, npm 11, global URLPattern, --permission, test runner subtest'leri kendisi bekliyor, url.parse() runtime deprecation
25.0.015 Ekim 2025V8 14.1, Web Storage varsayılan açık, --allow-net, corepack dağıtımdan çıktı
26.0.05 Mayıs 2026V8 14.6, Temporal varsayılan açık, undici 8, writeHeader ve _stream_* kaldırıldı, module.register() runtime deprecation

Bunların çoğunu seride zaten kullandık, o yüzden tekrar anlatmıyorum. --watch, node --run ve --env-file 02'de; require(esm)'in 22.12'de bayraksız, 25.4'te stable olması 03'te; type stripping'in 22.18 ve 23.6'da varsayılan, 24.12 ve 25.2'de stable olması ve 26'da --experimental-transform-types'ın kaldırılması 05'te; fs.glob ve Permission Model 08'de; writeHeader'ın kaldırılması 10'da anlatıldı. Permission Model için bir not: doküman modelin 23.5 ve 22.13'te stable olduğunu yazıyor, 24.0'ın sürüm notları ise bayrağın --experimental-permission'dan --permission'a geçtiğini öne çıkarıyor. 25'te gelen --allow-net ise dokümanda hâlâ "Active development" (1.1) seviyesinde; 08'deki "Use --allow-net" hatası bu bayraktan.

Gözden kaçabilecek iki madde daha var. 24 ile AsyncLocalStorage varsayılan olarak AsyncContextFrame adlı yeni implementasyonu kullanıyor; 07'deki request context'i API'si değişmeden çalışıyor, ama AsyncLocalStorage'a dayanan bir APM (uygulama performansını izleyen araç) agent'ı kullanıyorsan 24'e geçişte agent'ın sürümünü kontrol et. 24'te test runner, await edilmeyen subtest'leri de kendisi bekliyor; 16'daki testler bu davranışa dayanıyor.

URLPattern ve Web Storage

24'ten beri URLPattern global. Tarayıcıdaki URL eşleme standardı; path parametrelerini ve regex kısıtlarını tek bir pattern'de veriyorsun:

// URLPattern Node 24'ten beri global: framework olmadan route eşleme
const bookRoute = new URLPattern({ pathname: "/api/books/:isbn(97[89]\\d{10})" });

console.log(bookRoute.test("http://127.0.0.1:3000/api/books/9780441172719"));
console.log(bookRoute.test("http://127.0.0.1:3000/api/books/dune"));
console.log(bookRoute.exec("http://127.0.0.1:3000/api/books/9780441172719?format=json")?.pathname.groups);
true
false
{ isbn: '9780441172719' }

10'da elle yazdığımız routing tablosunu bununla kısaltabilirdik. Express'in kendi path syntax'ı (path-to-regexp 8, 11'de) ile aynı değil; Express kullanırken ikisini karıştırma. URLPattern'in yeri framework'süz küçük servisler, edge fonksiyonları ve tarayıcıyla paylaşılan kod.

25'te tarayıcının localStorage API'si varsayılan olarak açıldı, 26'da da davranışı netleşti: dosya verilmezse localStorage undefined dönüyor.

node -e "console.log(typeof localStorage)"
node --localstorage-file=labs/17/out/ls.json -e "localStorage.setItem('cart', '3'); console.log(localStorage.getItem('cart'))"
undefined
(node:78767) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.
(Use `node --trace-warnings ...` to show where the warning was created)
3

Sunucuda bu API'nin bir tuzağı var: localStorage process başına tek bir depo, kullanıcı başına değil. Ada'nın sepetini oraya yazarsan Alan da aynı sepeti görür. Tarayıcıyla kod paylaşan araçlar ve testler için işe yarar; request verisi için değil.

Node 26: Temporal Varsayılan Olarak Açık

26'nın en görünür yeniliği Temporal. Date'in sorunlarını (0'dan başlayan aylar, değiştirilebilir object, yalnız UTC ve makinenin saat dilimi) çözmek için tasarlanmış yeni tarih/saat API'si; .NET'teki DateTimeOffset, DateOnly ve TimeSpan ayrımına benzer şekilde her kavram için ayrı bir tip var:

// Sipariş zaman çizelgesi için Temporal tipleri: kesin an, takvim tarihi, süre
const placed = Temporal.Instant.from("2026-11-14T22:30:00Z");
const istanbul = placed.toZonedDateTimeISO("Europe/Istanbul");
console.log(istanbul.toString());

// Aylar Date'teki gibi 0'dan değil 1'den başlar
const due = Temporal.PlainDate.from({ year: 2026, month: 11, day: 18 });
console.log(due.dayOfWeek, due.toLocaleString("en-GB", { weekday: "long" }));

// Teslimata kalan gün, takvim günü olarak
const today = Temporal.PlainDate.from("2026-11-15");
console.log(today.until(due).toString(), today.until(due).days);

// Berlin 2027-03-28'de yaz saatine geçiyor: bir gün her zaman 24 saat değil
const berlin = Temporal.ZonedDateTime.from("2027-03-27T12:00[Europe/Berlin]");
console.log(berlin.add({ days: 1 }).toString());
console.log(berlin.add({ hours: 24 }).toString());

// Temporal değerleri değiştirilemez ve JSON'da ISO string olarak yazılır
console.log(JSON.stringify({ dueDate: due, placedAt: placed }));

// Hâlâ Date kullanan kodla birlikte çalışmak
const legacy = new Date("2026-11-14T22:30:00Z");
console.log(legacy.toTemporalInstant().equals(placed));
2026-11-15T01:30:00+03:00[Europe/Istanbul]
3 Wednesday
P3D 3
2027-03-28T12:00:00+02:00[Europe/Berlin]
2027-03-28T13:00:00+02:00[Europe/Berlin]
{"dueDate":"2026-11-18","placedAt":"2026-11-14T22:30:00Z"}
true

Instant zaman çizgisindeki kesin bir an (veritabanındaki UTC değer), ZonedDateTime o anın bir saat dilimindeki karşılığı, PlainDate saatsiz bir takvim tarihi (teslimat günü), Duration ise P3D gibi bir süre. Berlin örneği, "bir gün sonra" ile "24 saat sonra"nın yaz saati geçişinde farklı sonuç verdiğini gösteriyor; Date ile bu farkı elle hesaplaman gerekirdi. Türkiye 2016'dan beri yaz saati uygulamıyor, ama Bookstore'un EUR ile satış yaptığı müşteriler için bu fark gerçek.

API tarafında iki konuya dikkat. JSON'da Temporal değerleri "2026-11-18" gibi standart tarih string'lerine dönüşüyor, ama JSON.parse onları geri Temporal'a çevirmiyor; request body'sinde zod ile z.string().transform((s) => Temporal.PlainDate.from(s)) gibi açık bir dönüşüm gerekiyor. İkincisi, mssql (13) ve pino (14) gibi paketler hâlâ Date bekliyor ya da üretiyor; Temporal'ı domain'in içinde kullan, sınırlarda toTemporalInstant() ve Temporal.Instant.fromEpochMilliseconds() ile çevir.

V8 14.6: getOrInsert ve Iterator.concat

V8 14.6 ile gelen küçük ama günlük kodu kısaltan iki ekleme var. Sipariş satırlarını müşteriye göre gruplayalım:

// V8 14.6 (Node 26): Map.prototype.getOrInsert / getOrInsertComputed ve Iterator.concat
interface Line {
    customerId: string;
    isbn: string;
    quantity: number;
}

const lines: Line[] = [
    { customerId: "ada", isbn: "9780441172719", quantity: 3 },
    { customerId: "alan", isbn: "9780547928227", quantity: 1 },
    { customerId: "ada", isbn: "9780547928227", quantity: 1 },
];

// Önce: get, undefined kontrolü, set
const before = new Map<string, Line[]>();
for (const line of lines) {
    let list = before.get(line.customerId);
    if (!list) {
        list = [];
        before.set(line.customerId, list);
    }
    list.push(line);
}

// Sonra: tek çağrı; factory yalnız key yoksa çalışır
const byCustomer = new Map<string, Line[]>();
for (const line of lines) {
    byCustomer.getOrInsertComputed(line.customerId, () => []).push(line);
}
console.log([...byCustomer].map(([id, l]) => `${id}: ${l.length}`).join(", "));

// getOrInsert hazır bir değer alır: sayı için uygun, ama array literal'i her çağrıda yeniden oluşturulur
const quantities = new Map<string, number>();
for (const line of lines) {
    quantities.set(line.isbn, quantities.getOrInsert(line.isbn, 0) + line.quantity);
}
console.log(Object.fromEntries(quantities));

// TypeScript 7.0 Iterator.concat'i henüz tanımıyor: lib yetişene kadar kendimiz tanımlıyoruz
declare global {
    interface IteratorConstructor {
        concat<T>(...iterables: Iterable<T>[]): IteratorObject<T, undefined, unknown>;
    }
}

// Iterator.concat birden fazla kaynağı yeni bir array oluşturmadan, lazy olarak dolaşır
const tryBooks = ["Dune", "The Hobbit"];
const eurBooks = new Set(["Emma", "Middlemarch"]);
const all = Iterator.concat(tryBooks.values(), eurBooks.values());
console.log(all.take(3).toArray());
ada: 2, alan: 1
{ '9780441172719': 3, '9780547928227': 2 }
[ 'Dune', 'The Hobbit', 'Emma' ]

getOrInsertComputed, C#'taki ConcurrentDictionary.GetOrAdd(key, factory) overload'una benziyor: key yoksa factory'yi çalıştırıp sonucu kaydediyor, varsa olanı dönüyor. getOrInsert'e verilen değer ise her çağrıda hesaplanıyor; [] verirsen key varken de boş bir array oluşur ve çöpe gider. Aynı dosya Node 24'te ilk yeni çağrıda TypeError: byCustomer.getOrInsertComputed is not a function ile duruyor.

Daha ilginç olan, dosyanın ortasındaki declare global bloğu. Node 26 Iterator.concat'i çalıştırıyor, ama TypeScript 7.0.2'nin lib dosyalarında tipi yok; blok olmadan tsc şu hatayı veriyor:

labs/17/collections.ts(42,22): error TS2339: Property 'concat' does not exist on type 'IteratorConstructor'.

Runtime'ın ve TypeScript'in yeni özellikleri aynı anda desteklemesi gerekmiyor; Temporal ve getOrInsert'in tipleri TypeScript 7.0'da var, Iterator.concat'inki yok. Yükseltmeden sonra kullanmak istediğin her yeni API'yi hem node hem tsc ile dene.

Node 26'da Kaldırılanlar ve Deprecation'lar

Yeni özellik eklemek kimseyi bozmaz; yükseltmede sorun çıkaranlar kaldırılan API'ler. 26'da kaldırılanlardan biri, Node'un içindeki stream module'lerine eskiden açık olan _stream_readable, _stream_writable gibi yollar. Bunları kendi kodunda yazmazsın, ama eski bir dependency hâlâ kullanıyor olabilir:

// Node'un iç stream module'lerine hâlâ erişen eski bir dependency
const Readable = require("_stream_readable");
console.log(typeof Readable, Readable === require("node:stream").Readable);
# Node 24.22.3
function true

# Node 26.13.1
node:internal/modules/cjs/loader:1607
  throw err;
  ^

Error: Cannot find module '_stream_readable'
Require stack:
- /home/user/bookstore-api/labs/17/legacy-stream.cjs
    at Module._resolveFilename (node:internal/modules/cjs/loader:1603:15)
    ...
  code: 'MODULE_NOT_FOUND',
  ...

24'te uyarısız çalışıyor; 25'te deprecated oldu, 26'da process MODULE_NOT_FOUND ile 1 koduyla çıkıyor. Böyle bir hatayı production'da değil CI'da görmek için bir önceki sürümdeki deprecation uyarılarını takip etmen gerekir. 26'nın runtime deprecation'a çevirdiği module.register() buna iyi bir örnek:

import { register } from "node:module";

// module.register() ile async customization hook'ları: Node 26'da runtime deprecation
register("data:text/javascript,export async function resolve(s, c, next) { return next(s, c); }", import.meta.url);
console.log("hooks registered");
hooks registered
(node:78731) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
(Use `node --trace-deprecation ...` to show where the warning was created)

Kod çalışmaya devam ediyor, sadece stderr'e bir satır düşüyor; log'larda kimse fark etmez. Yerine önerilen module.registerHooks() senkron çalışıyor, hook'ları module'lerin yüklendiği thread'de çalıştırıyor ve dokümanda "Release candidate" (1.2) seviyesinde. Bu API'yi doğrudan kullanmıyorsan bile, TypeScript ya da coverage için loader kuran bir araç arka planda kullanıyor olabilir.

Uyarıyı hataya çevirmenin yolu --throw-deprecation. 24'ten beri runtime deprecation olan url.parse()'ı kullanan eski bir yardımcıyla deneyelim:

import { parse } from "node:url";

// Eski bir Bookstore yönetim script'indeki yönlendirme yardımcısı
const target = parse("https://shop.example.com/books/9780441172719?ref=mail");
console.log(target.pathname);

// WHATWG URL: deprecation uyarısı vermeyen karşılığı
console.log(new URL("https://shop.example.com/books/9780441172719?ref=mail").pathname);
node --throw-deprecation labs/17/legacy-url.ts
echo "exit $?"
/books/9780441172719
/books/9780441172719
node:internal/process/warning:176
        throw warning;
        ^

DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
    at urlParse (node:url:137:13)
    at file:///home/user/bookstore-api/labs/17/legacy-url.ts:4:16
    ... {
  code: 'DEP0169'
}

Node.js v26.13.1
exit 1

İki satır yazıldıktan sonra process çıktı ve kod 1 oldu: Node'un kaynak kodunda --throw-deprecation uyarıyı hemen değil process.nextTick ile fırlatıyor, bu yüzden o anki senkron kod sonuna kadar çalışıyor. Exit code 1 olduğu için CI job'u kırmızıya döner. Hata mesajındaki DEP0169 gibi kodlar dokümandaki deprecation listesinin anahtarları; CVE (güvenlik açıklarına verilen standart kimlik numarası) cümlesi de url.parse()'ın neden terk edilmesi gerektiğini açıkça söylüyor. CI'da test komutunu NODE_OPTIONS=--throw-deprecation npm test ile çalıştırmak, bütün child process'lere de aynı ayarı geçirir; module.register() örneği bu şekilde de 1 koduyla çıktı.

Native Addon'lar ve NODE_MODULE_VERSION 147

Bir paket C++ ile yazılmış ve V8'in C++ API'sine karşı derlenmiş bir .node dosyası içeriyorsa, o dosya tek bir ABI (Application Binary Interface; derlenmiş kodun birbirine bağlanma kuralları) sürümüne bağlı. Node bu sürümü process.versions.modules ile veriyor:

// Bir yükseltmenin altta değiştirdikleri: engine, libuv, HTTP client ve native addon ABI'si
const { node, v8, uv, undici, modules, napi } = process.versions;
console.log({ node, v8, uv, undici, modules, napi, lts: process.release.lts });
{
  node: '24.22.3',
  v8: '13.6.233.17-node.63',
  uv: '1.52.1',
  undici: '7.30.2',
  modules: '137',
  napi: '10',
  lts: 'Krypton'
}
{
  node: '26.13.1',
  v8: '14.6.202.34-node.40',
  uv: '1.52.1',
  undici: '8.12.4',
  modules: '147',
  napi: '10',
  lts: 'Lithium'
}

modules 137'den 147'ye çıkmış (Node 22'de 127), napi ise 10'da kalmış. Bu farkın ne demek olduğunu görmek için labs/17/addon'a tek fonksiyonluk bir C++ addon koydum ve Node 24 ile derledim:

cd labs/17/addon
fnm exec --using=24.22.3 npm rebuild
node -p "require('./build/Release/hello.node').hello()"
Error: The module '/home/user/bookstore-api/labs/17/addon/build/Release/hello.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 137. This version of Node.js requires
NODE_MODULE_VERSION 147. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
    ...

Node 26 altında npm rebuild çalıştırınca addon yeniden derlendi ve hello yazdı. Gerçek projede bu, Node'u yükselttikten sonra node_modules'u eski sürümden taşımamak, npm ci'yi yeni sürümle çalıştırmak demek. Prebuilt binary dağıtan paketler (bcrypt, sqlite sürücüleri gibi) yeni ABI için binary yayınlamadıysa kaynaktan derlemeye düşer; Windows'ta bu, node-gyp'in istediği Visual Studio C++ build araçlarını ve Python'u gerektirir (bu adımı Windows'ta denemedim). Addon Node-API (node_api.h) ile yazılmışsa sorun yok: doküman Node-API'nin ABI'sinin sürümler arasında sabit olduğunu söylüyor, napi değerinin 10'da kalması da bu yüzden. Bookstore API'sinde native addon yok; 13'teki mssql sürücüsü (tedious) saf JavaScript.

Yeni Release Takvimi: Node 27 ve Sonrası

17-release-timeline-tr.png

01'de kısaca anlattığımız takvim değişikliği, Node 26'yı eski düzenin son sürümü yapıyor. Tarihler nodejs/Release reposundaki schedule.json'dan: 22 (Jod) 30 Nisan 2027'de, 24 (Krypton) 30 Nisan 2028'de, 26 (Lithium) 30 Nisan 2029'da destekten çıkıyor. 24, 20 Ekim 2026'da bakım moduna geçti; artık yalnız güvenlik ve kritik düzeltmeler alıyor. 26, 20 Ekim 2027'ye kadar Active LTS.

Node 27 yeni düzenin ilk sürümü. Alpha kanalı 28 Ekim 2026'da açıldı, 27.0.0'ın 22 Nisan 2027'de Current olarak çıkması, 20 Ekim 2027'de LTS'e geçmesi ve 30 Nisan 2030'da destekten çıkması planlanıyor. Sonrası aynı ritimde: her Ekim bir Alpha, her Nisan bir Current, her Ekim bir LTS, toplam 36 ay destek. Duyuruda 28 için yalnız aylar var (Alpha Ekim 2027, Current Nisan 2028, LTS Ekim 2028, destekten çıkış Nisan 2031) ve takvimin değişebileceği, kesin kaynağın schedule.json olduğu yazıyor. Yani artık tek ve çift numara ayrımı yok, her sürüm LTS olacak ve numara yıla bağlı.

Bunun pratikteki etkisi şu: production'da "yalnız LTS" kuralı değişmiyor, ama artık her yıl bir LTS çıkıyor ve iki LTS arasında atlamak yerine her yıl yükseltmek daha az acılı. Library yazıyorsan CI matrisine Alpha'yı eklemek, breaking change'leri kullanıcılarından önce görmeni sağlar; duyuru da bunu öneriyor. Alpha sürümleri imzalı ve tag'li, ama production için değil.

Üretimde Dikkat

Bookstore API'sini 24'ten 26'ya taşıdığını düşün. Kontrol listesi:

  • .nvmrc, package.json'daki engines ve CI'daki Node sürümü aynı major'ı göstersin (02, 04).
  • @types/node'un major'ı runtime'la aynı olsun: 26 için @types/node 26. Eski tipler yeni API'leri tanımaz, yenileri eski runtime'da olmayan API'lere izin verir.
  • node_modules'u temizleyip npm ci'yi yeni sürümle çalıştır; native addon'lar NODE_MODULE_VERSION 147 için yeniden derlensin ya da prebuilt binary'si gelsin.
  • Bir sürüm önce testleri NODE_OPTIONS=--throw-deprecation ile çalıştır; çıkan her deprecation kodu bir sonraki major'da kaldırılmaya aday.
  • Kaldırılanları ara: _stream_*, writeHeader, --experimental-transform-types, module.register(). npm explain ile hangi dependency'nin getirdiğini bul (04).
  • Container image'ındaki base image tag'ini de değiştir; uygulama 26'da test edilip 24 ile deploy edilmesin (ayrıntısı Docker serisinde).

Yükseltmeyi güvenlik sürümlerinden ayır. 26.13.0'dan 26.13.1'e geçmek, aynı major içinde bir güvenlik düzeltmesi; bunu hemen yap. Major yükseltmeyi ise planlı bir iş olarak, kendi branch'inde ve testleriyle yap.

Sık Düşülen Tuzaklar

Deprecation uyarılarını görmezden gelmek. _stream_readable 25'te deprecated oldu, 26'da yok. Uyarının yazıldığı sürüm, düzeltmen için sana tanınan süre.

Runtime'da olan her şeyin TypeScript'te de olduğunu sanmak. Iterator.concat Node 26'da çalışıyor, TypeScript 7.0.2'de tipi yok.

Temporal'ı library'lerin sınırından geçirmek. Veritabanı sürücüsü ve logger Date bekliyor; çeviriyi sınırda yap, domain'in içine iki tarih tipini karışık sokma.

Sunucuda localStorage'ı kullanıcı başına bir depo sanmak. Process başına tektir.

Node 22'de kalmak. Nisan 2027'de destekten çıkıyor; şimdiden 24 ya da 26'ya geçişi planla.

Küçük Bir Deneme

★☆☆ labs/17/guess.ts'teki sipariş anını "2026-11-14T20:30:00Z" yap. Date ve Temporal sonuçları yine farklı mı? Neden?

★★☆ Bookstore API'sini NODE_OPTIONS=--throw-deprecation ile başlatıp 16'daki testleri bu ayarla çalıştır. Herhangi bir deprecation kodu çıkıyor mu? Çıkıyorsa hangi dependency'den geldiğini --trace-deprecation ile bul.

★★★ OrderDto'ya estimatedDeliveryDate alanı ekle: siparişin İstanbul'daki tarihinden üç iş günü sonrası (Cumartesi ve Pazar sayılmaz), Temporal ile. Pazar günü verilen bir sipariş ve yaz saati geçişi olan bir saat dilimi için test yaz.

Seri burada bitiyor. Runtime modelinden başlayıp module sistemi, npm, build'siz TypeScript, event loop, stream'ler, Express 5, SQL Server, production hazırlığı, güvenlik ve testle, Bookstore'un Node.js tarafını ASP.NET Core'daki karşılıklarıyla birlikte kurduk. Buradan sonraki doğal adım, bu API'yi bir container image'ına koymak ve ölçmek: Docker serisi ve Observability serisi tam bu noktadan devam ediyor.