TypeORM v0.3への移行について
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 へのコネクションの接続方法変更
Connection
→DataSource
に変更(Connection は非推奨に))
@Module({
imports: [
...,
TypeOrmModule.forRootAsync({
...,
connectionFactory: async (config) => {
const connection = new Connection(config)
await connection.connect()
return connection
}
})
]
})
@Module({
imports: [
...,
TypeOrmModule.forRootAsync({
...,
dataSourceFactory: async () => {
const dataSource = new DataSource({
type: "postgres",
...
});
await dataSource.initialize()
return dataSource
}
})
]
})
- Custom Repository の定義方法変更
EntityRepository
,getCustomRepository
が非推奨に
@EntityRepository(User)
export class UserRepository extends Repository<User> {
async findUser(id: string): Promise<User> {
return await this.find({ where: { id }});
}
}
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 は非推奨に
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)
}
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 の
select
やrelations
のプロパティの指定方法が配列からオブジェクトリテラルに変更
const users = await userRepository.find({
select: ['id', 'name', 'address.id']
})
const tasks = await taskRepository.find({
relations: ['users']
})
const users = await userRepository.find({
select: {
id: true,
name: true,
address: {
id: true
}
}
})
const tasks = await taskRepository.find({
relations: {
users: true
}
})
- パラメータなしの
findOne
廃止
const user = await userRepository.findOne()
const [user] = await userRepository.find()
-
findOne(id)
廃止
代わりにfindOneBy({ id: xxx })
を使用
const user = await userRepository.findOne(id)
const user = await userRepository.findOneBy({ id })
-
findOne
,findOneOrFail
,find
,count
,findAndCount
はパラメータに FindOptions しか受け付けないように
=findOne({ name: xxx })
みたいな where を省いた記述ができない
代わりにfindOneBy
,findOneByOrFail
,findBy
,countBy
,findAndCountBy
が追加
const user = await userRepository.findOne(id)
const users = await userRepository.find(id)
const user = await userRepository.findOneBy(id)
const users = await userRepository.findBy(id)
-
findByIds
が非推奨に
代わりにfindBy
とIn
を組み合わせて使用
const users = await userRepository.findByIds(ids)
const users = await userRepository.findBy({ id: In(ids) })
-
findOne
とQueryBuilder.getOne()
は DB に値が見つからなかった時、undefined
ではなくnull
を返すように変更(null を返す方が理にかなっている)
const user: Promise<User | undefined> = await userRepository.findOne(id)
const user: Promise<User | null> = await userRepository.findOneBy(id)
-
find*
メソッドで使用される where の値として null がサポートされない
代わりにIsNull()
を明示的に使用
const usersWithoutAddress = await userRepository.find({ where: { address: null }})
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
の書き方が変更(※参考)
await dataSource
.createQueryBuilder()
.insert()
.into(User)
.values([{
firstName: '田中',
lastName: '太郎',
externalId: 1,
}])
.orUpdate({
overwrite: ['firstName', 'lastName'],
conflict_target: ['externalId']
})
.execute()
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 内の TypeORM のコードに遷移できます。実際以下の例だと@deprecated
と記載してあり、非推奨になっていることがソースコードから分かります。
2. github から見に行く
ライブラリの github でソースコードを見ることが可能です。例えば、deprecated(非推奨)になったものを検索しようと思った時、github の右上の検索欄に@deprecated
と入力すればソースコードから探すことができます。
これでもいいのですが、個人的には VSCode みたいにソースコードが見れた方が検索しやすいです。
実は web 上で VSCode を開く方法がありまして、それはリポジトリの画面で.
を押すだけです。
そうすると、以下のように web 上で VSCode が開きます。(参考記事)
※ちなみに、github.com
というドメインをgithub1s.com
と書き換えて VSCode を開くこともできます。(参考記事)
VSCode が開けば左上の検索欄に@deprecated
と入力すれば、非推奨になったものを網羅的に見ることができます。
例えば、今回orUpdate
の書き方が変わっていましたが、どのように変わったのかは以下のようにソースコード内にちゃんと記載されています。
まとめ
TypeORM v0.3へのバージョンアップ大変です。この記事では全ては網羅していないので参考程度に読んでもらい、実際はリリースノートやソースコードを見ながらバージョンアップを行うことをおすすめします。
Discussion