back

Posts

Storage말고 IndexedDB는 어때요?

2025-05-21

새로운 회사에서 레거시 코드와 버그를 해결하던 중 다음과 같은 상황을 맞닥뜨렸습니다

  1. 외부결제 모듈 사용 때문에 외부 페이지를 접속 했다가 결제가 끝나면 다시 앱으로 복귀
  2. 이미지로 구성된 기프트카드는 결제가 완료된 후, 결제완료정보에 묶여 카드 정보를 같이 서버로 전달
  3. 해당 기프트카드 정보는 그동안 Recoil로 관리되고 있어서 카드 정보가 소멸된 채 결제가 완료

처음 해당 버그를 발견했을 땐 "뭐야? Recoil대신 스토리지로 관리하면 되는거 아니야?"란 생각을 가지고 수정을 진행했습니다.

하지만... 로직을 구성하던 중 발견한 문제가 하나 더 있었으니 해당 기프트카드의 정보 중, 이미지는 Base64형태였던 거였죠

Base64가 뭔데요

Base64는 바이너리데이터를 텍스트로 인코딩하는 방식 중 하나입니다.

64개의 ASCII 문자만을 사용해 데이터를 표현하는 인코딩 방식으로
A-Z (26개), a-z (26개), 0-9 (10개), +, /
총 64개의 문자로 구성되어 있어 이름이 Base64라고 하네요.

저희 회사앱은 Next.js와 Flutter로 구성된 웹뷰형태이며, 해당 카드의 이미지는 Flutter에서 Base64로 가공하여 웹으로 전달하는 방식이였죠

Base64는 텍스트로 인코딩되기 때문에 그 길이가 무지막지하게 깁니다. 따라서 스토리지에 그대로 저장할 수 없는 문제가 있었죠.

그래서 고민하다 예전에 이론만 봤었던 IndexedDB 활용해서 해결해보자 생각했습니다.

IndexedDB ?

IndexedDB는 브라우저에서 사용 가능한 로컬 DB API입니다. NoSQL계열로 key-value형식으로 데이터를 저장할 수 있습니다.
수 MB~수 GB의 데이터를 저장 가능하고 비동기로 작동하는 특징이 있죠. 또한 트랜잭션 기반으로 작동하여 안전하고 대부분 브라우저에서 지원하고 있습니다.

CRUD

IndexedDB를 사용하려면 우선 DB부터 만들어야 합니다

const request = indexedDB.open("MyDB", 1);

위 코드처럼 첫번째 매개변수로 DB명, 두번째로는 DB의 버전을 입력합니다.


IndexedDB에서는 버전이 가장 중요한데요

해당 버전은 object store(테이블이라 생각하면 됩니다)를 만들거나 수정하면 반드시 업그레이드해야 합니다.

DB를 만들었으니 이제 object store(*테이블로 지칭하겠습니다)를 만들어봐야겠죠?

request.onupgradeneeded = function (event) {
  const db = request.result;

  if (!db.objectStoreNames.contains("images")) {
    db.createObjectStore("images", { keyPath: "id" });
  }
};

request.onerror = () => {
  console.error("IndexedDB 열기 실패");
};

request.onsuccess = () => {
  console.log("IndexedDB 연결 성공");
};

db.objectStoreNames.contains('images')로 해당 테이블이 있는지 확인하고 없다면 createObjectStore를 사용해서 만듭니다. 여기서 keyPath 고유키라 생각하시면 됩니다.

Create

function addImage(id: string, base64: string) {
  const request = indexedDB.open("MyDB");

  request.onsuccess = () => {
    const db = request.result;
    const tx = db.transaction("images", "readwrite");
    const store = tx.objectStore("images");

    store.add({ id, base64 });

    tx.oncomplete = () => {
      console.log("이미지 저장 완료");
    };
  };
}

우선 DB에 접속 후, db.transaction('images', 'readwrite');로 images테이블에 대해 읽기/쓰기 권한을 가진 트랜잭션을 생성합니다.

이후 트랜잭션에서 images테이블을 가져와서 데이터를 Create하고 트랜잭션완료를 확인하면 Create가 끝납니다

Read

function getImage(id: string) {
  const request = indexedDB.open("MyDB");

  request.onsuccess = () => {
    const db = request.result;
    const tx = db.transaction("images", "readonly");
    const store = tx.objectStore("images");

    const getRequest = store.get(id);

    getRequest.onsuccess = () => {
      const result = getRequest.result;
      if (result) {
        console.log("이미지 데이터:", result.base64);
      } else {
        console.log("해당 ID의 데이터 없음");
      }
    };
  };
}

Read도 동일하게 DB연결 -> 트랜잭션 생성 -> 테이블 접근 순으로 동작하죠 여기서 DB를 생성할때 지정했던 keyPath로 데이터를 접근할 수 있습니다.

Update

function updateImage(id: string, newBase64: string) {
  const request = indexedDB.open("MyDB");

  request.onsuccess = () => {
    const db = request.result;
    const tx = db.transaction("images", "readwrite");
    const store = tx.objectStore("images");

    store.put({ id, base64: newBase64 });

    tx.oncomplete = () => {
      console.log("이미지 업데이트 완료");
    };
  };
}

Update에서 중요한건 기존 정보를 덮어씌우기 때문에 데이터 관리에 주의를 기울여야합니다

Delete

function deleteImage(id: string) {
  const request = indexedDB.open("MyDB");

  request.onsuccess = () => {
    const db = request.result;
    const tx = db.transaction("images", "readwrite");
    const store = tx.objectStore("images");

    store.delete(id);

    tx.oncomplete = () => {
      console.log("이미지 삭제 완료");
    };
  };
}

위 처럼 IndexedDB는 실제 DB처럼 트랜잭션도 존재하며 비동기로 동작하여 실무에서 유용하게 쓰일 경우가 많이 있습니다.

여러분도 기회가 된다면 IndexedDB를 한번 사용해보는건 어떨까요?