IndexedDB, tarayıcıda büyük miktarda yapılandırılmış veriyi (nesneler, diziler, tarihler, dosyalar ve blob'lar) anahtarla saklayıp dizinlerle hızlı sorgulamanızı sağlayan, işlem (transaction) tabanlı ve eş zamansız bir veritabanıdır. Kullanımı dört adımdır: indexedDB.open(ad, surum) ile veritabanını açarsınız, upgradeneeded olayında nesne depolarını (object store) ve dizinleri kurarsınız, db.transaction() ile bir işlem başlatıp add, put, get, delete gibi istekler yaparsınız ve sonucu success ile error olaylarından okursunuz.
Bu yazıda MDN'in "Using IndexedDB" kılavuzunu temel alarak veritabanı açmayı, sürüm yükseltmeyi, anahtar yolu ile anahtar üretecini, işlem modlarını ve ömrünü, imleçleri (cursor), dizinleri, anahtar aralıklarını ve başka sekmede açık eski sürümle çakışmayı adım adım anlatıyoruz. Daha küçük ayarlar için localStorage yeterli olabilir; dizinin genel haritası Web API öğrenme rehberi sayfasındadır.
IndexedDB'nin temel özellikleri

IndexedDB ilişkisel bir SQL veritabanı değildir. Tablolar ve sütunlar yerine nesne depoları vardır; her kayıt bir anahtar ve bir değerden oluşur. Değer, yapılandırılmış klonlama (structured clone) algoritmasının kopyalayabildiği her şey olabilir: nesneler, diziler, Date, RegExp, Blob ve dosyalar. Sorgular SQL ile değil, bir dizin veya anahtar aralığı üzerinde açılan imleçlerle yapılır. MDN'in sıraladığı temel özellikler şunlardır:
- İşlem tabanlıdır: Her okuma ve yazma bir işlem içinde olur; işlemin dışında komut çalıştıramaz, imleç açamazsınız. Bu model, aynı uygulamanın iki sekmede açık olduğu durumda değişikliklerin birbirini bozmasını önler.
- Eş zamansızdır: Değer döndürmek yerine bir istek nesnesi (
IDBRequest) verir; sonuç hazır olunca bu nesnede DOM olayı tetiklenir. - Aynı köken politikasına uyar: Her kökenin kendi veritabanları vardır;
http://www.example.comilehttps://www.example.comveya farklı bir port birbirinin verisini göremez. - Nesne yönelimlidir: Kayıtlar düz JavaScript nesneleri olarak saklanır; her nesne deposunun kendi dizinleri olabilir.
MDN sınırları da açıkça yazar: uluslararası sıralama (her dilin harf sırası) desteklenmez, sunucuyla senkronizasyonu sizin yazmanız gerekir ve SQL'deki LIKE gibi bir tam metin araması yoktur. Ayrıca tarayıcı veritabanını şu durumlarda silebilir: kullanıcı site verilerini temizlerse, gizli tarama oturumu biterse, disk veya kota sınırına ulaşılırsa ya da veri bozulursa. Kota ve silme kuralları depolama kotası yazısındadır.
Yapılandırılmış klonlamanın neyi kopyalayıp neyi reddettiğini, aynı algoritmayı kullanan structuredClone() ile görebilirsiniz. Aşağıdaki kod tarayıcıdan bağımsızdır ve Node.js ile çalıştırılıp çıktıları doğrulandı:
const kayit = {
id: 1,
ad: "Ayşe",
eklendi: new Date(0),
etiketler: new Set(["yeni", "vip"]),
};
const kopya = structuredClone(kayit);
console.log(kopya.eklendi instanceof Date); // true
console.log(kopya.etiketler.has("vip")); // true
console.log(kopya === kayit); // false
try {
structuredClone({ ad: "Ayşe", selamla() { return "merhaba"; } });
} catch (e) {
console.log(e.name); // DataCloneError
}localStorage'ın aksine Date ve Set türünü koruyarak kopyalanır; fonksiyon içeren bir nesne ise DataCloneError ile reddedilir. Kayıtlarınızı sınıf örnekleri yerine düz veri olarak tutmanız bu yüzden önemlidir.
Veritabanını açmak
Her şey indexedDB.open() çağrısıyla başlar. Bu çağrı veritabanını hemen açmaz; bir IDBOpenDBRequest nesnesi döndürür ve sonucu olaylarla bildirir. İkinci parametre sürüm numarasıdır ve veritabanının şemasını, yani hangi nesne depolarının ve dizinlerin bulunduğunu temsil eder. Veritabanı yoksa oluşturulur ve upgradeneeded olayı tetiklenir; varsa ve daha yüksek bir sürüm istediyseniz yine upgradeneeded tetiklenir.
let db;
const request = indexedDB.open("MyTestDatabase", 1);
request.onerror = (event) => {
console.error(`Veritabanı açılamadı: ${request.error?.message}`);
};
request.onsuccess = (event) => {
db = event.target.result; // IDBDatabase
};Açma sırasında sık görülen hatalardan biri sürüm hatasıdır: diskteki veritabanının sürümü, açmaya çalıştığınız sürümden büyükse istek hata ile sonuçlanır. Bu, genellikle eski bir kodun önbellekte kalıp yeni şemayı açmaya çalışmasıyla ortaya çıkar; MDN bu durumun hata işleyicisinde mutlaka ele alınmasını önerir. Hata olayları istekten işleme, işlemden de veritabanı nesnesine kabarcıklanır; her isteğe ayrı işleyici eklemek istemiyorsanız tek bir genel işleyici yeterlidir:
db.onerror = (event) => {
// Bu veritabanındaki tüm isteklerin hataları buraya kabarcıklanır
console.error(`Database error: ${event.target.error?.message}`);
};Şemayı kurmak: upgradeneeded, anahtar yolu ve dizinler
upgradeneeded işleyicisi, veritabanının yapısını değiştirebileceğiniz tek yerdir. Nesne deposunu db.createObjectStore(ad, secenekler) ile, dizini store.createIndex(ad, anahtarYolu, secenekler) ile oluşturursunuz. Önceki sürümde oluşturduğunuz depolar zaten vardır; yalnızca yenilerini eklemeniz veya artık gerekmeyenleri silmeniz gerekir. Var olan bir adla depo oluşturmaya ya da olmayan bir depoyu silmeye çalışmak hata fırlatır.
Anahtarın nereden geleceğini iki seçenek belirler. keyPath, anahtarın kaydın hangi özelliğinden okunacağını söyler (satır içi anahtar). autoIncrement: true ise bir anahtar üreteci açar. MDN'deki tabloyu özetlersek:
| keyPath | autoIncrement | Ne saklanabilir, anahtar nereden gelir |
|---|---|---|
| Yok | Hayır | Her tür değer; her eklemede anahtarı ayrı parametre olarak siz verirsiniz |
| Var | Hayır | Yalnızca nesneler; nesnede keyPath adlı özellik bulunmalıdır |
| Yok | Evet | Her tür değer; anahtar otomatik üretilir veya isterseniz ayrıca verirsiniz |
| Var | Evet | Yalnızca nesneler; üretilen anahtar keyPath özelliğine yazılır, özellik zaten varsa onun değeri kullanılır |
Aşağıdaki örnek, MDN'deki müşteri örneğinin uyarlamasıdır. email özelliği id yerine anahtar yolu olarak kullanılmaz ama benzersiz bir dizinle tekrarı önler; ad dizini ise aynı adı taşıyan birden çok kayda izin verir:
const musteriler = [
{ id: "m1", ad: "Ayşe", yas: 35, email: "ayse@example.com" },
{ id: "m2", ad: "Deniz", yas: 32, email: "deniz@example.org" },
];
const request = indexedDB.open("magaza", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// id özelliği her kaydın anahtarıdır
const store = db.createObjectStore("musteriler", { keyPath: "id" });
// Aynı ada sahip birden çok müşteri olabilir
store.createIndex("ad", "ad", { unique: false });
// Aynı e-posta iki kez eklenemesin
store.createIndex("email", "email", { unique: true });
// Şema işlemi bitince ilk verileri yaz
store.transaction.oncomplete = () => {
const yaz = db.transaction("musteriler", "readwrite").objectStore("musteriler");
musteriler.forEach((m) => yaz.add(m));
};
};Dizin tanımladığınız özelliği taşımayan bir nesne eklemek hata vermez; MDN'e göre kayıt başarıyla eklenir ama o dizinde görünmez. Var olan bir deponun keyPath değerini değiştirmek isterseniz depoyu silip yeniden oluşturmanız gerekir; bu işlem içindeki veriyi de siler, o yüzden önce okuyup başka yere kaydetmelisiniz.
Anahtar üretecinin sayacı depo ilk oluşturulduğunda 1'den başlar ve her eklemede artar. MDN'in vurguladığı ayrıntı şudur: sayaç, işlem iptal edilmedikçe asla geri gitmez. Kayıt silmek, hatta depoyu tamamen temizlemek bile sayacı sıfırlamaz. Anahtar olarak string, tarih, sayı, ikili veri (ArrayBuffer veya Uint8Array gibi) ve dizi kullanılabilir; dizi anahtarlar iç içe de olabilir.
İşlemler: modlar, kapsam ve ömür

Veri okumak veya yazmak için önce db.transaction(depoAdlari, mod) ile bir işlem başlatırsınız. Üç mod vardır: readonly, readwrite ve versionchange. Mod belirtmezseniz işlem readonly açılır. versionchange modunu siz doğrudan açmazsınız; daha yüksek sürümle open() çağırdığınızda tarayıcı bu işlemi upgradeneeded içinde size verir.
Kapsam, işlemin erişebileceği depo listesidir ve işlem boyunca değişmez; işlem yalnızca listede verdiğiniz depoları objectStore() ile açmanıza izin verir. MDN iki performans ipucu verir: kapsamı yalnızca gerçekten gereken depolarla sınırlayın ki çakışmayan işlemler aynı anda çalışabilsin, ve readwrite modunu yalnızca gerektiğinde kullanın. Birbiriyle çakışan çok sayıda readonly işlem aynı anda yürüyebilir, ama bir depo için aynı anda yalnızca bir readwrite işlem çalışır; diğerleri sıraya girer.
İşlemin ömrü olay döngüsüne sıkı sıkıya bağlıdır. İşlemi oluşturup üzerinde hiç istek yapmadan olay döngüsüne dönerseniz işlem pasif hale gelir. İşlemi canlı tutmanın tek yolu üzerinde istek yapmaktır; bir isteğin success işleyicisinde yeni bir istek yaparak süreyi uzatabilirsiniz. Bekleyen istek kalmadığında işlem kendiliğinden tamamlanır (auto-commit). Bu yüzden işlemin ortasında fetch() gibi ilgisiz bir eş zamansız işi await ile beklerseniz, döndüğünüzde işlem çoktan kapanmış olabilir. Bu zamanlama kurallarının arka planı için olay döngüsü yazısına bakabilirsiniz.
İşlemler error, abort ve complete olaylarını alır. Bir istekte hata olduğunda varsayılan davranış işlemin tamamen geri alınmasıdır; hatayı yakalayıp olayda stopPropagation() çağırmazsanız tüm değişiklikler iptal edilir ve işlemde abort tetiklenir. Hata olmazsa tüm istekler bittikten sonra complete gelir. Çok sayıda işlem yaptığınızda tek tek istekleri değil işlemin complete olayını izlemek işinizi kolaylaştırır.
Ekleme, okuma, güncelleme ve silme
add(), aynı anahtarla kayıt yoksa ekler; varsa hata verir. İsteğin result değeri eklenen kaydın anahtarıdır. put() ise kayıt varsa üzerine yazar, yoksa ekler; "ekle ya da güncelle" ihtiyacında put() kullanılır.
const tx = db.transaction(["musteriler"], "readwrite");
tx.oncomplete = () => console.log("Tüm kayıtlar yazıldı");
tx.onerror = () => console.error(tx.error);
const store = tx.objectStore("musteriler");
const istek = store.add({ id: "m3", ad: "Ece", yas: 28, email: "ece@example.com" });
istek.onsuccess = (event) => {
// event.target.result eklenen kaydın anahtarıdır: "m3"
};Tek kaydı anahtarla okumak için get() kullanırsınız. Yalnızca okuyacağınız için mod vermenize gerek yoktur; tek depo kullanıyorsanız depo adını dizi yerine string olarak da geçebilirsiniz:
db.transaction("musteriler")
.objectStore("musteriler")
.get("m1").onsuccess = (event) => {
const musteri = event.target.result; // kayıt yoksa undefined
console.log(musteri?.ad);
};Güncellemede kalıp "oku, değiştir, put() ile geri yaz" şeklindedir ve hepsi aynı readwrite işlem içinde olmalıdır:
const store = db.transaction(["musteriler"], "readwrite").objectStore("musteriler");
store.get("m1").onsuccess = (event) => {
const veri = event.target.result;
veri.yas = 36;
store.put(veri).onsuccess = () => {
// güncellendi
};
};
// Silme
db.transaction(["musteriler"], "readwrite").objectStore("musteriler").delete("m2");Bir depodaki tüm kayıtları almak için getAll(), yalnızca anahtarları almak için getAllKeys() vardır. MDN bir performans notu ekler: imlecin value özelliği nesneyi tembel (lazy) oluşturur; getAll() ise tüm nesneleri bir kerede oluşturur. Yalnızca anahtarlara bakacaksanız imleç daha verimlidir; tüm nesneleri bir diziye almak istiyorsanız getAll() kullanın.
İmleç ile dolaşmak
Anahtarı bilmediğiniz durumda veya kayıtları sırayla işlemek istediğinizde openCursor() kullanılır. İmleç isteğinin result değeri imleç nesnesinin kendisidir; key ve value özellikleri o anki kaydı verir. Devam etmek için continue() çağırırsınız. Sona ulaşıldığında yine success tetiklenir ama result boştur; döngüyü bu kontrolle bitirirsiniz.
const store = db.transaction("musteriler").objectStore("musteriler");
store.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log(`${cursor.key}: ${cursor.value.ad}`);
cursor.continue();
} else {
console.log("Kayıtlar bitti");
}
};Dizinler ve anahtar aralıkları
Anahtar dışındaki bir özelliğe göre arama yapmak için dizini kullanırsınız. store.index("ad").get("Deniz") o adı taşıyan kaydı verir; dizin benzersiz değilse ve birden çok eşleşme varsa en düşük anahtarlı kayıt döner. Tüm eşleşmeleri almak için dizin üzerinde imleç açarsınız. Normal imleç (openCursor) tüm kaydı value içinde verir; anahtar imleci (openKeyCursor) ise yalnızca dizin değerini (key) ve kaydın birincil anahtarını (primaryKey) verir.
const index = db.transaction("musteriler").objectStore("musteriler").index("ad");
index.get("Deniz").onsuccess = (event) => {
console.log(event.target.result?.email);
};
index.openKeyCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
// cursor.key ad, cursor.primaryKey kaydın id değeridir
console.log(cursor.key, cursor.primaryKey);
cursor.continue();
}
};Dizin birden çok özellik üzerinde de kurulabilir: createIndex("ad_email", ["ad", "email"]) gibi bir bileşik dizin oluşturur, sorgularken değerleri aynı sırayla dizi olarak verirsiniz. Sonuçları daraltmak için IDBKeyRange kullanılır; bunu openCursor() veya openKeyCursor() çağrısının ilk parametresine verirsiniz. İkinci parametre yönü belirler: varsayılan artan sıradır, "prev" azalan sıradır, "nextunique" ve "prevunique" ise benzersiz olmayan dizinlerde tekrar eden değerleri atlar.
IDBKeyRange.only("Deniz"); // yalnızca "Deniz"
IDBKeyRange.lowerBound("Ayşe"); // "Ayşe" ve sonrası
IDBKeyRange.lowerBound("Ayşe", true); // "Ayşe" hariç sonrası
IDBKeyRange.upperBound("Deniz", true); // "Deniz" hariç öncesi
const aralik = IDBKeyRange.bound("Ayşe", "Deniz", false, true); // Ayşe dahil, Deniz hariç
index.openCursor(aralik, "prev").onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log(cursor.value.ad);
cursor.continue();
}
};
// Yön değiştirip aralık vermemek için ilk parametre null olur
store.openCursor(null, "prev");Başka sekmede eski sürüm açıkken yükseltme
Yeni bir sürümle open() çağırdığınızda, aynı veritabanını açık tutan diğer bağlantıların kapanması gerekir. Kullanıcının eski sürüm kodunu çalıştıran bir sekmesi açık kalmışsa yeni sekmedeki istekte blocked olayı tetiklenir ve yükseltme bekler. MDN'in önerdiği çözüm, her bağlantıda versionchange olayını dinleyip bağlantıyı kapatmak ve kullanıcıya sayfayı yenilemesini söylemektir:
const openReq = indexedDB.open("magaza", 2);
openReq.onblocked = () => {
// Başka bir sekme veritabanını hâlâ açık tutuyor
console.log("Lütfen bu sitenin açık olduğu diğer sekmeleri kapatın.");
};
openReq.onsuccess = (event) => {
const db = event.target.result;
db.onversionchange = () => {
db.close();
console.log("Sayfanın yeni sürümü hazır, lütfen yenileyin.");
};
};Kapatma kodu yoksa yükseltme, kullanıcı eski sekmeyi kapatana kadar gerçekleşmez. MDN ayrıca, açık kalan eski uygulamaların güncel olmayan bir sürümle yeniden açma denemesine karşı VersionError hatalarını da dinlemenizi önerir.
Tarayıcı kapanırken veri tutarlılığı
Kullanıcı tarayıcıyı kapattığında, veritabanının bulunduğu disk çıkarıldığında veya izinler kaybolduğunda etkilenen tüm işlemler AbortError ile iptal edilir, bağlantı kapanır ve IDBDatabase nesnesinde close olayı tetiklenir. Bu yüzden MDN iki kural koyar. Birincisi, her işlemin sonunda veritabanı tutarlı bir durumda olmalıdır: bir listeyi "önce depoyu temizle, sonra yeni listeyi yaz" şeklinde kaydediyorsanız iki adımı ayrı işlemlere bölmeyin, aksi halde arada kapanan tarayıcı boş bir veritabanı bırakır. İkincisi, işlemleri unload olaylarına bağlamayın; tarayıcı kapanırken başlatılan eş zamansız işlemler tamamlanmadan iptal edilir. Değişiklikleri kullanıcı etkileşimi sırasında kaydedin. Sayfa yaşam döngüsü olayları için DOMContentLoaded ve sayfa yaşam döngüsü yazısına bakabilirsiniz.
Olayları promise ile sarmalamak
IndexedDB olay tabanlıdır, ama küçük bir yardımcıyla isteklerin sonucunu await ile bekleyebilirsiniz. Aşağıdaki işlev yalnızca MDN'de anlatılan onsuccess, onerror, result ve error üyelerini kullanır. İşlem ömrü kuralını unutmayın: aynı işlem içinde yalnızca IndexedDB isteklerini bekleyin, araya ağ isteği gibi başka işler koymayın. async/await sözdizimi için async/await rehberine bakabilirsiniz.
function istegiBekle(istek) {
return new Promise((resolve, reject) => {
istek.onsuccess = () => resolve(istek.result);
istek.onerror = () => reject(istek.error);
});
}
async function musteriAdi(db, id) {
const store = db.transaction("musteriler").objectStore("musteriler");
const musteri = await istegiBekle(store.get(id));
return musteri?.ad ?? null;
}Sık yapılan hatalar

- Şemayı upgradeneeded dışında değiştirmeye çalışmak:
createObjectStorevecreateIndexyalnızca sürüm değişikliği işleminde çalışır; yeni depo için sürümü artırın. - Sürümü ondalıklı artırmak: 1.5 gibi değerler yuvarlanır ve yükseltme tetiklenmeyebilir.
- add() ile güncelleme yapmak: Anahtar zaten varsa
add()hata verir ve hata yakalanmazsa tüm işlem geri alınır; güncelleme içinput()kullanın. - İşlem içinde başka bir eş zamansız iş beklemek: İşlem bekleyen istek kalmadığında kapanır; sonraki istek pasif işlem hatası verir.
- Tek bir okuma için readwrite açmak: Aynı depo üzerindeki diğer yazma işlemlerini gereksiz yere sıraya sokar.
- versionchange olayını dinlememek: Eski sekme bağlantıyı kapatmadığı için yeni sürüm hiç açılmaz.
Sık Sorulan Sorular
IndexedDB ile localStorage arasındaki temel fark nedir?
localStorage eş zamanlıdır, yalnızca string saklar ve köken başına birkaç megabaytla sınırlıdır. IndexedDB eş zamansızdır, nesneleri ve blob'ları saklar, dizinlerle sorgulanır ve çok daha büyük, tarayıcının belirlediği bir kotayı paylaşır.
IndexedDB worker içinde kullanılabilir mi?
Evet. MDN, giriş noktası olan IDBFactory nesnesinin hem Window.indexedDB hem de WorkerGlobalScope.indexedDB üzerinden sunulduğunu belirtir. Ağır veri işlemlerini Web Worker içinde yapabilirsiniz.
Veritabanındaki veriler kalıcı mı?
Varsayılan olarak "elden geldiğince" saklanır; disk baskısı olduğunda tarayıcı kökenin tüm verisini silebilir. Kritik veriler için navigator.storage.persist() ile kalıcı depolama isteyebilirsiniz; ayrıntılar Storage API yazısında.
Üçüncü taraf iframe IndexedDB kullanabilir mi?
MDN'in güvenlik notuna göre tarayıcı üçüncü taraf çerezleri hiç kabul etmeyecek şekilde ayarlandıysa <iframe> içindeki üçüncü taraf içerik IndexedDB'ye erişemez. Bu ayar kullanıcıya bağlı olduğu için gömülü widget'larda IndexedDB'nin bulunmayabileceğini hesaba katın.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Web API öğrenme rehberi · localStorage ve sessionStorage · Depolama kotası ve kalıcı depolama · Cache API · Web Workers · async/await