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) };
}
}