0
Help us understand the problem. What are the problem?

posted at

updated at

NestJS のチュートリアル、テーブルのリレーションを作る

UserTable の準備

User Entity を追加し、Book Entity とリレーションを作ります。
まずは、ID と 名前だけをもつUser Entity を準備し、対応するUserTable を作成します。

UserEntity を作成する

src/entities ディレクトリにuser.entity.ts を作成し、User Entity を設定します。

src/entities/user.entity.ts
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

UserTable が作成されていれば成功です。
Untitled.png

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 を定義します。

src/users/user.repository.ts
import { User } from 'src/entities/user.entity';
import { EntityRepository, Repository } from 'typeorm';

@EntityRepository(User)
export class UserRepository extends Repository<User> {}

UsersModule のimports プロパティにUserRepository を登録します。

src/users/users.module.ts
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() メソッドを記述します。

src/users/user.repository.ts
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() メソッドを記述します。

src/users/users.service.ts
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 で呼び出せるように調整します。

src/users/users.service.ts
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 のリレーションを定義します。

src/entities/user.entity.ts
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 も追加します。

src/entities/book.entity.ts
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 が追加されていれば成功です。
Untitled.png

CreateBookDto の調整

認証機能を利用して、Book Entity 内のuserId に値を渡すこともできますが、今回は簡単に動作確認だけをしたいので、そのままリクエストの中にuserId の値を渡す実装にします。

CreateBookDto にuserId を追加します。

src/books/dto/create-book.dto.ts
import { BookStatus } from '../book-status.enum';

export class CreateBookDto {
  name: string;
  status: BookStatus;
  userId: string;
}

BooksController からにuserId の値を受け取れるようにBookRepository を調整します。

src/books/book.repository.ts
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 リクエストを送ります。
Untitled.png
BooksTable のuserId に値が保存されていれば成功です。
Untitled (1).png

リレーションを使ったBook の取得

最後に、User のID と一致するuserId を持つBook を取得する処理を実装していきます。

BookRepository の登録

今回は、UsersService の中に検索機能を持たせます。

BookRepository をUsersService 内で使用できるように、UsersModule のimports プロパティに登録します。

src/users/users.module.ts
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 をインスタンス化します。

src/users/users.service.ts
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 に記述します。

src/users/users.service.ts
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 としました。

src/users/users.controller.ts
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 の配列が取れれば完成です。
Untitled.png

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?