오프라인에서 원활하게 작동하는 애플리케이션을 구축하는 것은 현대 웹 개발에서 핵심 요건이 되었습니다. IndexedDB는 개발자가 브라우저에서 직접 대량의 데이터를 저장하고 관리할 수 있도록 하는 강력한 솔루션을 제공합니다. 이 클라이언트 측 NoSQL 데이터베이스는 효율적인 데이터 처리를 가능하게 하며, 오프라인 기능이 필요한 웹 애플리케이션에 이상적입니다. 이 기사에서는 IndexedDB의 기본 개념, 주요 기능을 살펴보고, 웹 프로젝트에서 이를 설정하고 사용하는 단계를 안내합니다.
IndexedDB란 무엇인가요?
IndexedDB 는 웹 애플리케이션이 브라우저에서 구조화된 데이터를 직접 저장하고 관리할 수 있게 해주는 강력한 클라이언트 측 NoSQL 데이터베이스입니다. 로컬에서 대량의 데이터 세트를 처리하기 위한 효율적인 솔루션을 제공하므로 오프라인 기능과 데이터가 많은 애플리케이션에 이상적입니다.

IndexedDB의 주요 장점 중 하나는 오프라인 웹 애플리케이션을 지원하는 기능입니다. 대규모 데이터를 저장하고, 빠른 검색을 위한 인덱싱을 사용하며, 트랜잭션을 관리하고, 커서를 사용하여 데이터를 탐색 할 수 있습니다. 이러한 기능들은 웹 앱에서 복잡한 데이터를 처리하는 데 탁월한 선택이 되게 합니다.
Chrome, Firefox, Safari, Edge 등 대부분의 최신 웹 브라우저는 IndexedDB를 지원합니다. 그러나 귀하의 웹 애플리케이션을 타겟으로 하는 브라우저와 IndexedDB 버전이 호환되는지 확인하는 것이 중요합니다.
IndexedDB를 사용하려면, 첫 번째 단계는 데이터베이스에 연결을 여는 것입니다. 이는 데이터베이스를 생성하고 상호작용하기 위해 필요합니다.

IndexedDB 시작하기: 종합 가이드
IndexedDB는 웹 애플리케이션을 위한 강력한 클라이언트 측 저장 솔루션입니다. 이 가이드는 기본 설정에서 고급 기술 및 실제 구현에 이르기까지 모든 것을 안내합니다.
IndexedDB 설정하기
데이터베이스 생성 및 열기
IndexedDB에서 새 데이터베이스를 만들기 위해서는 indexedDB.open()
메서드를 사용합니다:
const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 여기에서 객체 저장소 및 인덱스를 생성합니다.
};
이 메서드는 두 개의 매개변수를 사용합니다: 데이터베이스 이름과 버전 번호입니다. 기존 데이터베이스를 열려면 버전 번호를 지정하지 않고 메서드를 간단히 호출하면 됩니다.
데이터베이스 버전 관리
IndexedDB는 스키마 변경 처리를 위한 버전 관리 기능을 지원합니다. 현재 데이터베이스보다 높은 버전 번호로 데이터베이스를 열면 onupgradeneeded
이벤트가 발생하여 데이터베이스 스키마를 업데이트할 수 있습니다.
객체 저장소 및 인덱스 생성
객체 저장소는 IndexedDB에서 데이터를 저장하는 컨테이너입니다:
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 키 경로와 함께 객체 저장소 생성
const objectStore = db.createObjectStore("customers", { keyPath: "id" });
// 효율적인 쿼리를 위한 인덱스 생성
objectStore.createIndex("name", "name", { unique: false });
};
트랜잭션 작업하기
트랜잭션 기본 이해하기
IndexedDB의 트랜잭션은 여러 작업을 단일 원자 단위로 그룹화하여 데이터 무결성을 유지합니다. 모든 변경 사항이 적용되거나 전혀 적용되지 않도록 보장합니다.
트랜잭션 생성 및 관리
const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
transaction.oncomplete = (event) => {
console.log("트랜잭션이 성공적으로 완료되었습니다.");
};
transaction.onerror = (event) => {
console.error("트랜잭션이 실패했습니다.");
};
오류 처리 및 롤백
작업이 실패할 경우, abort()
메서드를 사용하여 전체 트랜잭션을 롤백할 수 있습니다:
try {
// 작업 수행
} catch (error) {
transaction.abort();
console.error("트랜잭션이 중단되었습니다:", error);
}
IndexedDB에서 데이터 작업
데이터 추가하기
const customerData = { id: "00001", name: "John Doe", email: "john@example.com" };
const request = objectStore.add(customerData);
request.onsuccess = (event) => {
console.log("데이터가 성공적으로 추가되었습니다.");
};
데이터 검색하기
키를 사용한 기본 데이터 검색:
const request = objectStore.get("00001");
request.onsuccess = (event) => {
console.log("고객 데이터:", request.result);
};
인덱스를 사용한 필터링
const index = objectStore.index("name");
const request = index.get("John Doe");
request.onsuccess = (event) => {
console.log("이름으로 찾음:", request.result);
};
고급 쿼리 방법
복잡한 쿼리의 경우 IndexedDB는 openCursor()
및 IDBKeyRange
와 같은 메서드를 사용하여 범위 쿼리 및 복합 쿼리를 제공합니다:
const range = IDBKeyRange.bound("A", "F"); // A부터 F까지 시작하는 이름
const request = index.openCursor(range);
레코드 업데이트하기
const updateData = { id: "00001", name: "John Smith", email: "john@example.com" };
const request = objectStore.put(updateData);
레코드 삭제하기
const request = objectStore.delete("00001");
request.onsuccess = (event) => {
console.log("레코드가 삭제되었습니다.");
};
커서 작업하기
커서 기능 이해하기
커서는 객체 저장소 또는 인덱스의 레코드를 효율적으로 반복하여 데이터에 접근하고 조작할 수 있는 방법을 제공합니다.
레코드 탐색하기
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log("키:", cursor.key, "값:", cursor.value);
cursor.continue(); // 다음 레코드로 이동
} else {
console.log("더 이상 레코드가 없습니다.");
}
};
커서를 사용한 데이터 수정
const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
if (cursor.value.status === "inactive") {
const updateData = cursor.value;
updateData.status = "active";
cursor.update(updateData);
}
cursor.continue();
}
};
스키마 관리 및 업그레이드
데이터베이스 스키마 업그레이드
애플리케이션이 발전함에 따라 데이터베이스 스키마를 수정해야 할 수도 있습니다:
const request = indexedDB.open("MyDatabase", 2); // 버전 번호 증가
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 객체 저장소 존재 여부 확인
if (!db.objectStoreNames.contains("newStore")) {
db.createObjectStore("newStore", { keyPath: "id" });
}
};
업그레이드 중 데이터 마이그레이션
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion;
if (oldVersion < 1) {
// 첫 번째 버전 설정
}
if (oldVersion < 2) {
// 새로운 스키마로 데이터 마이그레이션
const transaction = event.target.transaction;
const oldStore = transaction.objectStore("oldStore");
const newStore = db.createObjectStore("newStore", { keyPath: "id" });
oldStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
newStore.add(cursor.value);
cursor.continue();
}
};
}
};
성능 최적화
효율적인 대량 작업
더 나은 성능을 위해 여러 레코드를 다룰 때는 대량 작업을 사용하십시오:
const transaction = db.transaction(["customers"], "readwrite");
const store = transaction.objectStore("customers");
// 단일 트랜잭션에서 여러 레코드 추가
customerList.forEach(customer => {
store.add(customer);
});
더 빠른 쿼리를 위한 인덱스 활용
자주 쿼리되는 속성에 인덱스를 생성하여 데이터 검색 속도를 보장하세요:
objectStore.createIndex("email", "email", { unique: true });
objectStore.createIndex("lastLogin", "lastLogin", { unique: false });
연결 관리 모범 사례
필요할 때만 연결을 열고 완료되면 닫으십시오:
let db;
function openDB() {
const request = indexedDB.open("MyDatabase", 1);
request.onsuccess = (event) => {
db = event.target.result;
};
return request;
}
// 데이터베이스 작업이 종료되었을 때
function closeDB() {
if (db) {
db.close();
db = null;
}
}
실제 예제: 오프라인 지원이 있는 작업 관리자
데이터베이스 설정
const request = indexedDB.open("TaskManagerDB", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const taskStore = db.createObjectStore("tasks", { keyPath: "id", autoIncrement: true });
// 쿼리를 위한 인덱스 생성
taskStore.createIndex("status", "status", { unique: false });
taskStore.createIndex("dueDate", "dueDate", { unique: false });
};
작업 추가하기
function addTask(taskData) {
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const request = taskStore.add({
title: taskData.title,
description: taskData.description,
status: "pending",
dueDate: taskData.dueDate,
createdAt: new Date()
});
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
작업 검색 및 표시하기
function getAllTasks() {
const transaction = db.transaction(["tasks"], "readonly");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const request = taskStore.openCursor();
const tasks = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
tasks.push(cursor.value);
cursor.continue();
} else {
resolve(tasks);
}
};
request.onerror = () => reject(request.error);
});
}
작업 업데이트 및 삭제하기
function updateTaskStatus(id, newStatus) {
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const getRequest = taskStore.get(id);
getRequest.onsuccess = () => {
const task = getRequest.result;
task.status = newStatus;
task.updatedAt = new Date();
const updateRequest = taskStore.put(task);
updateRequest.onsuccess = () => resolve(true);
updateRequest.onerror = () => reject(updateRequest.error);
};
getRequest.onerror = () => reject(getRequest.error);
});
}
서버와 동기화하기
function syncWithServer() {
if (!navigator.onLine) {
return Promise.reject(new Error("인터넷 연결이 없습니다."));
}
return getAllTasks()
.then(tasks => {
// 동기화가 필요한 작업 필터링
const unsynced = tasks.filter(task => !task.synced);
// fetch API를 사용하여 서버로 전송
return fetch('https://api.example.com/tasks/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unsynced)
});
})
.then(response => response.json())
.then(result => {
// 작업을 동기화된 것으로 표시
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
result.syncedIds.forEach(id => {
const request = taskStore.get(id);
request.onsuccess = () => {
const task = request.result;
task.synced = true;
taskStore.put(task);
};
});
return result;
});
}
// 온라인 이벤트를 듣고 자동으로 동기화
window.addEventListener('online', syncWithServer);
IndexedDB와 API 관리를 위한 Apidog 통합
클라이언트 측 저장을 위해 IndexedDB로 애플리케이션을 구축할 때, 데이터 동기화를 위해 백엔드 API와 상호 작용해야 하는 경우가 많습니다. Apidog는 이러한 API 상호 작용을 관리하기 위한 원활한 솔루션을 제공합니다.

Apidog가 IndexedDB 개발을 향상시키는 이유
IndexedDB로 오프라인 가능 애플리케이션을 개발할 때, Apidog는 여러 가지 이점을 제공합니다:
실시간 동기화: Apidog는 데이터 동기화를 위해 사용되는 API 엔드포인트가 항상 제대로 구성되고 테스트되도록 하여 애플리케이션이 온라인 상태가 되었을 때 통합 문제를 없애줍니다.
모의 API 응답: 오프라인 기능을 개발할 때, Apidog의 스마트 모의 엔진을 통해 실제 서버 없이 IndexedDB 동기화 로직을 테스트하기 위한 API 응답을 시뮬레이션할 수 있습니다.

협업 API 설계: 팀이 동시에 프론트엔드 저장소와 백엔드 API 작업을 진행할 때, Apidog는 API 사양에 대한 실시간 협업을 촉진합니다.

Apidog를 개발 워크플로에 통합함으로써 클라이언트 측 저장소와 서버 측 프로세싱 간의 원활한 연결 고리를 생성하여 애플리케이션의 안정성을 높이고 유지 관리하기 쉽게 만듭니다.