🚀

TypeORM v0.3への移行について

2023/07/29に公開

TypeORM v0.2→v0.3 系の移行に際し、かなりの breaking changes(破壊的変更)が見受けられました。
実際私もプロジェクトの中で TypeORM のバージョンアップを行いかなり面倒だったのでその知見をまとめたいと思います。
基本的に英語のドキュメントしかないので、日本語でまとめる価値はあるかなと思い書くに至りました。

前提

TypeORM v0.3 のリリースノートと自身のナレッジに基づいて主な変更点をまとめており、全てを網羅しているわけではありません。
使用しているフレームワークは NestJS とします。

内容

  • Node v14, ts-node v10.7.0, @nestjs/typeorm v9 以上であることが必要
  • ormconfig のサポートが廃止
    data source のインスタンスを新たなファイルで定義する必要あり
  • migration のコマンド変更(※参考記事
  • DB へのコネクションの接続方法変更
    ConnectionDataSourceに変更(Connection は非推奨に))
v0.2
@Module({
  imports: [
    ...,
    TypeOrmModule.forRootAsync({
      ...,
      connectionFactory: async (config) => {
        const connection = new Connection(config)
        await connection.connect()
        return connection
      }
    })
  ]
})
v0.3
@Module({
  imports: [
    ...,
    TypeOrmModule.forRootAsync({
      ...,
      dataSourceFactory: async () => {
        const dataSource = new DataSource({
          type: "postgres",
          ...
        });
        await dataSource.initialize()
        return dataSource
      }
    })
  ]
})
  • Custom Repository の定義方法変更
    EntityRepository, getCustomRepositoryが非推奨に
v0.2
@EntityRepository(User)
export class UserRepository extends Repository<User> {
  async findUser(id: string): Promise<User> {
    return await this.find({ where: { id }});
  }
}
v0.3
export const UserRepository = myDataSource.getRepository(UserEntity).extend({
  async findUser(id: string): Promise<User> {
    return await this.find({ where: { id }});
  },
});

AbstractRepository を継承した Custom Repository を inject している場合などは、例えば以下のような書き方で実現可能
v0.3 では AbstractRepository は非推奨に

v0.2
export abstract class AbstractUserRepository extends Repository<User> {
  abstract findUser(id: string): Promise<User>
}
---
@EntityRepository(User)
export class UserRepository extends AbstractUserRepository {
  async findUser(id: string): Promise<User> {
    return await this.find({ where: { id }});
  }
}

export const UserRepositoryProvider = {
  provide: AbstractUserRepository,
  useFactory: (qr: QueryRunner): UserRepository => qr.manager.getCustomRepository(UserRepository)
}
v0.3
export const USER_REPOSITORY = Symbol('USER_REPOSITORY')
export interface UserRepository extends Repository<User>, UserRepositoryAddition {}
export interface UserRepositoryAddition {
  findUser(id: string): Promise<User>
}

export const UserRepositoryProvider = {
  provide: USER_REPOSITORY,
  useFactory: (qr: QueryRunner): UserRepository => qr.manager.getRepository(User).extend<UserRepositoryAddition>({
    async findUser(id: string): Promise<User> {
      return await this.find({ where: { id }})
    }
  })
}
---
// injectする際は以下のような感じ
@Inject(USER_REPOSITORY) private UserRepository: UserRepository
  • FindOptions のselectrelationsのプロパティの指定方法が配列からオブジェクトリテラルに変更
v0.2
const users = await userRepository.find({
  select: ['id', 'name', 'address.id']
})

const tasks = await taskRepository.find({
  relations: ['users']
})
v0.3
const users = await userRepository.find({
  select: {
    id: true,
    name: true,
    address: {
      id: true
    }
  }
})

const tasks = await taskRepository.find({
  relations: {
    users: true
  }
})
  • パラメータなしのfindOne廃止
v0.2
const user = await userRepository.findOne()
v0.3
const [user] = await userRepository.find()
  • findOne(id)廃止
    代わりにfindOneBy({ id: xxx })を使用
v0.2
const user = await userRepository.findOne(id)
v0.3
const user = await userRepository.findOneBy({ id })
  • findOne, findOneOrFail, find, count, findAndCountはパラメータに FindOptions しか受け付けないように
    = findOne({ name: xxx })みたいな where を省いた記述ができない
    代わりにfindOneBy, findOneByOrFail, findBy, countBy, findAndCountByが追加
v0.2
const user = await userRepository.findOne(id)
const users = await userRepository.find(id)
v0.3
const user = await userRepository.findOneBy(id)
const users = await userRepository.findBy(id)
  • findByIdsが非推奨に
    代わりにfindByInを組み合わせて使用
v0.2
const users = await userRepository.findByIds(ids)
v0.3
const users = await userRepository.findBy({ id: In(ids) })
  • findOneQueryBuilder.getOne()は DB に値が見つからなかった時、undefinedではなくnullを返すように変更(null を返す方が理にかなっている)
v0.2
const user: Promise<User | undefined> = await userRepository.findOne(id)
v0.3
const user: Promise<User | null> = await userRepository.findOneBy(id)
  • find*メソッドで使用される where の値として null がサポートされない
    代わりにIsNull()を明示的に使用
v0.2
const usersWithoutAddress = await userRepository.find({ where: { address: null }})
v0.3
const usersWithoutAddress = await userRepository.find({ where: { address: IsNull() }})

リリースノートに書いていないが変更されていた点

  • OrderByConditionが非推奨に
    特に使っても問題なさそうな気がしたので、私は TypeORM からのインポートをやめて、リポジトリに独自に TypeORM と同じ型定義をしました
export type OrderByCondition = {
  [columnName: string]:
    | ('ASC' | 'DESC')
    | {
        order: 'ASC' | 'DESC';
        nulls?: 'NULLS FIRST' | 'NULLS LAST';
      };
};
  • orUpdateの書き方が変更(※参考
v0.2
await dataSource
  .createQueryBuilder()
  .insert()
  .into(User)
  .values([{
    firstName: '田中',
    lastName: '太郎',
    externalId: 1,
  }])
  .orUpdate({
    overwrite: ['firstName', 'lastName'],
    conflict_target: ['externalId']
  })
  .execute()
v0.3
await dataSource
  .createQueryBuilder()
  .insert()
  .into(User)
  .values([{
    firstName: '田中',
    lastName: '太郎',
    externalId: 1,
  }])
  .orUpdate(
    ["firstName", "lastName"],
    ["externalId"],
  )
  .execute()

その他のポイント

  • seed データを投入するためのライブラリであるtypeorm-seedingが TypeORM v0.3 では使えないように
    → 自作 or typeorm-extensionで代用

deprecated(非推奨)になったものを探す方法

実際私はリリースノートに従って TypeORM のバージョンアップを行いました。しかし、上記で記載したようにリリースノートには書いていないが非推奨になった型やメソッドも存在したので、そういう場合は直接ライブラリのコードを参照するしかありません。個人的には以下 2 つの方法で参照しています。

1. node_modules を見に行く
例えば、TypeORM v0.3 ではEntityRepositoryが非推奨になっています。これを自らの開発環境(VSCode)で見てみると、以下のように取り消し線が入っています。
非推奨の例
そして、このEntityRepositoryを右クリックして、「実装へ移動」をクリックします。
node_modulesへの移動の方法
すると、node_modules 内の TypeORM のコードに遷移できます。実際以下の例だと@deprecatedと記載してあり、非推奨になっていることがソースコードから分かります。
node_modulesの中身

2. github から見に行く
ライブラリの github でソースコードを見ることが可能です。例えば、deprecated(非推奨)になったものを検索しようと思った時、github の右上の検索欄に@deprecatedと入力すればソースコードから探すことができます。
githubでの探し方
これでもいいのですが、個人的には VSCode みたいにソースコードが見れた方が検索しやすいです。
実は web 上で VSCode を開く方法がありまして、それはリポジトリの画面で.を押すだけです。
そうすると、以下のように web 上で VSCode が開きます。(参考記事)
※ちなみに、github.comというドメインをgithub1s.comと書き換えて VSCode を開くこともできます。(参考記事
webのVSCode
VSCode が開けば左上の検索欄に@deprecatedと入力すれば、非推奨になったものを網羅的に見ることができます。
例えば、今回orUpdateの書き方が変わっていましたが、どのように変わったのかは以下のようにソースコード内にちゃんと記載されています。
orUpdateの変更点

まとめ

TypeORM v0.3へのバージョンアップ大変です。この記事では全ては網羅していないので参考程度に読んでもらい、実際はリリースノートやソースコードを見ながらバージョンアップを行うことをおすすめします。

Discussion