NestJS のチュートリアル、nest new ~ 基本的なCRUD の作成まで

# プロジェクトの作成

## NestJS CLI の導入

先ず、NestJS CLI をインストールします。

```zsh
npm i -g @nestjs/cli
```

## nest new の実行

次に、nest new コマンドを使い、プロジェクトを作成します。
今回は、tutorial-app というプロジェクト名を指定しています。

```zsh
nest new tutorial-app
```

実行すると、使用するパッケージマネージャーを聞かれます。
今回は、npm を選択します。
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684930/77359090-571c-f21c-8848-893498f5a529.png)

# CRUD の実装

## nest g コマンド の実行

nest g コマンドを実行し、BooksModule、BooksController、BooksService を作成します。

```zsh
nest g module books
nest g controller books --no-spec
nest g service books --no-spec
```

Controller とService は、オプションをつけずに実行すると、 books.controller.spec.ts のようなテストファイルが自動で作成されます。

今回はテストは行わないので、--no-spec オプションをつけて、テストファイルなしで、Controller とService を作成します。
![Untitled (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684930/914492a4-98ff-5315-e25d-ae36bb4e67b2.png)

## 依存性の注入

BooksService で定義したメソッドをBooksController で呼び出せるように、依存性の注入を行います。

```ts:src/books/books.controller.ts
import { Controller, Get } from '@nestjs/common';
import { BooksService } from './books.service';

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {} // booksService　をインスタンス化する
}
```

## Book Model の作成

Book の型を定義したModel を定義します。

```ts:src/books/book.model.ts
import { BookStatus } from './book-status.enum';

export interface Book {
  id: string;
  name: string;
  status: BookStatus;
}
```

本の貸し出し状況をstatus で管理するため、enum として、別ファイルに切り出す。

```ts:src/books/book-status.enum.ts
export enum BookStatus {
  RENTABLE = 'RENTABLE',
  LENT_OUT = 'LENT_OUT',
}
```

TypeORM の導入時にModel とTable の紐付けを定義するBookEntity というものを作成します。
その時にBook Model は不要になるので、全てBookEntity に置き換えることになります。

## Create 機能の実装

先ず、 値を保存するためのcreate メソッドをBooksServiceに用意します。

```ts:src/books/books.service.ts
import { Injectable } from '@nestjs/common';
import { Book } from './book.model';

@Injectable()
export class BooksService {
  private books: Book[] = [];

  create(book: Book) {
    this.books.push(book);
    return book;
  }
}
```

保存する値は、Post リクエストのBody からBooksController を介してBooksService に渡します。

BooksController にPost リクエストに対して、BooksService のcreate メソッドを呼び出す処理を書いていきます。

```ts:src/books/books.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { BookStatus } from './book-status.enum';
import { Book } from './book.model';
import { BooksService } from './books.service';

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {}

  @Post()
  create(@Body('id') id: string, @Body('name') name: string): Book {
    const book: Book = {
      id,
      name,
      status: BookStatus.RENTABLE,
    };

    return this.booksService.create(book);
  }
}
```

動作確認として、Postman を使います
http://localhost:3000/books にBody に保存するプロパティの値を渡し、POST でリクエストを送ります。

保存が成功していいるとリクエストに対してbook 変数の値が返ってきます。

![Untitled (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684930/7d5dc343-cec0-285e-6ff8-680fb1ee5bf6.png)
## Read 機能の実装

保存したBook の値を確認するため、BooksService にBook の内容を取得するためのロジックを記述します。

### 全件取得機能

保存したBook の値を全て取得するfindAll メソッドをBooksService に記述していきます。

```ts:src/books/books.service.ts
import { Injectable } from '@nestjs/common';
import { Book } from './book.model';

@Injectable()
export class BooksService {
  private books: Book[] = [];
  findAll() {
    return this.books;
  }

  create(book: Book) {
    this.books.push(book);
    return book;
  }
}
```

BooksController にGet リクエストに対し、BooksService のfindAll メソッドを呼び出す処理を追記します。

```tsx:src/books/books.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { BookStatus } from './book-status.enum';
import { Book } from './book.model';
import { BooksService } from './books.service';

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {}
  @Get()
  findAll() {
    return this.booksService.findAll();
  }

  @Post()
  create(@Body('id') id: string, @Body('name') name: string): Book {
    const book: Book = {
      id,
      name,
      status: BookStatus.RENTABLE,
    };

    return this.booksService.create(book);
  }
}
```

[http://localhost:3000/books](http://localhost:3000/books) にGET でリクエストを送り、Books の配列の値が返ってくれば成功です。

配列には、Post で保存したBook の値が入っています。
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684930/5858a9b5-c259-c599-8cca-27139e074664.png)


### ID を指定した取得

全件取得機能が作れたら、次に指定したID を持ったBook を取り出すfindById メソッドをBooksService に記述します。

```ts:src/books/books.service.ts
import { Injectable } from '@nestjs/common';
import { Book } from './book.model';

@Injectable()
export class BooksService {
  private books: Book[] = [];
  findAll() {
    return this.books;
  }

  findById(id: string): Book {
    return this.books.find((book) => book.id === id);
  }

  create(book: Book) {
    this.books.push(book);
    return book;
  }
}
```

取得したいID は、books/1 のようにURL に記述して渡します。
BooksController にGet リクエストに対し、BooksService のfindById メソッドを呼び出す処理を追記します。

```ts:src/books/books.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { BookStatus } from './book-status.enum';
import { Book } from './book.model';
import { BooksService } from './books.service';

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {}
  @Get()
  findAll() {
    return this.booksService.findAll();
  }

  @Get(':id')
  findById(@Param('id') id: string): Book {
    return this.booksService.findById(id);
  }

  @Post()
  create(@Body('id') id: string, @Body('name') name: string): Book {
    const book: Book = {
      id,
      name,
      status: BookStatus.RENTABLE,
    };

    return this.booksService.create(book);
  }
}
```
[http://localhost:3000/books](http://localhost:3000/books)/:id にGET でリクエストを送り、指定したid のBook の値が返ってくれば成功です。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684930/f47cc92d-1580-5379-d1d8-eb3596ff8f8b.png)

## Update 機能の実装

指定したID を持ったBook のstatus を更新するupdateStatus メソッドをBooksService に記述します。

リクエストで渡したID と一致するBook はfindById を使って取得します。

```ts:src/books/books.service.ts
import { Injectable } from '@nestjs/common';
import { Book } from './book.model';

@Injectable()
export class BooksService {
  private books: Book[] = [];
  findAll() {
    return this.books;
  }

  findById(id: string): Book {
    return this.books.find((book) => book.id === id);
  }

  create(book: Book) {
    this.books.push(book);
    return book;
  }

  updateStatus(id: string): Book {
    const book = this.findById(id);
    book.status = BookStatus.LENT_OUT;
    return book;
  }
}
```
BooksController にPATCH リクエストに対し、BooksService のfindById メソッドを呼び出す処理を追記します。

```ts:src/books/books.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { BookStatus } from './book-status.enum';
import { Book } from './book.model';
import { BooksService } from './books.service';

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {}
  @Get()
  findAll() {
    return this.booksService.findAll();
  }

  @Get(':id')
  findById(@Param('id') id: string): Book {
    return this.booksService.findById(id);
  }

  @Post()
  create(@Body('id') id: string, @Body('name') name: string): Book {
    const book: Book = {
      id,
      name,
      status: BookStatus.RENTABLE,
    };

    return this.booksService.create(book);
  }

	@Patch(':id')
    updateStatus(@Param('id') id: string): Item {
     return this.itemsService.updateStatus(id);
  }
}
```


[http://localhost:3000/books](http://localhost:3000/books)/:id にPATCH でリクエストを送り、指定したid のBook status の値がRENTABLE からLENT_OUT に更新されていれば成功です。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684930/9b6ba04e-57f5-f6dd-4cda-6516ff478562.png)

## Delete 機能の実装

BooksService に リクエストで指定したid の値と一致した、id を持つBook を削除するdelete メソッドを用意します。

```ts:src/books/books.service.ts
import { Injectable } from '@nestjs/common';
import { Book } from './book.model';
import { BookStatus } from './book-status.enum';

@Injectable()
export class BooksService {
  private books: Book[] = [];
  findAll() {
    return this.books;
  }

  findById(id: string): Book {
    return this.books.find((book) => book.id === id);
  }

  create(book: Book) {
    this.books.push(book);
    return book;
  }

  updateStatus(id: string): Book {
    const book = this.findById(id);
    book.status = BookStatus.LENT_OUT;
    return book;
  }

  delete(id: string): void {
    this.books = this.books.filter((book) => book.id !== id);
  }
}
```

BooksController にDELETE リクエストに対し、BooksService のdelete メソッドを呼び出す処理を追記します。

```ts:src/books/books.controller.ts
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
import { BookStatus } from './book-status.enum';
import { Book } from './book.model';
import { BooksService } from './books.service';

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {}
  @Get()
  findAll() {
    return this.booksService.findAll();
  }

  @Post()
  create(@Body('id') id: string, @Body('name') name: string): Book {
    const book: Book = {
      id,
      name,
      status: BookStatus.RENTABLE,
    };

    return this.booksService.create(book);
  }

  @Delete()
  delete(@Param('id') id: string): void {
    this.booksService.delete(id);
  }
}
```

[http://localhost:3000/books](http://localhost:3000/books)/:id にDELETE でリクエストを送った後、[http://localhost:3000/books](http://localhost:3000/books)　にGET でリクエストを送ります。

指定したid のBook が削除されていれば成功です。