TypeORMの導入でつまづいたこと
はじめに
TypeORMを使ったWebアプリを一から作ろうとしたところ、何度かエラーでつまづいたところがありましたので備忘録として記事に残しておきます。
前提事項
環境は以下の通りです。
- Node.js 18.16.0
必要なモジュールは公式のインストール手順に沿ってインストールされているものとします。package.jsonの内容は以下の通りです。
"devDependencies": { "@types/node": "^20.9.3", "@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/parser": "^6.13.1", "eslint": "^8.54.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.1", "prettier": "3.1.0", "ts-node": "^10.9.1", "typescript": "^5.3.2" }, "dependencies": { "pg": "^8.11.3", "reflect-metadata": "^0.1.13", "typeorm": "^0.3.17" }
またフォルダ構成は以下の通りです。
app ├─ node_modules ├─ src │ ├─ entity │ ├─ migration │ ├─ data-source.ts │ └─ index.ts ├─ package.json └─ tsconfig.json
エンティティを作成したら、The runtime will invoke the decorator with 2 arguments, but the decorator expects 1. エラー
entity
フォルダにUser.ts
を作成します。公式サイトのエンティティの例をそのまま記述します。
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm" @Entity() export class User { @PrimaryGeneratedColumn() id: number @Column() firstName: string @Column() lastName: string @Column() isActive: boolean }
すると、以下のようにエラーになってしまいました。
Unable to resolve signature of class decorator when called as an expression. The runtime will invoke the decorator with 2 arguments, but the decorator expects 1.
インストール手順には、TypeScriptの4.5以上を使うことと、tsconfig.json
に以下の設定を追加しすることが書かれています。
ちなみに、experimentalDecorators
は実験的なデコレーターを使えるようにするためのもので、emitDecoratorMetadata
はデコレーターのメタデータが生成されるようにするためのものです。
"experimentalDecorators": true, "emitDecoratorMetadata": true,
上記を確認してもエラーが解消されなかったため、更に調べていくと、tsconfig.json
のコンパイル対象の設定が誤っていました。
tsconfig.jsonは以下のようになっていました。
{ "compilerOptions": { "target": "es2022", "module": "commonjs", "lib": ["es2022"], "sourceMap": true, "outDir": "./dist", "rootDir": "./src", "strict": true, "moduleResolution": "node", "baseUrl": "src", "esModuleInterop": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/*"], "exclude": ["dist", "node_modules"], "compileOnSave": false }
include
を見ると、src
フォルダ配下をワイルドカードで指定していますが、src/entity
フォルダのように階層になっているフォルダが対象になっていませんでした。
そこで、include
を以下のように変更します。
"include": ["src/**/*"],
または、files
が指定されていなければデフォルトで**/*
が対象になるので、include
ごと削除します。
これでデコレーターに関するエラーは解消しました。
エンティティのプロパティで、typeorm Property 'id' has no initializer and is not definitely assigned in the constructor. エラー
デコレーターに関するエラーは解消しましたが、別の箇所でエラーになりました。
Property 'id' has no initializer and is not definitely assigned in the constructor.
クラスのプロパティがコンストラクタに設定されていないときに発生するエラーです。
このエラーはstrictPropertyInitialization
を有効にしているときに発生します。今回の設定だと、strict
を有効にしているため自動的にこの設定も有効になっています。
strictPropertyInitialization
を無効にすればエラーは解消されますが、エンティティ以外のクラスにも影響してしまいます。
そこで、以下のように!
または?
をつけることで、コンストラクタに設定されていなくても問題ないことをTypeScriptに伝えます。!
を使えば、必ず初期化されることを伝えます。?
を使えば、undefinedの可能性があることを伝えます。どちらを使うかは、DBの値がnullになる可能性があるかどうかで判断します。
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm" @Entity() export class User { @PrimaryGeneratedColumn() id!: number @Column() firstName?: string @Column() lastName?: string @Column() isActive!: boolean }
これでコンパイルエラーはなくなりました。
接続しようとして、Driver not Connected エラー
試しにDBに接続してみようと、index.tsを以下のようにしました。
import { AppDataSource } from './data-source' const test = async () => { const result = await AppDataSource.manager.query("SELECT 'hoge'") console.log(result) } void test()
インポートしているdata-source.tsは以下のような設定です。
import 'reflect-metadata' import { DataSource } from 'typeorm' export const AppDataSource = new DataSource({ type: 'postgres', host: 'localhost', port: 5434, username: 'fc_test', password: 'fc_test', database: 'fc_test', entities: ['src/entity/*.ts'], migrations: ['src/migration/*.ts'], logging: true, logger: 'file', }) AppDataSource.initialize() .then(() => { console.log('Data Source has been initialized!') }) .catch((err) => { console.error('Error during Data Source initialization', err) })
これで動かしてみると、エラーになってしまいました。
TypeORMError: Driver not Connected
よく見ると、DataSourceのinitializeメソッドはPromise型でした。DBにクエリを投げる前にDataSourceが初期化されていなかったようです。
そこで、クエリ前に初期化されるようにinitializeメソッドの位置を変更しました。
import { AppDataSource } from './data-source' const test = async () => { await AppDataSource.initialize() .then(() => { console.log('Data Source has been initialized!') }) .catch((err) => { console.error('Error during Data Source initialization', err) }) const result = await AppDataSource.manager.query("SELECT 'hoge'") console.log(result) } void test()
無事にDBに接続することができました。
おわりに
普段プロジェクトで何気なく触っているものでも、改めて一から作ってみると戸惑うことがたくさんあり、勉強になりました。
この記事がどなたかのお役に立てれば幸いです。