【備忘録】NestJS + TypeORM 0.2系 でCRUDするまで 【古いです】

# はじめに
**Nest.js + TypeORM** についての **備忘録** です。

# 環境
**Nest V8** なので古いと思いますが、**modular architecture** を学習する分には大丈夫かと思います。
**TypeORM V2** なのでmigration周りが少し違うようです。
いつか書き直します・・・


## NestJSのスケルトン作成
・**nest new ${プロジェクト名}** で作成する。

・ex) **flea-market** というプロジェクト名で作成する。
```console
nest new flea-market
```

## Nestの3つのコア要素
・**Module** -> **Controller** と **Service** をまとめて **Nestに登録する** 役割を持つ
・**Controller** -> **ルーティングする** 役割を持つ
・**Service** -> **実装したいロジックを定義する** 役割を持つ

3つのコア要素を使って **エンドポイントを作る**。

## Module概要
・ロジックとルーティングをまとめる **論理的な構造** というイメージ

1.**class** に **Moduleデコレーター** をつける。
2.**Moduleデコレーターの引数** に **オブジェクト** を渡す。
3.**オブジェクトのプロパティ** は 
**providers** -> **Injectableデコレータ** がついたクラスを渡す。
**controllers** -> **Controllerデコレータ** がついたクラスを渡す。
**imports** -> モジュールの中で使う**外部のモジュール** を渡す。
**exports** -> 外部のモジュールに **エクスポートしたいもの** を渡す。

```ts
@Module({
  imports: [ItemsModule],
  controllers: [],
  providers: [],
})
```

### Module作成コマンド
・**nest g module ${モジュール名}** で作成する。

・ex) **items** というモジュール名で作成する。

```console
nest g module items
```
## Controller概要
・**ルーティングの機能** を担っている。

1.**class** に **Controllerデコレーター** をつける。
2.**Controllerデコレーターの引数** に **文字列** を渡す。
これが **パス と コントローラー** との紐づけになる。
3.**Controllerデコレーター** と紐づけた **classのメソッド** に 
**HTTPメソッドデコレーター** をつける。

```ts
@Controller('items')
export class ItemsController {
  @Get()
  findAll() {
    return 'これはfindAllメソッドです。';
  }
}
```

### Controller作成コマンド
・**nest g controller ${コントローラー名}** で作成する。

・ex) **items** というコントローラー名で作成する。

```console
nest g controller items
nest g controller items --no-spec //テスト用のファイルを作成しない
```

## Service概要
・**実装したいロジックを定義する** 役割を持つ

・**コントローラー** から呼び出して、**ルーティングと機能** を紐づける。

1.**class** に **Injectableデコレーター** をつける。
2.**Injectableデコレーター** と紐づけた **classのメソッド** に
**実装したいロジックをメソッドとして** 定義する。

```ts
@Injectable()
export class ItemsService {
  findAll() {
    return 'これはItemsServiceです。';
  }
}
```

## ControllerからServiceを利用する
1.**Module** の **providers** に **Service** を登録する。
これにより **Nestが勝手にDI** してくれるようになる。

```ts
@Module({
  controllers: [ItemsController],
  providers: [ItemsService],
})
export class ItemsModule {}
```

2.**Controllerの中にconstructor** で **Serviceを引数** に取る。
これにより **Controller** から **Service** を利用出来る。

```ts
@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}
  @Get()
  findAll() {
    return this.itemsService.findAll();
  }
}
```
### Service作成コマンド
・**nest g service ${サービス名}** で作成する。

・ex) **items** というコントローラー名で作成する。

```console
nest g service items
nest g service items --no-spec // テスト用のファイルを作成しない
```

## DI(Dependency Injection/依存性の注入)について
**クラスAがクラスBに依存している** 時に、
**Aの中でBをインスタンス化せずに**、
**Aの外** で **Bをインスタンス化** し、Aに渡して実装します。

このように **外部から依存しているオブジェクト** を **渡すパターン** を **DI** と呼ぶ。

## ダイナミックルーティング + ルーティングパスの取得
・**HTTPデコレータ** に対して 
**:${パス}の文字列** を渡すと **ダイナミックルーティング** が出来る。

・ハンドラの引数に 
**Paramデコレータの引数に渡したダイナミックルーティングのパス** と
**パスの文字列** を引数とすることで取得出来る。

・**/items/${id}** への **GETリクエスト** の例を下記に示します。

```ts
@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  // ダイナミックルーティング
    @Get(':id')
    findById(@Param('id') id: string): Item {
      return this.itemsService.findById(id);
    }
}
```

## Post送信のBodyを取得する
・ハンドラの引数に **Bodyデコレータ** と **引数** を渡して取得する。

・**/items** への **POSTリクエスト** の例を下記に示します。 

```ts
@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  @Post()
  create(
    @Body('id') id: string,
    @Body('name') name: string,
    @Body('price') price: number,
    @Body('description') description: string,
  ): Item {
    const item: Item = {
      id,
      name,
      price,
      description,
      status: ItemStatus.ON_SALE,
    };

    return this.itemsService.create(item);
  }
}
```

## DTO(Data Transfer Object)について
・**データの受け渡し**に使われるオブジェクトのこと

・ex)**DBとモデル間** でのデータやり取り・**リクエストオブジェクト** からのデータ受け取り

## DTOの作り方
・ex) **CreateItemDto** というDTOの定義を下記に示します。
```ts
export class CreateItemDto {
  id: string;
  name: string;
  price: number;
  description: string;
}
```

### DTOを使ってBodyを取得する
・**Post送信のBodyを取得する** ではデコレータに引数を渡す必要がありますが、
**DTOを使ってBodyを取得する**時はデコレータに引数を渡す必要がありません。

下記に **DTOを使ったBody取得** の例を示します。

```ts
@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  @Post()
  create(@Body() CreateItemDto: CreateItemDto): Item {
    return this.itemsService.create(CreateItemDto);
  }
}
```

## Nestでのバリデーション実装
・Nestでのバリデーションには **pipe** を使います。 
・pipeでは **バリデーション・データの変換** が可能です。

### バリデーション用の組み込み関数
・**ValidationPipe** -> 入力のバリデーション
・**ParseIntPipe** -> 入力を整数型に変換する
・**ParseBoolPipe** -> 入力をBoolean型に変換する
・**ParseUUIDPipe** -> 入力をUUID型に変換する
・**DefaultValuePipe** -> 入力がnull・undefinedの時にデフォルト値を与える

### バリデーションの適用箇所
・**ハンドラ** に対して適用する
・**パラメータごと** に対して適用する
・**グローバル** に対して適用する

・ex) **パラメータごと** に対してバリデーションを適用する例を下記に示します。

```ts
@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  // ダイナミックルーティング
  @Get(':id')
  findById(@Param('id', ParseUUIDPipe) id: string): Item {
    return this.itemsService.findById(id);
  }
}
```
### DTOにバリデーションを実装
・**バリデーションルール** はDTOに実装します。

```ts
import { Type } from 'class-transformer';
import { IsInt, IsNotEmpty, IsString, MaxLength, Min } from 'class-validator';

export class CreateItemDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(40)
  name: string;

  @IsInt()
  @Min(1)
  @Type(() => Number)
  price: number;

  @IsString()
  @IsNotEmpty()
  description: string;
}
```

・バリデーションは **グローバルに適用** します。

```ts
async function bootstrap() {
  // 引数に渡したものがルートモジュールになる
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();
```

## レスポンスにビルトインされている例外処理を使う
・**ダイナミックルーティングされた値** をキーにDBからレコードを取得して、
レスポンスとして返すようなAPIを想定します。

・**NotFoundException** はビルトインされた例外処理であり、
そのまま使用することが出来ます。
その実装例を下記に示します。

```ts
@Injectable()
export class ItemsService {
  private items: Item[] = [];

  findById(id: string): Item {
    const found = this.items.find((item) => item.id === id);
    if (!found) {
      throw new NotFoundException();
    }

    return found;
  }
}
```
・また、ビルトインされた例外処理は公式から見ることが出来ます。

https://docs.nestjs.com/exception-filters#built-in-http-exceptions

## TypeORM
・**Entity** -> テーブルに対応するクラス
・**Repository** -> DB操作に対応するクラス

### TypeORMをNestにインストールする
・**TypeOrmModule** をインポートして適用する。

```ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'postgres',
      autoLoadEntities: true,
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
```

## Entityの作成方法
・**Entityデコレータ** をつけたクラスを定義して、その中にカラム名を記述する。

```ts
import { ItemStatus } from 'src/items/item-status.enum';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Item {
  // 主キーかつ自動採番であることを示すデコレータ
  @PrimaryGeneratedColumn('uuid')
  id: string;

  // 通常のカラムであることを示すデコレータ
  @Column()
  name: string;

  @Column()
  price: number;

  @Column()
  description: string;

  @Column()
  status: ItemStatus;

  @Column()
  createdAt: string;

  @Column()
  updatedAt: string;
}
```

### Entityを使ってmigrationする
1. **/ormconfig.js** でexportする
2. **migrationファイル** を作成する
3. **migration** を実行する 

```js:ormconfig.js
module.exports = {
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'postgres',
  password: 'postgres',
  database: 'postgres',
  autoLoadEntities: true,
  entities: ["dist/entities/*.entity.js"],
  migrations: ["dist/migrations/*.js"],
  cli: {
    entitiesDir: "src/entities",
    migrationsDir: "src/migrations"
  }
}
```

・ex) 下記はCreateItemというマイグレーションファイル名で作成している例です。
```console
npx typeorm migration:generate -n CreateItem
```

・ex) migrationを実行している例です。
```console
npx typeorm migration:run
```

## Repositoryの作成方法

・**EntityRepositoryデコレータ** をつけたクラスを定義する
・**デコレータ** の引数に **Entity** を渡す
・**Repository\<Entity>** を継承する

```ts
import { Item } from 'src/entities/item.entity';
import { EntityRepository, Repository } from 'typeorm';

@EntityRepository(Item)
export class ItemRepository extends Repository<Item> {
  async createItem(createitemDto: CreateItemDto): Promise<Item> {
    const { name, price, description } = createitemDto;

    // 親クラスの継承しているメソッドをthisでたどれる
    const item = this.create({
      name,
      price,
      description,
      status: ItemStatus.ON_SALE,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    });

    await this.save(item);

    return item;
  }
}
```

### RepositoryをServiceから使えるようにする
・**imports** に **TypeOrmmodule.forFeature([${Repository}])** を渡す

```ts
import { Module } from '@nestjs/common';
import { ItemsController } from './items.controller';
import { ItemsService } from './items.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ItemRepository } from './item.repository';

@Module({
  imports: [TypeOrmModule.forFeature([ItemRepository])],
  controllers: [ItemsController],
  providers: [ItemsService],
})
export class ItemsModule {}
```

・**Service** で **Repository** を呼び出す
ex) **constructor(private readonly itemRepository: ItemRepository) {}** の部分です

```ts
@Injectable()
export class ItemsService {
  constructor(private readonly itemRepository: ItemRepository) {}

  async create(createItemDto: CreateItemDto): Promise<Item> {
    return await this.itemRepository.createItem(createItemDto);
  }
}
```

# さいごに
学習させていただいたUdemy講座です。

https://www.udemy.com/course/nestjs-t/