필요 지식
javascript, IndexedDB, Dexie

SSR(Server Side Rendering) 방식으로 개발할때는 화면에 필요한 데이터를 거의 항상 서버로 부터 조회하게 됩니다. 그러나 CSR(Client Side Rendering) 방식으로 만들거나, 혹은 SPA(Single Page Application) 로 구현된 웹페이지에서는 데이터를 항상 서버에서 조회하지 않고 로컬 브라우저에 저장하고 재사용하는 방법을 고민하게 됩니다.

브라우저에서는 Session StorageLocal StorageIndexedDB 를 활용하여 데이터를 저장할수 있습니다. Session Storage, Local Storage 가 사용하기엔 쉽지만 데이터양이 많고 구조화가 필요한 경우엔 IndexedDB를 사용해야 합니다. 하지만 기본적인 기능만 사용할려고 해도 굉장히 지저분한 코딩을 해야 한다는 것을 곧 깨닫게 됩니다.

IndexedDB 참고
https://developer.mozilla.org/ko/docs/IndexedDB/Using_IndexedDB

이때 사용 가능한 매우 훌륭한 Wrapper 라이브러리가 있습니다. 바로 Dexie 입니다. Dexie 는 IndexedDB를 간결하게 사용할수 있도록 도와줍니다.

경로 : https://dexie.org
설치 : npm install dexie, yarn add dexie
현재버전 : 2.0.4

특징

가장 큰 특징 두가지는 기존에 사용하던 IndexedDB 를 마이그레이션 할 필요없이 사용 가능하다는 것과, Promise/A+ 와 ECMA6을 지원한다는 것입니다. 그리고 npm 이나 yarn을 사용하지 않더라도 CDN 으로 제공하는 스크립트를 추가하는 것만으로 간단하게 적용이 가능합니다.

상세한 비교 설명은 아래 URL을 참고하시면 되겠습니다.

about Dexie.js
https://dexie.org/docs/Dexie.js

dexie vs lawnchair vs localforage vs pouchdb
https://www.npmtrends.com/dexie-vs-lawnchair-vs-localforage-vs-pouchdb

DB 생성

  1. var db = new Dexie("MyDatabase");
  2. db.version(1).stores({
  3. friends: "++id, name, age, *tags",
  4. gameSessions: "id, score"
  5. });

store() 함수를 통해 사용할 테이블의 구조를 정의하면 됩니다. 테이블의 컬럼에 정의 가능한 속성은 아래와 같습니다.

기호의미
++Auto-incremented primary key
&Unique
*Multi-entry index
[A+B]Compound index

DB 업그레이드

만약 서비스를 제공중에 DB 구조를 바꿔야 할 경우, 서버 DB에서도 마이그레이션 작업이 필요하듯이 IndexedDB도 마이그레이션이 필요합니다. 아래 소스는 변경되는 구조를 버전2로 정의하고 upgrade() 에서 마이그레이션 작업을 정의하고 있습니다.

  1. db.version(1).stores({
  2. friends: "++id,name,age,*tags",
  3. gameSessions: "id,score"
  4. });
  5. db.version(2).stores({
  6. friends: "++id, [firstName+lastName], yearOfBirth, *tags", // Change indexes
  7. gameSessions: null // Delete table
  8. }).upgrade(tx => {
  9. // Will only be executed if a version below 2 was installed.
  10. return tx.friends.modify(friend => {
  11. friend.firstName = friend.name.split(' ')[0];
  12. friend.lastName = friend.name.split(' ')[1];
  13. friend.yearOfBirth = new Date(new Date().getFullYear() - friend.age, 0);
  14. delete friend.name;
  15. delete friend.age;
  16. });
  17. });

데이터 추가

단일 데이터를 추가할수도 있고, 여러개의 데이터를 대량으로 추가할수도 있습니다.
Dexie 에서 제공하는 CRUD 함수들은 Promise로 제공하기 때문에 다른 Promise 로직과 결합하여 흐름제어가 가능합니다. 다른 Promise와 결합할 필요가 없다면 await 키워드를 사용하여 호출을 완료할 수 있습니다.

  1. await db.friends.add({name: "Josephine", age: 21});
  2. await db.friends.bulkAdd([
  3. {name: "Foo", age: 31},
  4. {name: "Bar", age: 32}
  5. ]);

데이터 수정

데이터 update 를 위해 Table.put()Table.bulkPut()Table.update()Collection.modify() 를 사용할 수 있습니다. 여기서 put은 같은 키의 값이 존재할때는 update, 존재하지 않을때는 insert를 하는 upsert 의미로 동작하므로 매우 유용합니다.

데이터 조회

데이터를 조회할때는 where() 와 get() 을 사용할수 있습니다. 단일건을 조회할때는 get() 이 좀더 간단한 방법을 제공하고 있습니다.

  1. const forbundsKansler = await db.friends.where({
  2. firstName: "Angela",
  3. lastName: "Merkel"
  4. }).first();
  5. const forbundsKansler = await db.friends.get({
  6. firstName: "Angela",
  7. lastName: "Merkel"
  8. });

그러나 복잡한 조건을 적용하기 위해서는 where()를 사용해야 합니다. 메소드 체인으로 조건식을 적용할 수 있습니다.

  1. const someFriends = await db.friends
  2. .where("age").between(20, 25)
  3. .offset(150).limit(25)
  4. .toArray();
  5. await db.friends
  6. .where("name").equalsIgnoreCase("josephine")
  7. .each(friend => {
  8. console.log("Found Josephine", friend);
  9. });
  10. const abcFriends = await db.friends
  11. .where("name")
  12. .startsWithAnyOfIgnoreCase(["a", "b", "c"])
  13. .toArray();
  14. // This query is equal to:
  15. // select * from friends where firstName='Angela' order by lastName
  16. const angelasSortedByLastName = await db.friends
  17. .where('[firstName+lastName]')
  18. .between([["Angela", ""], ["Angela", "\uffff"])
  19. .toArray()
  20. const best5GameSession = await db.gameSessions
  21. .orderBy("score").reverse()
  22. .limit(5)
  23. .toArray();