UserTable の準備
User Entity を追加し、Book Entity とリレーションを作ります。
まずは、ID と 名前だけをもつUser Entity を準備し、対応するUserTable を作成します。
UserEntity を作成する
src/entities ディレクトリにuser.entity.ts を作成し、User Entity を設定します。
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: string;
@Column()
name: string;
@Column()
createdAt: string;
@Column()
updatedAt: string;
}
マイグレーション の実行
一度アプリを起動し、entity ファイルをコンパイルします。
npm run start:dev
次に、マイグレーションファイル作成用のコマンドを実行します。
npx typeorm migration:generate -n CreateUser
src/migrations ディレクトリにマイグレーションファイルが追加されていれば、再度アプリを再起動します。
最後に、マイグレーション用のコマンドを実行します。
npx typeorm migration:run
Userのcreate 機能を作成する
User 用のresource を準備する
nest g コマンドを実行し、UsersModule、UsersController、UsersService を作成します。
nest g module users
nest g controller users --no-spec
nest g service users --no-spec
UserRepository を作成する
src/users ディレクトリにuser.repository.ts を作成し、UserRepository を定義します。
import { User } from 'src/entities/user.entity';
import { EntityRepository, Repository } from 'typeorm';
@EntityRepository(User)
export class UserRepository extends Repository<User> {}
UsersModule のimports プロパティにUserRepository を登録します。
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
@Module({
imports: [TypeOrmModule.forFeature([UserRepository])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
Create 機能の実装
UserRepository にUser 保存用のcreate() メソッドを記述します。
import { User } from 'src/entities/user.entity';
import { EntityRepository, Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
@EntityRepository(User)
export class UserRepository extends Repository<User> {
async createUser(createUserDto: CreateUserDto): Promise<User> {
const { name } = createUserDto;
const user = this.create({
name,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
await this.save(user);
return user;
}
}
また、UsersService にUserRepository で作成したcreateUser() メソッドを呼び出すためのcreate() メソッドを記述します。
import { Injectable } from '@nestjs/common';
import { User } from 'src/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UserRepository } from './user.repository';
@Injectable()
export class UsersService {
constructor(private readonly userRepository: UserRepository) {}
async create(createUserDto: CreateUserDto): Promise<User> {
return await this.userRepository.createUser(createUserDto);
}
}
最後に、UsersService のcreate() メソッドをUsersController で呼び出せるように調整します。
import { Body, Controller, Post } from '@nestjs/common';
import { User } from 'src/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
async create(@Body() createUserDto: CreateUserDto): Promise<User> {
return await this.usersService.create(createUserDto);
}
}
User とBook のリレーション追加
User Entity の調整
User Entity Book Entity に対して、OneToMany のリレーションを定義します。
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Book } from './book.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: string;
@Column()
name: string;
@Column()
createdAt: string;
@Column()
updatedAt: string;
@OneToMany(() => Book, (book) => book.user)
books: Book[];
}
Book Entity の調整
Book Entity にはUser Entity に対して、ManyToOne のリレーションを定義します。
また、リレーション関係にあるUser のId を保存するためのColumn も追加します。
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { BookStatus } from '../books/book-status.enum';
import { User } from './user.entity';
@Entity()
export class Book {
@PrimaryGeneratedColumn()
id: string;
@Column()
name: string;
@Column()
status: BookStatus;
@Column()
createdAt: string;
@Column()
updatedAt: string;
@ManyToOne(() => User, (user) => user.books)
user: User;
@Column()
userId: string;
}
マイグレーション
アプリを機動し直し、マイグレーションファイル作成用のコマンドを実行します。
npx typeorm migration:generate -n AddRelation
src/migrations ディレクトリにマイグレーションファイルが追加されていることを確認し、アプリを機動しなおします。
マイグレーション用のコマンドを実行します。
npx typeorm migration:run
BookTable にuserId が追加されていれば成功です。
CreateBookDto の調整
認証機能を利用して、Book Entity 内のuserId に値を渡すこともできますが、今回は簡単に動作確認だけをしたいので、そのままリクエストの中にuserId の値を渡す実装にします。
CreateBookDto にuserId を追加します。
import { BookStatus } from '../book-status.enum';
export class CreateBookDto {
name: string;
status: BookStatus;
userId: string;
}
BooksController からにuserId の値を受け取れるようにBookRepository を調整します。
import { Book } from 'src/entities/book.entity';
import { EntityRepository, Repository } from 'typeorm';
import { BookStatus } from './book-status.enum';
import { CreateBookDto } from './dto/create-book.dto';
@EntityRepository(Book)
export class BookRepository extends Repository<Book> {
async createBook(createBookDto: CreateBookDto): Promise<Book> {
// userId を追加する
const { name, userId } = createBookDto;
const book = this.create({
name,
status: BookStatus.RENTABLE,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
userId,
});
await this.save(book);
return book;
}
}
動作確認として、リクエストボディにuserId の値を渡し、POST リクエストを送ります。
BooksTable のuserId に値が保存されていれば成功です。
リレーションを使ったBook の取得
最後に、User のID と一致するuserId を持つBook を取得する処理を実装していきます。
BookRepository の登録
今回は、UsersService の中に検索機能を持たせます。
BookRepository をUsersService 内で使用できるように、UsersModule のimports プロパティに登録します。
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
import { BookRepository } from 'src/books/book.repository';
@Module({
imports: [TypeOrmModule.forFeature([UserRepository, BookRepository])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
UsersService 内でBookRepository をインスタンス化します。
import { Injectable } from '@nestjs/common';
import { BookRepository } from 'src/books/book.repository';
import { User } from 'src/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UserRepository } from './user.repository';
@Injectable()
export class UsersService {
constructor(
private readonly userRepository: UserRepository,
// BookRepository をインスタンス化する
private readonly bookRepository: BookRepository,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
return await this.userRepository.createUser(createUserDto);
}
}
検索機能の実装
リクエストで送られてきたUser のID と同じuserId を持つBook を検索する処理をUsersService に記述します。
import { Injectable, NotFoundException } from '@nestjs/common';
import { BookRepository } from 'src/books/book.repository';
import { Book } from 'src/entities/book.entity';
import { User } from 'src/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UserRepository } from './user.repository';
@Injectable()
export class UsersService {
constructor(
private readonly userRepository: UserRepository,
private readonly bookRepository: BookRepository,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
return await this.userRepository.createUser(createUserDto);
}
async findOwnedBooks(id: string): Promise<Book[]> {
const user = await this.userRepository.findOne(id);
if (!user) {
throw new NotFoundException();
}
const books = await this.bookRepository.find();
return books.filter((book) => book.userId === user.id);
}
}
UsersController にGET リクエストとUsersService のfindOwnedBooks() メソッドを紐付ける処理を記述ます。
リクエストのURL はusers/owned/:id としました。
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { Book } from 'src/entities/book.entity';
import { User } from 'src/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
async create(@Body() createUserDto: CreateUserDto): Promise<User> {
return await this.usersService.create(createUserDto);
}
@Get('/owned/:id')
async findOwnedBooks(@Param('id') id: string): Promise<Book[]> {
return await this.usersService.findOwnedBooks(id);
}
}
http://localhost:3000/users/owned/:id のパスにGet リクエストを送ってみて、渡したID と同じ値のuserId を持つBook の配列が取れれば完成です。