🗿

TypeORMを使ってブラウザでデータを永続化する

2022/09/25に公開

TypeORMをブラウザで利用した際に、永続化する方法がドキュメントや見つかった記事から読み取れなかったために半日潰してしまったのでメモを残しておく。

結論としては、TypeORMを(sql.jsを使って)ブラウザで利用する場合、ローカルストレージへ永続化するのがデフォルトの挙動だった。なので特別なことは不要で、シンプルにsql.jsの利用をセットアップすれば良い。

サンプルコード

  1. 以下のようなUserを用意して
src/entity/user.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number

  @Column('text')
  name: string
}
  1. 以下のようにDataSourceを初期化すれば
src/database.ts
import initSqlJs from 'sql.js'
import { DataSource } from 'typeorm'
import { User } from './entity/user'

export const setupSQLite = async () => {
  ;(window as any).SQL = await initSqlJs({
    locateFile: (file: string) => `https://sql.js.org/dist/${file}`,
  })
  const AppDataSource = new DataSource({
    type: 'sqljs',
    autoSave: true,
    location: 'test',
    entities: [User],
    synchronize: true, // for development
    logging: true, // for development
  })
  const connection = await AppDataSource.initialize()
  const userRepository = connection.getRepository(User)
  return { userRepository }
}
  1. 以下の形で永続化できる
src/main.ts
import { setupSQLite } from './database'
import { User } from './entity/user'

const sample = async () => {
  const { userRepository } = await setupSQLite()

  const count = await userRepository.count()
  console.log(`count: ${count}`)

  const user = new User()
  user.name = 'piyo'
  await userRepository.save(user)

  console.log(user)
}

sample()

実行結果

ブラウザで実行して、コンソールログに以下のような出力があれば成功。

実行の度にcountやidの値が増えていくことが確認できる。

またDeveloperConsoleで見ると、ローカルストレージにバイナリが保存されていることがわかる。

ローカルストレージを使いたくない場合

ただローカルストレージは以下のような点もあり、利用したくないケースもあるかもしれない

  • 容量上限が数MB(ブラウザによる)
  • 処理が同期的

その場合は、localForageというローカルストレージ互換のAPIでIndexedDBやWebSQLへ永続化ができるライブラリを使うと問題が回避できそう。TypeORMでlocalForageを利用するオプションが用意されている
window.localforageを用意してから、useLocalForage: trueをDataSource初期化時のオプションに渡すと保存先がIndexedDBに変わった。

src/database.ts
+  ;(window as any).localforage = (await import('localforage')).default
   const AppDataSource = new DataSource({
     type: 'sqljs',
     autoSave: true,
     location: 'test',
     entities: [User],
     synchronize: true, // for development
     logging: true, // for development
+    useLocalForage: true,
   })

今回苦戦したポイント

参考

Discussion