NestJS+TypeORMでバックエンド構築 PART2 【リレーション、ER図自動作成】

PART1からの続きから実装していきます。

https://qiita.com/ms2geki/items/6bf1f1740ed1b8bd9905

# 実装

## -1- PhotosModuleをCLIで作成

```shell
nest g m photos
nest g s photos
nest g controller photos
```

## -2- photo.entity.tsを作成

```typescript:photo.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from '../users/user.entity';

@Entity()
export class Photo {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  url: string;

//  ()=>Userはリレーション先の型、(user)=>user.photosはリレーション先の型のどのプロパティがPhoto型なのかを表しています。
  @ManyToOne(() => User, (user) => user.photos)
  user: User;
}
```

## -2- user.entity.tsを編集

```typescript:user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { Photo } from '../photos/photo.entity';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column)(
  lastName: string;

  @Column({ default: true })
  isActive: boolean;

  @OneToMany(() => Photo, (photo) => photo.user)
  photos: Photo[];
}
```

## -3- アプリケーションを起動してphotoテーブルを確認
```shell
npm run start:dev
```
アプリケーションを起動してDBを確認すると、photoテーブルにuserIdというカラムが作成されていることがわかります。このカラムはuserテーブルのidカラムと対応しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/13f61372-0aed-da97-fbd7-5531d4fe338a.png)

## -4- photo.entity.tsを編集
以下のようにphoto.entity.tsを編集して、userIdを反映させます。

```photo.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from '../users/user.entity';
import {Optional} from "@nestjs/common";

@Entity()
export class Photo {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  url: string;

  @ManyToOne(() => User, (user) => user.photos)
  user: User;

  //追加
  @Column()
  userId: number;
}
```

## -5- photos.controller.tsとphotos.service.tsを編集

```typescript:photos.controller.ts
import { Controller, Post, Req } from '@nestjs/common';
import { PhotosService } from './photos.service';
import { Photo } from './photo.entity';

@Controller('photos')
export class PhotosController {
  constructor(private photoService: PhotosService) {}

  @Post('save')
  async savePhoto(@Req() req): Promise<Photo[]> {
    return this.photoService.savePhoto(req.body);
  }
}
```

```typescript:photos.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Photo } from './photo.entity';
import { Repository } from 'typeorm';

@Injectable()
export class PhotosService {
  constructor(
    @InjectRepository(Photo)
    private photoRepository: Repository<Photo>,
  ) {}
  savePhoto(photo: Photo[]) {
    return this.photoRepository.save(photo);
  }
}
```

## -6- users.controller.tsとusers.service.tsを編集

```typescript:users.controller.ts
import {Controller, Get, Param, Post, Req} from '@nestjs/common';
import { UsersService } from './users.service';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './user.entity';
import { Repository } from 'typeorm';

@Controller('users')
export class UsersController {
  constructor(private usersService: UsersService) {}

  @Get('')
  async findAllUser(): Promise<User[]> {
    return this.usersService.findAll();
  }

  @Get(':id')
  async findUserById(@Param() param): Promise<User> {
    return this.usersService.findOne(param.id);
  }

  @Get('delete/:id')
  async deleteUserById(@Param() param) {
    return this.usersService.remove(param.id);
  }

  //追加
  @Post('save')
  async saveUser(@Req() req) {
    return this.usersService.saveUser(req.body);
  }
}
```

```typescript:users.service.ts
import {Repository} from "typeorm";
import {InjectRepository} from "@nestjs/typeorm";
import {Injectable} from "@nestjs/common";
import {User} from "./user.entity";

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }

  findOne(id: string): Promise<User> {
    return this.usersRepository.findOne(id);
  }

  async remove(id: string): Promise<void> {
    await this.usersRepository.delete(id);
  }

  //追加
  async saveUser(user: User) {
    console.log(user);
    await this.usersRepository.save(user);
  }
}
```

## -7- userテーブルとphotoテーブルにRESTAPI経由でレコードを追加

user追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/849eb962-05f1-5d31-345d-fccb8664a0c8.png)


photo追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/4b3a7792-f7f3-9edb-41ca-ce6b25872c39.png)


postmanで以下のリンクをimportするとすぐにRESTAPIを叩けます。

https://www.getpostman.com/collections/a80875ea7db91a5ff8d9

## -8- DBを確認

photoレコードが追加されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/da12cf54-b0e1-67d1-3d08-e98a0efda293.png)


## -9- users.service.tsを編集

```typescript:users.service.ts
import {Repository} from "typeorm";
import {InjectRepository} from "@nestjs/typeorm";
import {Injectable} from "@nestjs/common";
import {User} from "./user.entity";

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }
  
  //relationを追加
  findOne(id: string): Promise<User> {
    return this.usersRepository.findOne(id, { relations: ['photos'] });
  }

  async remove(id: string): Promise<void> {
    await this.usersRepository.delete(id);
  }

  async saveUser(user: User) {
    console.log(user);
    await this.usersRepository.save(user);
  }
}
```

## -10- localhost:3000/users/1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/919d3d4f-b581-d484-37da-d55f8eeaa259.png)
**photoの情報も取得できています！**

# ER図自動作成

```shell
npm install　typeorm-uml --save-dev
npx typeorm-uml --download=er-diagram.png
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659469/0b4c12dd-57b1-0827-3fe4-91d1e67f8e61.png)

これだけで、ER図を自動作成できます。

需要があればPART3で環境別の変数の設定や、GraphQLの導入を行なっていきたいと思います。