Dev Study
NestJS コース

27. リポジトリでCRUD

リポジトリは、1つのエンティティに対する読み書き(CRUD)をまとめて担当するオブジェクトです。TypeOrmModule.forFeature([User]) でそのエンティティのリポジトリを使えるようにし、サービスのコンストラクタで @InjectRepository(User) を付けて注入します。これは既習の DI の一種で、自分で new せずフレームワークからリポジトリを受け取る点は通常のサービス注入と同じです。

実務では find()(全件)・findOne()(条件で1件)・save()(新規作成と更新の両方)・delete()(削除)といったメソッドを組み合わせて使います。コントローラには処理を書かず、リポジトリ操作はサービスに寄せるという既習の役割分担をそのまま踏襲します。メソッド名や細部は ORM ごとに違っても、「エンティティ専用の窓口を通して CRUD する」という構造は共通です。

つまずきポイントは save() の挙動です。id を持たないオブジェクトを渡すと新規作成(INSERT)、既存の id を持つオブジェクトを渡すと更新(UPDATE)になり、1つのメソッドが両方を兼ねます。また findOne() は該当なしのとき null を返すため、見つからない前提で 404 を返すなどの分岐を必ず入れます。

サンプルコード(フレームワーク環境が必要なため表示のみ)

import { Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { User } from "./user.entity";

@Injectable()
export class UsersService {
  // エンティティ専用のリポジトリを注入(DI の一種)
  constructor(
    @InjectRepository(User)
    private readonly usersRepo: Repository<User>,
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepo.find(); // SELECT * FROM users
  }

  async findOne(id: number): Promise<User> {
    const user = await this.usersRepo.findOne({ where: { id } });
    if (!user) throw new NotFoundException(); // null なら 404
    return user;
  }

  create(name: string, email: string): Promise<User> {
    // id なしで渡すと INSERT、id ありなら UPDATE になる
    return this.usersRepo.save({ name, email });
  }
}
サンプルコードで身につける(5本) →

学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。

公式ドキュメントで詳しく ↗