【入門】Nest.js×Next.jsでCRUD機能作ってみる【backend】

仕事で使うかもしれないと言うところからnest.js
CRUDをどう書くのか気になったのでまとめてみた
# この記事で何をやるか  
backendのnest.jsの構築
（frontendのnext.jsの構築は別記事で行う）
（今回はデータベースを使わずにnestのメモリに保存するように実装）
* `task`（タスク）の
  * 作成(create)
  * 一覧取得(read)
  * 編集(update)
  * 削除(delete)

## 事前準備

`node.js`はインストール済みで考えているので、
まだの人はこちらの記事を確認してください。
https://qiita.com/kiharito/items/4785d4d54c967b8ddc9a

`Nest CLI`は`npm`が入っていれば下記でインストール可能
```
npm install -g @nestjs/cli
nest -v  #使えるかの確認
```

# backendの作成
### プロジェクト作成
nest.jsで`nest-task-api`というプロジェクトを作成
```
nest new nest-task-api
```
**パッケージマネージャ（npm/yarn）**を聞かれるので、今回は`npm`を使用  

`nest-task-api`に移動してサーバーを起動
```
cd nest-task-api
npm run start:dev
```
`http://localhost:3000`にアクセスしてみて、「Hello World!」が表示されているか確認
表示されていればプロジェクトの作成は完了

### 機能作成

「`Task` を登録・取得・編集・削除」するCRUD 機能を作る
```:nest-task-api
nest g resource tasks
```
プロンプトには下記のように回答
```
? What transport layer do you use? (Use arrow keys)
> REST API

? Would you like to generate CRUD entry points? (Y/n)
> Y
```
これにより、自動で以下が生成される
* コントローラー`（tasks.controller.ts）`
* サービス`（tasks.service.ts）`
* DTO`（create-task.dto.ts, update-task.dto.ts）`
* エンティティ`（task.entity.ts）`

### Taskのエンティティ定義
今回はCRUDの動作を知りたいためDBを用意せずメモリ管理

`task.entity.ts`を修正
```ts:task.entity.ts
export class Task {
  id: number;
  title: string;
  description?: string;
}
```
### DTOの編集

`create-task.dto.ts`を修正
```ts:create-task.dto.ts
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';

export class CreateTaskDto {
  @IsNotEmpty()
  @IsString()
  title: string;

  @IsOptional()
  @IsString()
  description?: string;
}
```
この際`class-validator`が見つからないとエラーが出る場合下記を実行
```
npm install class-validator class-transformer
```
`update-task.dto.ts`を修正
```ts:update-task.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateTaskDto } from './create-task.dto';

export class UpdateTaskDto extends PartialType(CreateTaskDto) {}
```
### tasks.service.ts を編集（メモリ管理の実装）

```ts:tasks.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';
import { Task } from './entities/task.entity';

@Injectable()
export class TasksService {
  private tasks: Task[] = [];
  private idCounter = 1;

  create(createTaskDto: CreateTaskDto): Task {
    const task: Task = {
      id: this.idCounter++,
      ...createTaskDto,
    };
    this.tasks.push(task);
    return task;
  }

  findAll(): Task[] {
    return this.tasks;
  }

  findOne(id: number): Task {
    const task = this.tasks.find(t => t.id === id);
    if (!task) throw new NotFoundException('Task not found');
    return task;
  }

  update(id: number, updateTaskDto: UpdateTaskDto): Task {
    const task = this.findOne(id);
    Object.assign(task, updateTaskDto);
    return task;
  }

  remove(id: number): void {
    const index = this.tasks.findIndex(t => t.id === id);
    if (index === -1) throw new NotFoundException('Task not found');
    this.tasks.splice(index, 1);
  }
}
```

## 動作確認（Postmanを使用）
サーバー起動：
```
npm run start:dev
```
エンドポイント例：
| メソッド   | URL        | 内容                                |
| ------ | ---------- | --------------------------------- |
| GET    | `/tasks`   | タスク一覧                             |
| POST   | `/tasks`   | タスク作成（JSON: `{ "title": "テスト" }`） |
| GET    | `/tasks/1` | ID=1のタスク取得                        |
| PATCH  | `/tasks/1` | タイトルなど更新                          |
| DELETE | `/tasks/1` | タスク削除                             |


### 作成(POST)
![スクリーンショット 2025-06-14 14.31.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801775/78bb515b-5672-414e-a793-40f3f56c623e.png)
### 取得(GET)
![スクリーンショット 2025-06-14 14.32.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801775/0e851589-bfb8-4299-9e9e-7282ad86dab3.png)
### 編集(PATCH)
![スクリーンショット 2025-06-14 14.32.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801775/40f1bfdc-822f-474e-89b2-84c5e529e39d.png)
### 削除(DELETE)
![スクリーンショット 2025-06-14 14.33.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801775/056977da-0fa2-4f6e-849f-31f5281897fe.png)

問題なく動いていそうですね。