【NestJS】NestJSを使用してCRUD実装!

![logo-small.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/85ba3780-a0d8-9ce7-f55e-a150fdea6684.png)
### NestJSとは
NestJSとは、NodeJS環境で動くサーバーサイドアプリケーションフレームワークです。

基本的にTypeScriptでコーディングを行いますが、JavaScriptで書くことも可能です。

Expressをコアに作られているので、Expressの機能は備えつつ、テストフレームワークJestを標準装備していたり、NestCLIを利用して効率良く開発を進めることができたり、多くの機能を搭載しています。

今回は、DockerでPostgreSQLサーバを立ち上げ、NestJSを用いた商品管理のCRUD実装を行なっていこうと思います。

### 1. NestJSのプロジェクト作成
NestCLIを使用して、NestJSのプロジェクトを立ち上げます。

まずは以下コマンドで、NestCLIがインストールされているか確認します。
~~~bash
nest --version
~~~

バージョンが表示されれば、既にインストールされています。`command not found`などのエラーが出た場合はNestCLIがインストールされていないので、以下コマンドを実行してください。
~~~bash
npm i -g @nestjs/cli
~~~

インストールが完了したら、以下コマンドでNestJSのプロジェクトを立ち上げます。
~~~bash
nest new nest-tutorial
~~~
使用するパッケージマネージャーを選べますので、今回はnpmを選択してください
~~~bash
? Which package manager would you ❤️ to use? (Use arrow keys)
❯ npm 
  yarn
~~~
以下のようなログが出れば、NestJSプロジェクトの立ち上げ完了です!👏
~~~bash
                       Thanks for installing Nest 🙏
              Please consider donating to our open collective
                     to help us maintain this package.
                                         
                                         
               🍷  Donate: https://opencollective.com/nest
~~~

### 2. DB接続（PostgreSQL）

#### 2-1. PostgreSQLのサーバー起動
今回はPostgreSQLを利用します。

Dockerを使用してDBサーバーを立ち上げましょう。

以下Dockerファイルをルートディレクトリに置いてください。
~~~yaml:docker-compose.yml
version: '3.7'
services:

  postgres:
    image: postgres:14.2-alpine
    container_name: nest-postgres
    ports:
      - 5432:5432
    volumes:
      - ./docker/postgres/init.d:/docker-entrypoint-initdb.d
      - ./docker/postgres/pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
      POSTGRES_DB: postgres
    hostname: postgres
    restart: always
    user: root

  pgadmin:
    image: dpage/pgadmin4
    restart: always
    ports:
      - 81:80
    environment:
      PGADMIN_DEFAULT_EMAIL: test@test.com
      PGADMIN_DEFAULT_PASSWORD: password
    volumes:
      - ./docker/pgadmin:/var/lib/pgadmin
    depends_on:
      - postgres
~~~

以下コマンドでPostgreSQLのコンテナを立ち上げます。
~~~bash
docker-compose up --build
~~~



コンテナが立ち上がったら、ブラウザで[localhost:81](http://localhost:81/)にアクセスしてください。
以下のような画面が表示されれば、正常にPostgreSQLコンテナが立ち上がっています。
![スクリーンショット 2022-09-18 15.40.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/3fb9b161-b9d1-d9af-48ac-776db83092ab.png)

メールアドレスとパスワードはそれぞれDockerファイルに記載されてある`PGADMIN_DEFAULT_EMAIL`と`PGADMIN_DEFAULT_PASSWORD`の値を入力してください。

ログインできたら、`Dashbord`から`Add New Server`を選択し、それぞれ以下の情報を入力してください。
|  項目  |  入力内容  |
| ---- | ---- |
|  Name  |  nest-js-tutorial  |
|  Host name/address  |  postgres  |
|  Port  |  postgres  |
|  Maintenance database  |  postgres  |
|  Username  |  postgres  |
|  Password  |  postgres  |

`Save`ボタンを押して、以下のような画面が表示されたら、PostgreSQLサーバーの立ち上げが完了

![スクリーンショット 2022-09-19 14.50.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/edee972d-ffe0-7d11-28e2-abfc683b1ca3.png)



#### 2-2. ORM設定
では、立ち上げたPostgreSQLサーバーと、NestJSを接続していきましょう。

今回は、TypeORMというORMを使用します。

以下コマンドで、TypeORM、PostgreSQLのドライバーをインストールしてください。
~~~bash
npm i --save typeorm@0.2.45 @nestjs/typeorm@8.0.2 pg
~~~


ルートモジュール(`/src/app.module.ts`)を以下のように編集します。
TypeORMのモジュールをインポートし、`forRoot()`メソッドを`imports`の中に入れます。
~~~typescript:src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    TypeOrmModule.forRoot()
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
~~~

ルートディレクトリに`ormconfig.js`を作成し、以下のようなファイルを作成してください。
TypeORMはこのファイルを元にDB接続を行ってくれます。
~~~javascript: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',
  },
};
~~~


では、以下のNestCLIコマンドを実行して、実際にNestJSサーバーを立ち上げてみましょう。
~~~bash
npm run start:dev
~~~

以下なログが出力されて、エラーログっぽいものがでなければDB接続が完了です!
![スクリーンショット 2022-09-18 21.40.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/36bae89f-c8ad-f44d-5514-e0a6f183e747.png)

### 3. エンティティの作成
エンティティとは、データベースのテーブル毎に作成されるテーブル定義をするものです。

`typeorm`モジュールから`Entity`デコレーターをインポートして、classに付与することでエンティティクラスを作成することができます。

クラス内で、それぞれ`typeorm`モジュールからインポートしたデコレーターを使用して、カラム毎のルールを定義していきます。


~~~typescript:src/entities/item.entity.ts
import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm';

@Entity()
export class Item {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  name: string;

  @Column()
  price: number;

  @Column()
  createdAt: string;

  @Column()
  updatedAt: string;
}

~~~

### 4. マイグレーション実行
マイグレーションをおこなっていきます。
以下のTypeORMのCLIコマンドを使用して、マイグレーションファイルを作成します。
~~~bash
npx typeorm migration:generate -n CreateItemsTable -d src/migrations
~~~

実行すると、`src/migrations`ディレクトリ配下に、以下のようなファイルが生成されます。
~~~typescript:src/migrations/1663567364289-CreateItemsTable.ts
import {MigrationInterface, QueryRunner} from "typeorm";

export class CreateItemsTable1663567364289 implements MigrationInterface {
    name = 'CreateItemsTable1663567364289'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`CREATE TABLE "items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "price" integer NOT NULL, "createdAt" character varying NOT NULL, "updatedAt" character varying NOT NULL, CONSTRAINT "PK_ba5885359424c15ca6b9e79bcf6" PRIMARY KEY ("id"))`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`DROP TABLE "items"`);
    }

}
~~~

他バックエンドのフレームワークに詳しい方は、これからマイグレーションファイルにテーブル定義を記載していくと思われる方も多いかと思いますが、TypeORMでは、紐付くエンティティを見てマイグレーションを行なってくれるので、マイグレーションファイル自体を編集することは基本的にありません。

では、実際にマイグレーションを実行しましょう。
~~~bash
npx typeorm migration:run
~~~

以下のようなログが出たら、マイグレーション成功です!
~~~bash
query: SELECT * FROM current_schema()
query: CREATE EXTENSION IF NOT EXISTS "uuid-ossp"
query: SHOW server_version;
query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = 'public' AND "table_name" = 'migrations'
query: CREATE TABLE "migrations" ("id" SERIAL NOT NULL, "timestamp" bigint NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_8c82d7f526340ab734260ea46be" PRIMARY KEY ("id"))
query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = 'public' AND "table_name" = 'typeorm_metadata'
query: CREATE TABLE "typeorm_metadata" ("type" character varying NOT NULL, "database" character varying, "schema" character varying, "table" character varying, "name" character varying, "value" text)
query: SELECT * FROM "migrations" "migrations" ORDER BY "id" DESC
0 migrations are already loaded in the database.
1 migrations were found in the source code.
1 migrations are new migrations that needs to be executed.
query: START TRANSACTION
query: CREATE TABLE "items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "price" integer NOT NULL, "createdAt" character varying NOT NULL, "updatedAt" character varying NOT NULL, CONSTRAINT "PK_ba5885359424c15ca6b9e79bcf6" PRIMARY KEY ("id"))
query: INSERT INTO "migrations"("timestamp", "name") VALUES ($1, $2) -- PARAMETERS: [1663567364289,"CreateItemsTable1663567364289"]
Migration CreateItemsTable1663567364289 has been executed successfully.
query: COMMIT
~~~

テーブルが実際に作成されているか、[localhost:81](http://localhost:81/)にアクセスし、データベースを見てみましょう

横のタブから、`postgres`👉`Databases(1)`👉`Schemas(1)`👉`Tables(3)`の順に開くと、データベースのテーブルを見ることができます。

![スクリーンショット 2022-09-19 15.05.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/8a8c013f-19d3-5ffe-ea9e-537790bb795c.png)

`items`テーブルががありますね!

マイグレーションが無事に成功しました!👏


### 5. モジュール/コントローラー/サービスの作成

NestJSでは、<strong>モジュール</strong>、<strong>サービス</strong>、<strong>コントローラー</strong>の3つで、1つの機能を実装していきます。

#### 5-1. サービス作成
NestCLIコマンドでサービスを作成します。

NestCLIを利用してサービス/コントローラーを作成すると、テストファイルを自動生成してくれます。
今回はテストファイルは必要ないので、`--no-spec`オプションを付与して自動生成を無効にします。

~~~bash
nest generate service items --no-spec
~~~

srcディレクトリ配下に、itemsディレクトリが自動で作成され、その配下に`items.service.ts`が作成されます。
~~~typescript:src/items/items.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class ItemsService {}
~~~

#### 5-2. コントローラー作成
NestCLIコマンドでコントローラーを作成します。
~~~bash
nest generate controller items --no-spec
~~~
itemsディレクトリ配下に`items.controller.ts`が作成されます。
コンストラクターで、サービスクラスをDIしておきます。

~~~typescript:src/items/items.controller.ts
import { Controller } from '@nestjs/common';
import { ItemsService } from './items.service';

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


#### 5-3. モジュール作成
NestCLIコマンドでモジュールを作成します。
~~~bash
nest generate module items
~~~
itemsディレクトリ配下に`items.module.ts`が作成されます。
~~~typescript:src/items/items.module.ts
import { Module } from '@nestjs/common';
import { ItemsController } from './items.controller';
import { ItemsService } from './items.service';

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

#### 5-4. ルートモジュールに登録
ルートモジュール（`app.module.ts`）に登録することで追加した機能が使えるようになります。
`src/app.module.ts`で作成したitemsモジュールをインポートして、`imports`にモジュールクラス追加してください。

~~~typescript:src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
// モジュールをインポート!
import { ItemsModule } from './items/items.module';

@Module({
  imports: [
    TypeOrmModule.forRoot(),
    // 追加!!
    ItemsModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

~~~

これにて、itemsモジュールの機能が使えるようになりました👏

### 6. リポジトリの作成

#### 6-1. リポジトリクラスの作成
リポジトリクラスとは、DB操作を実際に行うメソッド群を持つクラスです。

`src/items`ディレクトリ配下に、`items.repository.ts`ファイルを作成して、以下のように編集します。

~~~typescript:src/items/items.repository.ts
import { Items } from 'src/entities/items.entity';
import { EntityRepository, Repository } from 'typeorm';

@EntityRepository(Items)
export class ItemsRepository extends Repository<Items> {}
~~~

`@EntityRepository()`デコレーターをインポートし、引数に紐付くエンティティを渡します。

`Repository`クラスを継承し、TypeORMのメソッド群を使用できるようにし、ジェネリクスに紐付くエンティティを設定します。

#### 6-2. モジュールにリポジトリを登録
モジュール（`items.module.ts`）にリポジトリクラスを登録します。

`imports`プロパティに`TypeOrmModule.forFeature()`メソッドを入れます。
`forFeature`の引数に作成したリポジトリクラスを含む配列にを入れます。

これでサービスでリポジトリをDIすることが可能になりました。
~~~typescript:src/items/items.module.ts
import { Module } from '@nestjs/common';
// 追加=============================================
import { TypeOrmModule } from '@nestjs/typeorm';
import { ItemsRepository } from './items.repository';
// ↑↑↑↑============================================
import { ItemsController } from './items.controller';
import { ItemsService } from './items.service';

@Module({
  // 追加=============================================
  imports: [TypeOrmModule.forFeature([
    ItemsRepository
  ])],
  // ↑↑↑↑============================================
  controllers: [ItemsController],
  providers: [ItemsService]
})
export class ItemsModule {}

~~~


#### 6-3. サービスでリポジトリクラスをDI
サービスクラス内でリポジトメソッドを使用できるように、コンストラクタでリポジトリをDIします。

~~~typescript:src/items/items.service.ts
import { Injectable } from '@nestjs/common';
// 追加=============================================
import { ItemsRepository } from './items.repository';
// ↑↑↑↑============================================

@Injectable()
export class ItemsService {
  // 追加=============================================
  constructor (private readonly itemsRepository: ItemsRepository) {}
  // ↑↑↑↑============================================
}
~~~

### 7. DTOの作成
DTOを定義していきます。

DTOとはプロパティ毎の型などを定義したものを集めたオブジェクトで、登録や更新処理を行う際に使います。

更に便利なのが、型定義と同時にバリデーションルールも設定することができます。

バリデーションルールを設定するために、以下のパッケージ`class-validator`をインストールしましょう。
~~~bash
npm i --save class-validator
~~~

`src/items`ディレクトリに`dto`ディレクトリを作り、その配下に`item.dto.ts`ファイルを作成します。

それぞれのプロパティの上に、`class-validator`からインポートしたデコレーターを使用してバリデーションルールを指定していきます。
こうすることで、リクエスト時にバリデーションルールに引っかかると、エラーが返るようになります。

~~~javascript:src/items/dto/item.dto.ts
import { IsNumber, IsString } from 'class-validator';

export class ItemDto {
  @IsString() // 文字であること
  name: string;

  @IsNumber() // 数値であること
  price: number;
}
~~~


### 8. 登録機能の作成（CREATE）


#### 8-1. リポジトリの編集
リポジトリに、登録処理を行うメソッド`createItem`を作成します。

TypeORMメソッド`create`で新たなデータオブジェクトを作成し、`save`で実際にデータベースに保存しています。

データベース操作処理は非同期で行われる為、メソッドの返り値はPromise型にして、ジェネリクスで紐付くエンティティクラスを指定します。

~~~typescript:src/items/item.repository.ts
import { EntityRepository, Repository } from 'typeorm';
// 追加=============================================
import { Items } from 'src/entities/items.entity';
import { ItemDto } from './dto/item.dto';
// ↑↑↑↑============================================

@EntityRepository(Items)
export class ItemsRepository extends Repository<Items> {
  // 追加=============================================
  async createItem (itemDto: ItemDto): Promise<Items> {
    const { name, price } = itemDto;

    const item = this.create({
      name,
      price,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    }); // データオブジェクトを作成

    await this.save(item); // 実際にDBに登録

    return item; // 作成したデータを返す
  }
  // ↑↑↑↑============================================
}
~~~


#### 8-2. サービスの編集

作成したリポジトリメソッド`createItem`を実行するメソッドを作成します。

引数には、作成したDTO型のオブジェクトを定義しましょう。

~~~typescript:src/items/items.service.ts
import { Injectable } from '@nestjs/common';
import { ItemsRepository } from './items.repository';
// 追加=============================================
import { Items } from 'src/entities/items.entity';
import { ItemDto } from './dto/item.dto';
// ↑↑↑↑============================================

@Injectable()
export class ItemsService {
  constructor (private readonly itemsRepository: ItemsRepository) {}

  // 追加=============================================
  async createItem (itemDto: ItemDto): Promise<Items> {
    return await this.itemsRepository.createItem(itemDto);
  }
  // ↑↑↑↑============================================
}

~~~

#### 8-3. コントローラーの編集

最後に、URL`/items`にPOSTリクエストがきた場合に、登録処理を行うようにハンドラーメソッドを作成しましょう。

引数は、`@Body()`を利用してリクエストボディにアクセスすることができます。

リクエストボディとして渡ってきた`ItemDto`型のオブジェクトを引数にして、サービスメソッド`createUser`を実行しましょう。

~~~typescript:src/items/items.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { ItemsService } from './items.service';
// 追加=============================================
import { Items } from 'src/entities/items.entity';
import { ItemDto } from './dto/item.dto';
// ↑↑↑↑============================================

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

  // 追加=============================================
  @Post() // @Post()デコレーターを使用して、POSTリクエストに対するハンドラーを登録
  // サービスのcreateItemメソッドを実行するハンドラを定義
  // DB操作は非同期処理のため、返り値の型は、Promise。ジェネリクスでItemsエンティティを付与
  async createUser (@Body() itemDto: ItemDto): Promise<Items> {
    return await this.itemsService.createItem(itemDto);
  }
  // ↑↑↑↑============================================
}
~~~


#### 8-4. 動作確認
Postmanで、作成した登録APIの動作確認をしてみます。

メソッドを`POST`にし、URLを`http://localhost:3000/items`にします。

`Body`タブを選択し、`x-www-form-unlencoded`を選択します。

`name`と`price`にそれぞれ値を指定し、`Send`ボタンを押してAPIを実行します。

以下のようなレスポンスが返って来れば、正常に登録処理が終了しています。

![スクリーンショット 2022-09-19 19.04.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/74183464-4b9d-a2d5-f2f5-240b5fd6aa3c.png)

データベースを見てみて、レスポンスとして返ってきたデータが登録されていることが確認できれば、登録APIの実装が完了です！👏
![スクリーンショット 2022-09-19 19.08.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/3b515f4c-bc5e-9f97-fad2-6969493635a5.png)

### 9. 参照機能の作成（READ）

#### 9-1. サービスの編集

全件取得と、一件取得のAPIを実装しましょう。

今回はTypeORMの標準メソッドの`find`と`findOne`を実行すれば全件取得が可能なので、リポジトリの編集はしません。

TypeORMメソッド`find`/`findOne`を実行するサービスメソッドをそれぞれ定義します。

全件取得の際は配列形式のレスポンスになる為、Promiseのジェネリクスで指定するエンティティクラスに`[]`を付与するのを忘れないでください。

~~~typescript:src/items/items.service.ts
import { Injectable } from '@nestjs/common';
import { Items } from 'src/entities/items.entity';
import { ItemDto } from './dto/item.dto';
import { ItemsRepository } from './items.repository';

@Injectable()
export class ItemsService {
  constructor (private readonly itemsRepository: ItemsRepository) {}

  // 追加=============================================
  // 全件取得
  async findAll (): Promise<Items[]> {
    return await this.itemsRepository.find();
  }

  // 1件取得
  async findById (id: string): Promise<Items> {
    // 引数として渡ってきたidを元に、データベースから検索
    const foundItem = await this.itemsRepository.findOne(id);
    if (!foundItem) {
      throw new NotFoundException(); // データが見つからない場合、例外を投げる
    }
    return foundItem;
  }
  // ↑↑↑↑============================================

  async createItem (itemDto: ItemDto): Promise<Items> {
    return await this.itemsRepository.createItem(itemDto);
  }
}
~~~

#### 9-2. コントローラーの編集
`@Get()`デコレーターで、GETメソッドに対するハンドラーメソッドを作成します。

デコレーターの引数に文字列を付与することで、`items/`以降のパスを定義することができます。

`:id`とすることで、パスパラメータを定義することができます。

GETメソッド`items/`でリクエストが来た場合は、サービスクラスの`findAll`メソッドを実行するハンドラを定義し、

GETメソッド`items/:id`でリクエストが来た場合は、サービスクラスの`findById`メソッドを実行するハンドラを定義します。

~~~typescript:src/items/items.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { Items } from 'src/entities/items.entity';
import { ItemsService } from './items.service';
import { ItemDto } from './dto/item.dto'

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

  // 追加=============================================
  @Get()
  async findAll (): Promise<Items[]> {
    return await this.itemsService.findAll();
  }

  @Get(':id') // "items/"以降のパスを定義。パスパラメータにidを定義
  async findById (@Param() id: string): Promise<Items> {
    // パスパラメータのidを引数にして、サービスメソッドfindByIdを実行
    return await this.itemsService.findById(id);
  }
  // ↑↑↑↑============================================

  @Post()
  async createUser (@Body() itemDto: ItemDto): Promise<Items> {
    return await this.itemsService.createItem(itemDto);
  }
}
~~~

#### 9-3. 動作確認

まずは全件取得のAPIのテストをしてみます。

メソッドを`GET`にして、URLを`http://localhost:3000/items`を実行してみます。

以下のように、配列形式で登録済みのデータが返れば、正常に全件取得処理が実行できています👏

![スクリーンショット 2022-09-19 19.50.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/04ef5102-055e-6970-6728-612cc25849aa.png)

1件取得APIのテストをしてみます。

登録済みのデータのうちどれかのデータの`id`をコピーして、`http://localhost:3000/items/`の末尾にペーストして実行してください。

以下のように、オブジェクト形式でIDが一致する一件のデータが返れば、正常に1件取得処理が実行できています👏

![スクリーンショット 2022-09-19 19.50.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/82f09782-41dc-8e8b-1dda-f3e6f23e4bc0.png)

### 10. 更新機能の作成（UPDATE）

更新APIを実装しましょう。

流れとしては、パスパラメータのIDで取得したデータを、リクエストボディの値に更新するという処理になります。

#### 10-1. サービスの編集

既存の`findById`メソッドを利用して、IDからデータを検索します。

取得できたら、プロパティの値をリクエストボディの値に更新して、更新日を現在日時にします。

`save`メソッドを実行してデータベースに反映します。

~~~typescript:src/items/items.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Items } from 'src/entities/items.entity';
import { ItemDto } from './dto/item.dto';
import { ItemsRepository } from './items.repository';

@Injectable()
export class ItemsService {
  constructor (private readonly itemsRepository: ItemsRepository) {}

  async findAll (): Promise<Items[]> {
    return await this.itemsRepository.find();
  }

  async findById (id: string): Promise<Items> {
    const foundItem = await this.itemsRepository.findOne(id);
    if (!foundItem) {
      throw new NotFoundException();
    }
    return foundItem;
  }

  async createItem (itemDto: ItemDto): Promise<Items> {
    return await this.itemsRepository.createItem(itemDto);
  }

  // 追加=============================================
  async updateItem (id: string, itemDto: ItemDto): Promise<Items> {
    let foudnItemToUpdate = await this.findById(id); // 更新するデータを取得
    foudnItemToUpdate.name = itemDto.name; // nameを更新
    foudnItemToUpdate.price = itemDto.price; // priceを更新
    foudnItemToUpdate.updatedAt = new Date().toISOString(); // 更新日時を更新
    return await this.itemsRepository.save(foudnItemToUpdate); // データ保存
  }
  // ↑↑↑↑============================================
}
~~~

#### 10-2. コントローラーの編集

PATCHメソッドでリクエストを受けた際に、パラメータとリクエストボディを引数にしてサービスメソッドを実行します。

~~~typescript:src/items/items.controller.ts
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { Items } from 'src/entities/items.entity';
import { ItemsService } from './items.service';
import { ItemDto } from './dto/item.dto'

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

  @Get()
  async findAll (): Promise<Items[]> {
    return await this.itemsService.findAll();
  }

  @Get(':id')
  async findById (@Param() id: string): Promise<Items> {
    return await this.itemsService.findById(id);
  }

  @Post()
  async createUser (@Body() itemDto: ItemDto): Promise<Items> {
    return await this.itemsService.createItem(itemDto);
  }

  // 追加=============================================
  @Patch(':id')
  async updateItem (
    @Param() id: string,
    @Body() itemDto: ItemDto
  ): Promise<Items> {
    return await this.itemsService.updateItem(id, itemDto);
  }
  // ↑↑↑↑============================================
}

~~~



#### 10-3. 動作確認
更新したい既存のデータの`id`をコピーします。

![スクリーンショット 2022-09-19 20.58.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/18973c79-3fbb-f498-dbd1-e552572e64a7.png)

メソッドを`PATCH`にして、URLを`http://localhost:3000/items/{コピーしたid}`にします。

リクエストボディに、更新後の値を入れて、APIを実行します。

以下のように、更新後のデータがレスポンスとして返却されれば、APIが正常終了しています。

![スクリーンショット 2022-09-19 20.59.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/1a742fbb-7d69-a1d4-6a38-d08bd1be5ca7.png)

メソッドを`GET`にして、URLを`http://localhost:3000/items/{コピーしたid}`で1件取得APIを実行し、データが更新されていることが確認できれば、更新APIの実装が完了です！👏
![スクリーンショット 2022-09-19 21.02.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/53cf19c3-240b-bda8-1c21-53f0a10d7bcd.png)

### 11. 削除機能の作成（DELETE）

#### 11-1. サービスの編集
TypeORMの`delete`メソッドを実行するサービスメソッドを作成します。

`delete`メソッドの引数にIDを渡すことで、データの削除ができます。

レスポンスはないので、Promiseのジェネリクスには`void`を設定します。

~~~typescript:src/items/items.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Items } from 'src/entities/items.entity';
import { ItemDto } from './dto/item.dto';
import { ItemsRepository } from './items.repository';

@Injectable()
export class ItemsService {
  constructor (private readonly itemsRepository: ItemsRepository) {}

  async findAll (): Promise<Items[]> {
    return await this.itemsRepository.find();
  }

  async findById (id: string): Promise<Items> {
    const foundItem = await this.itemsRepository.findOne(id);
    if (!foundItem) {
      throw new NotFoundException();
    }
    return foundItem;
  }

  async createItem (itemDto: ItemDto): Promise<Items> {
    return await this.itemsRepository.createItem(itemDto);
  }

  async updateItem (id: string, itemDto: ItemDto): Promise<Items> {
    let foudnItemToUpdate = await this.findById(id); // 更新するデータを取得
    foudnItemToUpdate.name = itemDto.name; // nameを更新
    foudnItemToUpdate.price = Number(itemDto.price); // priceを更新
    foudnItemToUpdate.updatedAt = new Date().toISOString(); // 更新日時を更新
    return await this.itemsRepository.save(foudnItemToUpdate); // データ保存
  }

  // 追加=============================================
  async deleteItem (id: string): Promise<void> {
    await this.itemsRepository.delete(id);
  }
  // ↑↑↑↑============================================
}
~~~


#### 11-2. コントローラーの編集
DELETEメソッドでリクエストを受けた際に、パラメータとして渡ってきた商品IDを引数にして、作成したサービスメソッド`deleteItem`を実行します。
~~~typescript:src/items/items.controller.ts
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
import { Items } from 'src/entities/items.entity';
import { ItemsService } from './items.service';
import { ItemDto } from './dto/item.dto'

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

  @Get()
  async findAll (): Promise<Items[]> {
    return await this.itemsService.findAll();
  }

  @Get(':id')
  async findById (@Param() id: string): Promise<Items> {
    return await this.itemsService.findById(id);
  }

  @Post()
  async createUser (@Body() itemDto: ItemDto): Promise<Items> {
    return await this.itemsService.createItem(itemDto);
  }

  @Patch(':id')
  async updateItem (
    @Param() id: string,
    @Body() itemDto: ItemDto
  ): Promise<Items> {
    return await this.itemsService.updateItem(id, itemDto);
  }

  // 追加=============================================
  @Delete(':id')
  async deleteItem (@Param() id: string): Promise<void> {
    return await this.itemsService.deleteItem(id);
  }
  // ↑↑↑↑============================================
}
~~~

#### 11-3. 動作確認

削除したい既存のデータの`id`をコピーします。

メソッドを`DELETE`にして、URLを`http://localhost:3000/items/{コピーしたid}`にして実行します。

エラーが出なければ、正常に削除処理が終了しています。

![スクリーンショット 2022-09-19 21.09.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/17e640c8-d6b3-a1e1-f413-86e907ff1ee8.png)

メソッドを`GET`にして、URLを`http://localhost:3000/items/{コピーしたid}`で1件取得APIを実行し、404NotFoundエラーが返ってくれば、削除APIの実装が完了です！👏

![スクリーンショット 2022-09-19 21.10.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/9082b1d1-22d6-bb36-5d83-4aa90ebc18ce.png)

### あとがき
これにてNestJSを使用したCRUD実装が完了です!!

認証機能の実装などの記事も今後書いてみたいと思っています。

この記事がNestJSの学習に少しでもお力になれたら幸いです!

最後まで読んでいただき、ありがとうございました!
![rick-astley-never-gonna-give-you-up.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/8e0c48c5-8890-9462-ba9a-51f6044f9542.gif)