JavaScript ve IndexedDB

JavaScript ve IndexedDB

Bu makalede JavaScript ve IndexedDB'yi açıklayacağız.

Bu eğitim, her adımda pratik örnek kodlar içeren, JavaScript ve IndexedDB'nin adım adım açıklamasını sunar ve anlayışınızı derinleştirmenize yardımcı olur.

YouTube Video

javascript-indexed-db.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    .container-flex {
 32        display: flex;
 33        flex-wrap: wrap;
 34        gap: 2em;
 35        max-width: 1000px;
 36        margin: 0 auto;
 37        padding: 1em;
 38        background-color: #ffffff;
 39        border: 1px solid #ccc;
 40        border-radius: 10px;
 41        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 42    }
 43
 44    .left-column, .right-column {
 45        flex: 1 1 200px;
 46        min-width: 200px;
 47    }
 48
 49    h1, h2 {
 50        font-size: 1.2rem;
 51        color: #007bff;
 52        margin-top: 0.5em;
 53        margin-bottom: 0.5em;
 54        border-left: 5px solid #007bff;
 55        padding-left: 0.6em;
 56        background-color: #e9f2ff;
 57    }
 58
 59    button {
 60        display: block;
 61        margin: 1em auto;
 62        padding: 0.75em 1.5em;
 63        font-size: 1rem;
 64        background-color: #007bff;
 65        color: white;
 66        border: none;
 67        border-radius: 6px;
 68        cursor: pointer;
 69        transition: background-color 0.3s ease;
 70    }
 71
 72    button:hover {
 73        background-color: #0056b3;
 74    }
 75
 76    #output {
 77        margin-top: 1em;
 78        background-color: #1e1e1e;
 79        color: #0f0;
 80        padding: 1em;
 81        border-radius: 8px;
 82        min-height: 200px;
 83        font-family: Consolas, monospace;
 84        font-size: 0.95rem;
 85        overflow-y: auto;
 86        white-space: pre-wrap;
 87    }
 88
 89    .highlight {
 90        outline: 3px solid #ffc107; /* yellow border */
 91        background-color: #fff8e1;  /* soft yellow background */
 92        transition: background-color 0.3s ease, outline 0.3s ease;
 93    }
 94
 95    .active {
 96        background-color: #28a745; /* green background */
 97        color: #fff;
 98        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 99        transition: background-color 0.3s ease, box-shadow 0.3s ease;
100    }
101  </style>
102</head>
103<body>
104    <div class="container">
105        <h1>JavaScript Console</h1>
106        <button id="executeBtn">Execute</button>
107        <div id="output"></div>
108    </div>
109
110    <script>
111        // Override console.log to display messages in the #output element
112        (function () {
113            // Override console.log
114            const originalLog = console.log;
115            console.log = function (...args) {
116                originalLog.apply(console, args);
117                const message = document.createElement('div');
118                message.textContent = args
119                    .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
120                    .join(" ");
121                output.appendChild(message);
122            };
123
124            // Override console.error
125            const originalError = console.error;
126            console.error = function (...args) {
127                originalError.apply(console, args);
128                const message = document.createElement('div');
129                message.textContent = args
130                    .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
131                    .join(" ");
132                message.style.color = 'red'; // Color error messages red
133                output.appendChild(message);
134            };
135        })();
136
137        document.getElementById('executeBtn').addEventListener('click', () => {
138            // Prevent multiple loads
139            if (document.getElementById('externalScript')) return;
140
141            const script = document.createElement('script');
142            script.src = 'javascript-indexed-db.js';
143            script.id = 'externalScript';
144            //script.onload = () => console.log('javascript-indexed-db.js loaded and executed.');
145            //script.onerror = () => console.log('Failed to load javascript-indexed-db.js.');
146            document.body.appendChild(script);
147        });
148    </script>
149</body>
150</html>

JavaScript ve IndexedDB

IndexedDB, tarayıcılara yerleşik olan asenkron bir anahtar-değer depo veritabanıdır. İlişkisel veritabanlarına benzer özellikler sunar ve istemci tarafında büyük miktarda yapılandırılmış veriyi saklamanızı ve aramanızı sağlar. Özellikle çevrimdışı çalışabilen uygulamalar ve PWAs (Aşamalı Web Uygulamaları) için oldukça faydalıdır.

IndexedDB'nin Özellikleri

  • Asenkron ve olay tabanlı bir şekilde çalışır.
  • JavaScript nesneleri nesne depolarında saklanabilir.
  • Sorgular veya indeksler ile arama yapmak mümkündür.
  • Büyük bir depolama kapasitesine sahiptir (yüzlerce MB ve üzeri), böylece çerezlerden veya localStorage'dan çok daha fazla veri saklayabilirsiniz.

Veritabanı Açma ve Oluşturma

IndexedDB kullanmak için öncelikle bir veritabanı açmalısınız. Eğer mevcut değilse, otomatik olarak oluşturulur.

1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
  • open metodu, veritabanını asenkron olarak açar ve aşağıdaki üç olayı tetikler.

onsuccess

1// Fired when database is successfully opened
2request.onsuccess = (event) => {
3    const db = event.target.result; // Database instance
4    console.log('Database opened successfully:', db.name);
5};
  • onsuccess olayı, veritabanı başarıyla açıldığında tetiklenir. Bundan sonraki işlemler, bu noktada kullanılabilen request.result kullanılarak yapılmalıdır.

onerror

1// Fired when database fails to open
2request.onerror = (event) => {
3    console.error('Failed to open database:', event.target.error);
4};
  • onerror olayı, veritabanı açılamadığında tetiklenir. Hata kaydı tutma ve hata yönetimi burada gerçekleştirilmelidir.

onupgradeneeded

 1// Fired when database is newly created or upgraded
 2request.onupgradeneeded = (event) => {
 3    const db = event.target.result;
 4    console.log('Database upgrade needed (or newly created):', db.name);
 5
 6    // Example: Create an object store (like a table) if it doesn’t exist
 7    if (!db.objectStoreNames.contains('users')) {
 8        db.createObjectStore('users', { keyPath: 'id' });
 9        console.log('Object store "users" created');
10    }
11};
  • onupgradeneeded, veritabanı yeni oluşturulduğunda veya belirtilen sürüm mevcut sürümden yüksek olduğunda tetiklenir. Tablo (nesne deposu) oluşturma ve şema tanımlama bu aşamada yapılmalıdır.

Nesne Deposu Oluşturma

Öncelikle, onupgradeneeded içerisinde bir 'nesne deposu' (tabloya eşdeğer) oluşturun.

1request.onupgradeneeded = function (event) {
2    const db = event.target.result;
3    console.log("onupgradeneeded triggered. Database version:", db.version);
4    if (!db.objectStoreNames.contains('users')) {
5        console.log("Creating object store: users");
6        const store = db.createObjectStore('users', { keyPath: 'id' });
7        store.createIndex('name', 'name', { unique: false });
8    }
9};

Burada aşağıdaki ayarlar uygulanır:.

  • createObjectStore createObjectStore, veritabanında yeni bir nesne deposu oluşturmak için kullanılan bir yöntemdir. Nesne deposunda kayıtlar saklayabilir ve keyPath ve diğer seçenekleri belirleyerek verinin nasıl yönetileceğini tanımlayabilirsiniz. Bu işlem mutlaka onupgradeneeded olayı içerisinde yapılmalıdır.

  • keyPath: 'id' keyPath olarak id özelliğini belirleyin, böylece her kayıt birincil anahtar olarak benzersiz şekilde tanımlanır. Bu sayede veri eklerken, ararken veya güncellerken id otomatik olarak kullanılır.

  • createIndex createIndex metodunu kullanarak arama için name özelliğine dayalı bir indeks oluşturun. unique: false ayarlanarak, aynı name değerine sahip birden çok kayda izin verilir.

Başarılı Veritabanı Bağlantılarını Yönetme

Başarılı bir veritabanı bağlantısında yürütülecek işlemleri onsuccess olayına atayın. Bu işlemde veritabanı örneğini alır ve veri okuma/yazma için hazırlık yaparsınız.

1request.onsuccess = function (event) {
2    const db = event.target.result;
3    console.log('Database opened successfully');
4    // Use db for reading and writing in subsequent operations
5};
  • Bu işlem, IndexedDB veritabanı bağlantısı başarıyla tamamlandığında çalışır.

  • event.target.result, işlemlerin başlatılması ve nesne depolarına erişim için kullanılan açılmış veritabanı örneğini (IDBDatabase nesnesi) içerir.

  • Veri ekleme, alma, güncelleme ve silme gibi gerçek okuma ve yazma işlemleri db nesnesi kullanılarak gerçekleştirilir.

Bu noktada veritabanı hazırdır, böylece işlemlere güvenle başlayabilirsiniz.

Veri Ekleme

IndexedDB'ye yeni veri eklemenin yolu şöyledir.

 1function addUser(db, user) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.add(user);
 5
 6    request.onsuccess = () => {
 7        console.log('User added:', user);
 8    };
 9
10    request.onerror = () => {
11        console.error('Add failed:', request.error);
12    };
13}
14
15// Example: Add a user
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    addUser(db, { id: 1, name: 'Alice' });
19    addUser(db, { id: 2, name: 'Bob' });
20    addUser(db, { id: 3, name: 'John' });
21};
  • db.transaction() ile bir işlem oluşturun ve çalışılacak nesne deposunu ve modu (bu durumda, readwrite) belirtin.
  • store.add() metodunu kullanarak yeni veri ekleyin.
  • İşlemler otomatik olarak tamamlanır, fakat birden fazla işlemi gruplamak isterseniz işlem sonu olaylarını kullanarak bunları yönetebilirsiniz.

Veri Alma (Birincil Anahtar ile Arama)

Birincil anahtar kullanarak belirli bir verinin nasıl alınacağı aşağıda gösterilmiştir.

 1function getUserById(db, id) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.get(id);
 5
 6    request.onsuccess = () => {
 7        if (request.result) {
 8            console.log('User found:', request.result);
 9        } else {
10            console.log('User not found');
11        }
12    };
13
14    request.onerror = () => {
15        console.error('Error retrieving user:', request.error);
16    };
17}
18
19// Example: Get a user by id
20request.onsuccess = function (event) {
21    const db = event.target.result;
22    getUserById(db, 1);
23};
  • db.transaction() ile salt okunur bir işlem başlatın.
  • Belirtilen birincil anahtara karşılık gelen veriyi almak için store.get(id) kullanın.
  • Veri başarıyla alındığında onsuccess çağrılır ve sonuç mevcutsa görüntülenir. Sonuç yoksa, 'karşılık gelen veri yok' olarak kabul edilir.

İndeks ile Arama Kullanmak

Birincil anahtar dışında bir özellik ile arama yapmak isterseniz, önceden oluşturduğunuz indeksi kullanın.

 1function getUserByName(db, name) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name');
 5    const request = index.get(name);
 6
 7    request.onsuccess = () => {
 8        if (request.result) {
 9            console.log('User by name:', request.result);
10        } else {
11            console.log('No user found with that name');
12        }
13    };
14
15    request.onerror = () => {
16        console.error('Error retrieving user by name:', request.error);
17    };
18}
19
20// Example: Get a user by id
21request.onsuccess = function (event) {
22    const db = event.target.result;
23    getUserByName(db, 'Alice');
24};
  • Önceden oluşturulmuş name indeksine store.index('name') ile erişin.
  • index.get(value) ile eşleşen ilk kaydı alırsınız. Aynı değere sahip birden fazla kayıt varsa, bunların hepsini index.getAll(value) ile alabilirsiniz.

Veri Güncelleme

Mevcut veriyi güncellemek (üzerine yazmak) için put() metodunu kullanın.

Eşleşen bir birincil anahtara sahip bir kayıt varsa güncellenir; yoksa yeni bir kayıt eklenir.

 1function updateUser(db, updatedUser) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.put(updatedUser);
 5
 6    request.onsuccess = () => {
 7        console.log('User updated:', updatedUser);
 8    };
 9
10    request.onerror = () => {
11        console.error('Update failed:', request.error);
12    };
13}
14
15// Example: Update user
16request.onsuccess = async (event) => {
17    const db = event.target.result;
18
19    // Test : update existing user
20    updateUser(db, { id: 3, name: 'John Updated' });
21
22    // Test : insert new user
23    updateUser(db, { id: 4, name: 'Charlie' });
24};
  • put() hem güncelleme hem de ekleme için uygun bir metottur.
  • Aynı birincil anahtara sahip veri zaten varsa, üzerine yazılır.
  • Güncellemeden önce var olup olmadığını kontrol etmek isterseniz, önceden get() ile kontrol edebilirsiniz.

Veri Silme

Belirli bir birincil anahtara karşılık gelen veriyi silmek için delete() metodunu kullanın.

 1function deleteUser(db, id) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.delete(id);
 5
 6    request.onsuccess = () => {
 7        console.log(`User with id=${id} deleted successfully`);
 8    };
 9
10    request.onerror = () => {
11        console.error(`Failed to delete user with id=${id}:`, request.error);
12    };
13}
14
15// Example: Delete a user by id
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    deleteUser(db, 4);
19};
  • Eşleşen birincil anahtara sahip veriyi silmek için store.delete(id) kullanın.
  • Veri mevcut olmasa bile hata oluşmaz ve işlem başarılı kabul edilir.
  • Hata yönetimi uygulamak daha sağlam bir kod ile sonuçlanacaktır.

Tüm Veriyi Alma

getAll()

Nesne deposundaki tüm kayıtları almak için getAll() metodunu kullanın.

 1function getAllUsers(db) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.getAll();
 5
 6    request.onsuccess = () => {
 7        console.log('All users:', request.result);
 8    };
 9
10    request.onerror = () => {
11        console.error('Failed to retrieve users:', request.error);
12    };
13}
14
15// Example: Get all users
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    getAllUsers(db);
19};
  • getAll(), belirtilen nesne deposundaki tüm kayıtları bir dizi olarak alır.
  • Büyük miktarda veri tek seferde alınırken bile verimli bir şekilde işlenir.
  • Sonuçlar diziler halinde request.result'ta saklanır.
  • Her zaman hata yönetimi ekleyin ki başarısızlıklarla başa çıkabilin.

openCursor()

openCursor(), bir nesne deposunda veya indekste kayıtların sıralı olarak gezinilmesini sağlayan bir metottur. Büyük miktarda veriyi tek seferde almak yerine, verileri teker teker işlemek istediğinizde faydalıdır.

 1function getAllUsersWithCursor(db) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.openCursor();
 5
 6    request.onsuccess = () => {
 7        const cursor = request.result;
 8        if (cursor) {
 9            console.log('User:', cursor.value); // Process the current record
10            cursor.continue(); // Move to the next record
11        } else {
12            console.log('All users have been processed.');
13        }
14    };
15
16    request.onerror = () => {
17        console.error('Failed to open cursor:', request.error);
18    };
19}
20
21// Example: Get all users
22request.onsuccess = function (event) {
23    const db = event.target.result;
24    getAllUsersWithCursor(db);
25};
  • openCursor() kullanarak, bir imleç başlatır ve nesne deposundaki tüm kayıtları teker teker alırsınız.
  • Mevcut kaydın veri nesnesini almak için cursor.value kullanılır.
  • İmleci bir sonraki kayda taşımak için cursor.continue() kullanılır.
  • cursor === null olduğunda, tüm kayıtlarda gezinilmiş olur.

openCursor() kullanılarak güncelleme işlemi örneği

Örneğin, name değeri Alice olan bir kullanıcının adını Alicia olarak değiştirme işlemi şu şekilde olur:.

 1function updateUserName(db, oldName, newName) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name'); // Use the 'name' index
 5    const request = index.openCursor(IDBKeyRange.only(oldName));
 6
 7    request.onsuccess = () => {
 8        const cursor = request.result;
 9        if (cursor) {
10            const user = cursor.value;
11            user.name = newName; // Update the name
12            const updateRequest = cursor.update(user);
13
14            updateRequest.onsuccess = () => {
15                console.log('Updated user:', user);
16            };
17
18            updateRequest.onerror = () => {
19                console.error('Failed to update user:', updateRequest.error);
20            };
21
22            cursor.continue();
23        } else {
24            console.log('All matching users have been updated.');
25        }
26    };
27
28    request.onerror = () => {
29        console.error('Cursor error:', request.error);
30    };
31}
32
33// Example: Update user name
34request.onsuccess = function (event) {
35    const db = event.target.result;
36    updateUserName(db, 'Alice', 'Alicia');
37};
  • IDBKeyRange.only(oldName) IDBKeyRange.only kullanarak, anahtarı tam olarak oldName ile eşleşen kayıtları hedefleyebilirsiniz. Belirli bir değere doğrudan erişmek istediğinizde bu yararlıdır.

  • cursor.update() cursor.value güncellendikten sonra, update() fonksiyonunun çağrılması ilgili kaydın üzerine yazacaktır.

  • Birden Fazla Eşleşmeyi İşleme cursor.continue() çağrılarak imleci bir sonraki eşleşen kayda taşıyabilirsiniz. Bu, aynı anahtara veya koşula uyan birden fazla kaydı sırayla işlemden geçirmenizi sağlar.

  • Hata Yönetimi Bir işlem başarısız olduğunda onerror içinde günlükleme yapmak, nedenleri araştırmayı ve işletim sırasında sorun gidermeyi kolaylaştırır.

openCursor() kullanılarak silme işlemi örneği

Örneğin, name değeri Bob olan tüm kullanıcıları silme işlemi şu şekilde olur:.

 1function deleteUsersByName(db, targetName) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name');
 5    const request = index.openCursor(IDBKeyRange.only(targetName));
 6
 7    request.onsuccess = () => {
 8        const cursor = request.result;
 9        if (cursor) {
10            const deleteRequest = cursor.delete();
11
12            deleteRequest.onsuccess = () => {
13                console.log('Deleted user:', cursor.value);
14            };
15
16            deleteRequest.onerror = () => {
17                console.error('Failed to delete user:', deleteRequest.error);
18            };
19
20            cursor.continue();
21        } else {
22            console.log('All matching users have been deleted.');
23        }
24    };
25
26    request.onerror = () => {
27        console.error('Cursor error:', request.error);
28    };
29}
30
31// Example: Delete user by name
32request.onsuccess = function (event) {
33    const db = event.target.result;
34    deleteUsersByName(db, 'Bob');
35};
  • cursor.delete() cursor.delete() ile imlecin bulunduğu konumdaki kayıt silinir. Sonuçlar eşzamansız olarak döndüğü için işlemi onsuccess içinde kontrol edebilirsiniz.

İşlemler ve Eşzamansız İşleme Hakkında Notlar

IndexedDB eşzamansız ve olay tabanlıdır. Tüm işlemler onsuccess veya onerror olayları ile yönetilmelidir. Birden fazla işlemi birlikte gruplamak istediğinizde, bunları bir Promise içerisine sarmak kullanışlıdır.

 1function openDatabase() {
 2    return new Promise((resolve, reject) => {
 3        const request = indexedDB.open('MyDatabase', 1);
 4
 5        request.onupgradeneeded = function (event) {
 6            const db = event.target.result;
 7
 8            // Initialization process (creating object stores and indexes, etc.)
 9            const store = db.createObjectStore('users', { keyPath: 'id' });
10            store.createIndex('name', 'name', { unique: false });
11        };
12
13        request.onsuccess = function (event) {
14            const db = event.target.result;
15            resolve(db);
16        };
17
18        request.onerror = function () {
19            reject(request.error);
20        };
21    });
22}
23
24function addUserAsync(db, user) {
25    return new Promise((resolve, reject) => {
26        const transaction = db.transaction('users', 'readwrite');
27        const store = transaction.objectStore('users');
28        const request = store.add(user);
29
30        request.onsuccess = () => resolve();
31        request.onerror = () => reject(request.error);
32    });
33}
34
35async function main() {
36    try {
37        const db = await openDatabase();
38        await addUserAsync(db, { id: 1, name: 'Alice' });
39        console.log('User added successfully');
40    } catch (error) {
41        console.error('Error:', error);
42    }
43}
  • openDatabase veya addUserAsync gibi fonksiyonları Promise ile sarmak, eşzamansız işlemleri sezgisel olarak async/await ile yönetmenizi sağlar.
  • Bu, callback cehennemini önler ve kodun daha okunabilir olmasını sağlar.

Özet

Tarayıcı tarafında gelişmiş veri yönetimi yapmak istediğinizde IndexedDB çok güçlü bir özelliktir. Başlangıçta olay tabanlı eşzamansız işlemler kafa karıştırıcı olabilir, ancak yapıyı anladığınızda istemci tarafında tam ölçekli veri işlemleri yapabilirsiniz.

Özellikle aşağıdaki noktaları göz önünde bulundurmak, onu daha sorunsuz kullanmanıza yardımcı olacaktır:.

  • İlk ayarları onupgradeneeded ile yapın.
  • İşlemlerin okuma/yazma moduna dikkat edin.
  • Dizinler, verimli arama yapmanızı sağlar.
  • Veriler nesne olarak saklanabilir ve bu da onları JSON ile oldukça uyumlu kılar.

IndexedDB'yi tam olarak öğrenerek PWA ve çevrimdışı uygulamalar için veri yönetimi çok daha kolay hale gelir.

Yukarıdaki makaleyi, YouTube kanalımızda Visual Studio Code'u kullanarak takip edebilirsiniz. Lütfen YouTube kanalını da kontrol edin.

YouTube Video