NestJS導入 TypeORM編
おはようございます!スペースマーケットの小見と申します。
NestJS導入ブログ第2回、今回はTypeORMの初期設定について書いていきます。
基本は公式に乗っかり+αとして
環境変数の設定とrailsDBへの対応を記述していこうと思います。
※この記事は基本的にyarnで書かせておりますのでこの点ご注意ください。
※この記事はRailsで生成されたDBを前提としてますのでこの点ご注意ください。
この記事の最終形態を用意いたしましたので、気になる方はこちらもご確認頂ければと思います。
https://github.com/SeijiOmi/nestjs-example/tree/feature/add_user_model
準備
では早速TypeORMをNestJSプロジェクトにインストールしていきましょう。
yarn add @nestjs/typeorm typeorm mysql2
ついでに後々、環境変数を設定するためのライブラリもインストールしておきましょう。
yarn add @nestjs/config
これでインストールは完了です。
初期設定
まずは.envファイルから環境変数を取得する設定を行います。
お手持ちのapp.module.ts内のimpoertsにConfigModuleを設定してください。
この設定だけでプロジェクト配下に設定されている.envファイルから環境変数が読み込まれます。
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot(),
],
})
export class AppModule {}
.envは以下のように設定を行います。
(内容はご自身のDB情報を記載ください。)
DB_HOST="localhost"
DB_PORT="3306"
DB_NAME="development"
DB_USER="root"
DB_PASS=""
続いてこの環境変数を基にデータベースの設定を行っていきます。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
UsersModule,
ConfigModule.forRoot(),
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASS,
synchronize: false,
}),
],
})
export class AppModule {}
process.envを補足しておきますと、環境変数を読み込む機能となっており、
process.env.DB_HOSTでDB_HOST環境変数(.envで設定した値)を取得することが出来ます。
ここで注意しなければ行けない点としては、マイグレーションを自動的に行わない場合は、
synchronize: falseで設定してくださいtrueで設定するとDBの項目が気が付かない間に消えたりすることが発生しますので、ご注意ください。
モデルの追加
今回はUserモデルを作って行こうと思います。
まずはDBとマッピングするためのentityを記載していきます。
以下のようにuserテーブルに設定されているDB項目を記載することで、
データの取得が可能になります。
src/user/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
email: string;
}
モジュールの設定を行います。
注目するポイントとしては、TypeOrmModule.forFeature([User])です。
この記述を行うことで、UsersService内でUserエンティティが利用可能になります。
src/user/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
})
export class UsersModule {}
サービスの設定を行います。
注目ポイントは private usersRepository: Repository<User>です。
この様に定義することで、userテーブルを操作することに特化したリポジトリが簡易的に作成されます。
例えば、this.usersRepository.findOne(id);
で主キー(今回だとid)を基にUserデータを1件取得出来る様になります。
src/user/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findOne(id: string): Promise<User> {
return this.usersRepository.findOne(id);
}
}
最後に上記で作成したUserModuleをapp.module.tsに登録していきましょう。
追加している部分はUsersModuleとentities: [User]となります。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { UsersModule } from './user/users.module';
@Module({
imports: [
UsersModule,
ConfigModule.forRoot(),
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASS,
synchronize: false,
entities: [User],
}),
],
})
export class AppModule {}
以上で一旦モデルの設定は終了となります。
RailsDBへの対応
TypeORMとRailsDB(Active Record)では標準でサポートするテーブル名項目名が異なります。
TypeORM
テーブル名(スネークケース&単数形)
user_photo
項目名(キャメルケース)
userPhotoId
Active Record
テーブル名(スネークケース&複数形)
user_photos
項目名(スネークケース)
user_photo_id
ということで、現状のままでは、データ取得が実行されても、以下の様なエラーが出てしまいます。
QueryFailedError: Table 'development.user' doesn't exist
そこで、TypeORMの設定をカスタマイズする方法をご紹介します。
内容は、こちらの記事をまるまる参考にさせていただいております。
長いですが、以下のような設定ファイルを作成します。
src/customNamingStrategy.ts
import * as pluralize from 'pluralize';
import { DefaultNamingStrategy, NamingStrategyInterface } from 'typeorm';
import { snakeCase } from 'typeorm/util/StringUtils';
export class CustomNamingStrategy
extends DefaultNamingStrategy
implements NamingStrategyInterface
{
tableName(targetName: string, userSpecifiedName: string): string {
return userSpecifiedName
? userSpecifiedName
: pluralize.plural(snakeCase(targetName));
}
columnName(
propertyName: string,
customName: string,
embeddedPrefixes: string[],
): string {
return customName ? customName : snakeCase(propertyName);
}
joinColumnName(relationName: string, referencedColumnName: string) {
return snakeCase(
pluralize.singular(relationName) + '_' + referencedColumnName,
);
}
joinTableName(
firstTableName: string,
secondTableName: string,
firstPropertyName: string,
secondPropertyName: string,
) {
return snakeCase(firstTableName + '_' + secondTableName);
}
joinTableColumnName(
tableName: string,
propertyName: string,
columnName: string,
) {
return snakeCase(
pluralize.singular(tableName) + '_' + (columnName || propertyName),
);
}
}
この設定ファイルをapp.module.tsに読み込ませます。
namingStrategy: new CustomNamingStrategy()が追加部分になります。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { UsersModule } from './user/users.module';
import { CustomNamingStrategy } from './customNamingStrategy';
import { User } from './user/user.entity';
@Module({
imports: [
UsersModule,
ConfigModule.forRoot(),
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASS,
synchronize: false,
entities: [User],
namingStrategy: new CustomNamingStrategy(),
}),
],
})
export class AppModule {}
以上でUserのデータが取得可能になりました。
お疲れさまでした!
まとめ
最初の部分はチュートリアル通りで問題なく進めたのですが、
命名の違いに気づくまで大分時間を費やしてしまいました。
RailsからNestJSを検討中で躓いている方の手助けになれば幸いです。
最後に宣伝です。
スペースマーケットでは、エンジニアを募集中です。
スペースシェアが当たり前になる世界観を目指して日々開発を進めています。プロダクトにご興味があれば是非、下記リンクよりご応募お待ちしております。
お仕事や作業に集中できるスペースをワークルームと言うカテゴリで検索出来るようになりました。
出先で少し作業したい場合などに便利なので機会あれば見てみて下さい。