Dev Study
← 解説「リポジトリでCRUD」に戻る

サンプルコードで身につける: リポジトリでCRUD

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

1リポジトリを注入する最小形

@InjectRepository(User) でエンティティ専用のリポジトリをサービスに注入する最小例です。自分で new せずフレームワークから受け取る点は通常の DI と同じで、find() ひとつで全件取得ができます。

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 readonly usersRepo: Repository<User>,
  ) {}

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

2forFeature でリポジトリを使えるようにする

リポジトリ注入を有効にするには、その機能モジュールで TypeOrmModule.forFeature([User]) を imports に登録する必要があることを示す例です。これを忘れると注入が解決できずに起動エラーになる、頻出のつまずきどころです。

import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./user.entity";
import { UsersService } from "./users.service";
import { UsersController } from "./users.controller";

@Module({
  // これがないと @InjectRepository(User) が解決できない
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

3save() が INSERT と UPDATE を兼ねる

save() は id を持たないオブジェクトなら INSERT、既存の id を持つなら UPDATE になります。この「1つのメソッドが両方を兼ねる」挙動を、id の有無で分岐するインメモリ実装としてプレーン TypeScript で再現した例です。

TypeScript

4findOne の null を 404 に変換する

findOne() は該当行がないと null を返すため、見つからない前提の分岐を必ず入れます。null のまま放置すると後続で実行時エラーになりがちで、NotFoundException に変換して 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 {
  constructor(
    @InjectRepository(User)
    private readonly usersRepo: Repository<User>,
  ) {}

  async findOne(id: number): Promise<User> {
    const user = await this.usersRepo.findOne({ where: { id } });
    if (!user) {
      // null を放置せず明示的に 404 へ変換する
      throw new NotFoundException("User " + id + " が見つかりません");
    }
    return user;
  }
}

5ページネーション付き一覧 + 総件数

一覧 API の実務定番である、件数を区切って返すページネーションの例です。findAndCount() は「そのページの行」と「条件に合う総件数」を同時に返すため、フロントが総ページ数を計算するのに必要な情報を1回の呼び出しで揃えられます。

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 readonly usersRepo: Repository<User>,
  ) {}

  async paginate(page: number, perPage: number) {
    // findAndCount は [行の配列, 総件数] を同時に返す
    const [items, total] = await this.usersRepo.findAndCount({
      skip: (page - 1) * perPage, // 読み飛ばす件数
      take: perPage,              // 取得する件数
      order: { id: "DESC" },
    });
    return { items, total, totalPages: Math.ceil(total / perPage) };
  }
}