Open9

TypeORM x NestJS についての備忘録

OkaKyoOkaKyo

ここでは、TypeORM を NestJS で使用する際の備忘録としてまとめています。
公式ドキュメントでは詳細に書かれていなかった部分を補足して書いていきたいと思います。

https://typeorm.io/#/

OkaKyoOkaKyo

ormconfig ファイルの設定

ormconfig を設定してDB との接続やマイグレーションについての設定を行います。
.js ファイルや .json ファイルを利用して設定できますが、.tsファイルで設定すると、
TypeScript で import 時に MODULE_NOT_FOUND‘ エラーが発生しないのでTypeScript で書いたほうがいいかもしれません。

また、NestJS では entitiesにパスを設定してEntity クラスを自動的に読み込めなかったので、直接 Entity クラスを import して読み込ませました。

// 2020/2/23 日追記
うまく読み込めなかった要因が docker-compse.yml の WORKDIR の場所を書き間違たために発生したことが判明したため、この部分の仕様を変更後、実装するとうまくいった


 export default {
  name: 'default',
  type: 'mysql',
  host: 'db',
  port: 3306,
  username: process.env.MYSQL_USERNAME,
  password: process.env.MYSQL_PASSWORD,
  database: process.env.MYSQL_DATABASE,
  synchronize: false,
  logging: true,
  entities: [
      entities: [
      __dirname +"/../../entities/**/*.entity.{js,ts}"
   ]
  ],
  migrations: ['./src/migrations/**/*.ts'],
  cli: {
      migrationsDir: './src/migrations',
  },
}

なお、Migration についての設定は後日記述します。

OkaKyoOkaKyo

Relation についてのまとめ

続いて、TypeORM での Relation ( One to Many, Many to One, Many to Many ) について書いていきます。Youtube や 公式ドキュメントでは、テーブルを構築する方法についての情報が多く掲載されているが、どのようにしてRelation 関係にあるテーブルの情報を追加、取得するか について詳細には掲載されていなかったので書いていきたいと思います。

https://typeorm.io/#/relations

OkaKyoOkaKyo

ここでは、 One to Many について
設定するEntity は次のようになっています。

  • User Entity
import { IsEmail,  IsInt,  MaxLength } from "class-validator";
import { TaskEntity } from "../../../task/dto/getter/task.entity";
import{Column, Entity,
       PrimaryGeneratedColumn, 
      Unique, OneToMany} from "typeorm";

@Entity("user")
@Unique(['email'])
export class UserEntity {

    @PrimaryGeneratedColumn()
    @IsInt()
    id:number;

    @Column({nullable:false})
    name: string;

    @Column({nullable:false})
    @IsEmail()
    email: string
    
    @OneToMany(()=>TaskEntity,task=>task.author)
    task?:Task[]

}

  • Memo Entity
import { IsInt, MaxLength} from "class-validator";
import { UserEntity } from "../../../user/dto/getters/user.entity";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";

@Entity('task')
export class TaskEntity {
    @PrimaryGeneratedColumn()
    @IsInt()
    id?:number

    @Column({nullable:false,length:100})
    @MaxLength(100)
    title:string

    @Column({type:'text',nullable:false})
    @MaxLength(5000)
    description:string
    
    @ManyToOne(()=>UserEntity,user=>user.task)
    readonly author?:User
}
OkaKyoOkaKyo

これにより、SQL で次のようなテーブルが生成されます。

  • User Table
+--------------+--------------+------+-----+----------------------+--------------------------------+
| Field        | Type         | Null | Key | Default              | Extra                          |
+--------------+--------------+------+-----+----------------------+--------------------------------+
| id           | int(11)      | NO   | PRI | NULL                 | auto_increment                 |
| name         | varchar(255) | NO   |     | NULL                 |                                |
| email        | varchar(255) | NO   | UNI | NULL                 |                                |
+-------------+--------------+------+-----+----------------------+--------------------------------+
  • Task Table
+-------------+--------------+------+-----+----------------------+--------------------------------+
| Field       | Type         | Null | Key | Default              | Extra                          |
+-------------+--------------+------+-----+----------------------+--------------------------------+
| id          | int(11)      | NO   | PRI | NULL                 | auto_increment                 |
| title       | varchar(100) | NO   |     | NULL                 |                                |
| description | text         | NO   |     | NULL                 |                                |
| authorId    | int(11)      | YES  | MUL | NULL                 |                                |
+-------------+--------------+------+-----+----------------------+--------------------------------+

OkaKyoOkaKyo

データを追加

ドキュメント上では、Relation 関係にあたるデータの挿入について、

const photo1 = new Photo();
photo1.url = "me.jpg";
await connection.manager.save(photo1);

const photo2 = new Photo();
photo2.url = "me-and-bears.jpg";
await connection.manager.save(photo2);

const user = new User();
user.name = "John";
user.photos = [photo1, photo2];
await connection.manager.save(user);

と設定しています。

User 情報を新規に登録する際、ORM 側で条件に合うデータを検索し、発見したデータのID を呼び出して追加する仕様になっているようです。

そのため、登録するために findOne() を使ってあらかじめ呼び出さずに引数をひとつ ( Id など)をインスタンス化したオブジェクトに代入したものを追加するといいです、

OkaKyoOkaKyo

これをもとに、Task の追加についての関数は次のようにしました。


exort class TaskService(){
// (省略)
public async createTask(
        @Params('userId') userId:number,
        @Params('task') task:TaskEntity
    ):Promise<TaskEntity> {
        const author = new User()
        let setTask = new Task()
        author.id=userId;
        setTask = {
            ...task,
            author
        }
        return this.taskService.saveTask(setTask);
}
OkaKyoOkaKyo

データの取得

Relation 関係にある情報を呼び出すとき、findOne({relation:[***]}) として、relation に結合したい情報を設定するのですが、呼び出す引数は Entity で設定された引数 をString 型で入力してください。

呼び出す際、 Module で Relation 関係にある Entity を注入する必要はありません。

そのため、 Task 情報に作成者(User)のデータを呼び出すなら


@Injectable()
export class TaskService {
    constructor(
        @InjectRepository(Task)
        private readonly taskRepository:Repository<Task>
    ){}
   public async findOneByTaskId (id:number){
       return this.taskRepository.findOne(id,{relations:['author']})
   }

と実装します。