NestJS TypeORM postgres で環境構築 with docker(DBのみ)

#記事の内容
NestJS TypeORM postgresで環境構築します。
postgresはDocker上で起動します。

Dockerについては、完全初心者です。
ただ、dbをローカルに作るのが嫌でしたので導入してみました。

今回調べながらの環境構築なので
間違っている所やこうした方がいい所、等があった場合
教えていただけると嬉しいです。


環境構築を終えたら、
URLのアクセスからデータ操作までの流れを簡単に実装します。

ソースコードはこちら↓
https://github.com/tokio-k/docker-nest-postgres-sample

※Docker、yarnのインストールは前提で進めます。
※調べればすぐできると思うのでよろしくお願いします。


#プロジェクトの作成

###フォルダ作成
ターミナルで今回作成していくフォルダを作成し、そのフォルダに移動します。
(今回はdocker-nest-postgres-sampleというフォルダ)

```terminal.
# フォルダ作成
mkdir docker-nest-postgres-sample
# 移動
cd docker-nest-postgres-sample
```
(terminal. の「.」は気にしないでください。普通にターミナルで実行しています。)

###NestJSのプロジェクトを作成
Nest用CLIを用いて作成していきます。
Nest用CLIをインストールします。(既にインストールしてある場合は飛ばしてください。)

```terminal.
yarn global add @nestjs/cli
```

作成したフォルダの中に、Nestのプロジェクトを作成します。
簡単に雛形を作成できます。

```terminal.
# Nestのプロジェクトを作成
nest new .
```
# ライブラリのインストール
TypeORM関連やバリデーションのライブラリをインストールしておきます。
DBにはpostgresを使用します。

```terminal.
#TypeORM関連
yarn add @nestjs/typeorm typeorm pg

#バリデーション
yarn add class-transformer class-validator
```

#Dockerでpostgresの環境構築

最初に作成したフォルダの中にdocker-compose.ymlを作成します。
srcフォルダやpackage.jsonと同じ階層です。

```terminal.
touch docker-compose.yml
```
作成したdocker-compose.ymlの中に以下の記述をします。
データベース接続情報などは好きに書いてください。(environment)
本当は、環境変数や.envファイルに記述し、参照する様にするみたいです。
いつか書き換えればいいので、今は直接書きで。

```docker-compose.yml
version: '3'

services:
  postgres:
    image: postgres:13.1
    container_name: postgres
    ports:
      - 5432:5432
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: password
      POSTGRES_DB: test_db
      POSTGRES_INITDB_ARGS: '--encoding=UTF-8'
      TZ: 'Asia/Tokyo'
    restart: always
```
docker-composeに記載した内容で、
docker上にpostgresの環境を立ち上げます。

```terminal.
docker-compose up -d
```
pgAdmin4などで確認してみると、データベースが作成されていることが確認できます。

#NestJSからTypeORMを使用

###ormconfig.jsonを作成

srcフォルダやpackage.jsonと同じ階層にormconfig.jsonを作成します。

```terminal.
touch ormconfig.json
```
ormconfig.jsonを編集していきます。
データベース情報は、先程設定したものと合わせてください。

```ormconfig.json
{
    "type": "postgres",
    "host": "localhost",
    "port": 5432,
    "username": "root",
    "password": "password",
    "database": "test_db",
    "entities": ["dist/entities/**/*.entity.js"],
    "migrations": ["dist/migrations/**/*.js"],
    "logging": true,
    "synchronize": false
}
```

###app.module.tsを編集
app.module.tsを編集します。
これで、TypeORMの機能を呼び出すことができる様になります。

```app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';//追加箇所

@Module({
  imports: [TypeOrmModule.forRoot()],//編集箇所
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```

# テーブル作成
テーブルを作成していきます。

###Entityの作成
Entityを作成します。
Entityでは、テーブルの構造を定義し、これを元にテーブルが作成されます。

srcフォルダの中にentitiesフォルダを作成します。
entitiesフォルダの中にitem.entity.tsを作成します。

item.entity.tsを編集していきます。

```item.entity.ts
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
} from 'typeorm';

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

  @Column()
  todo: string;

  @Column('boolean', { default: false })
  idDone: boolean;

  @CreateDateColumn()
  readonly createdAt?: Date;

  @UpdateDateColumn()
  readonly updatedAt?: Date;
}
```
id,todo,isDone,createdAt,updatedAtがそれぞれテーブルのカラムになります。

###マイグレーションファイルの作成/実行

マイグレーションファイルを作成していきます。

```terminal.
# ビルド
yarn build

#マイグレーションファイルの作成
yarn typeorm migration:generate -d src/migrations -n create-item
```

マイグレーションファイルを実行していきます。

```terminal.
# ビルド
yarn build

# マイグレーションファイルの実行　(DB作成)
yarn typeorm migration:run
```

pgAdmin4などで確認してみると、テーブルが作成されている事が確認できます。

#module,controller,serviceの作成

コマンドでそれぞれ作成していきます。

```terminal.
# module作成
yarn nest g module item

# controller作成
yarn nest g controller item

# service作成
yarn nest g service item
```

コマンドで作成すると、読み込まれるように、app.module.tsやitem.module.tsに自動で追記されます。
app.module.tsを編集します。

```item.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';//追加箇所
import { Item } from 'src/entities/item.entity';//追加箇所
import { ItemController } from './item.controller';
import { ItemService } from './item.service';

@Module({
  controllers: [ItemController],
  imports: [TypeOrmModule.forFeature([Item])],//編集箇所
  providers: [ItemService],
})
export class ItemModule {}
```

#DTOの作成

###DTOを作成します。
データのやり取りをするときにデータ構造を表す箱のようなものです。
ここでバリデーション(入力チェック)などができます。

```item.dto.ts
import { IsNotEmpty, IsString } from 'class-validator';

export class CreateItemDTO {
  @IsNotEmpty()
  @IsString()
  todo: string;
}

```
todoを必須+文字列に制限しました。
データ追加をするときに、このクラスを使ってデータのやり取りをします。

###バリデーションを有効にする
src/main.tsを編集して、バリデーションを有効にします。

```main.ts
import { ValidationPipe } from '@nestjs/common';//追加箇所
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());//追加箇所
  await app.listen(3000);
}
bootstrap();
```

#APIの実装
Service,Controllerを実装していきます。

###Serviceの実装
Serviceを実装していきます。

```item.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Item } from 'src/entities/item.entity';
import { InsertResult, Repository } from 'typeorm';
import { CreateItemDTO } from './item.dto';

@Injectable()
export class ItemService {
  constructor(
    @InjectRepository(Item)
    private readonly itemRepository: Repository<Item>,
  ) {}

  // 全データ検索
  async findAll(): Promise<Item[]> {
    return await this.itemRepository.find();
  }

  //データ追加
  async create(item: CreateItemDTO): Promise<InsertResult> {
    return await this.itemRepository.insert(item);
  }
}

```

constructorの部分でRepositoryをInjectionしています。
Repositoryに指示を出すことでテーブルのデータを変更できます。

###Controllerの実装

```item.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { Item } from 'src/entities/item.entity';
import { InsertResult } from 'typeorm';
import { CreateItemDTO } from './item.dto';
import { ItemService } from './item.service';

@Controller('item')
export class ItemController {
  constructor(private readonly service: ItemService) {}

  // 全データ検索
  @Get()
  async getItemList(): Promise<Item[]> {
    return await this.service.findAll();
  }
  //データ追加
  @Post()
  async addItem(@Body() item: CreateItemDTO): Promise<InsertResult> {
    return await this.service.create(item);
  }
}
```
/itemにGetでアクセスがあった時に「getItemList()」
/itemにPostでアクセスがあった時に「addItem(item)」が呼び出されます。
addItem(item)の「item」が先程作成したCreateItemDTOの型には入ります。

constructorでは、先程作成したServiceがInjectionされています。

とりあえず、実装は完成

#動作確認

###実行
以下のコマンドで実行できます。

```terminal.
yarn start dev
```
###新規データの追加 (addItemを呼び出す)

コマンドで以下を実行してデータを追加できます。

```terminal.
curl http://localhost:3000/item -X POST -d "todo=test"
```

###ブラウザからアクセス (getItemListを呼び出す)
ブラウザから以下で検索すると、追加したデータが表示されます。
http://localhost:3000/item

これで、全検索と追加のAPIが作成することができました。

##参考記事
[How To Set Up TypeORM With Postgres in NestJS](https://betterprogramming.pub/how-to-set-up-typeorm-with-postgres-in-nestjs-5575c949c05f)
[NestJSの始め方](https://qiita.com/tobita0000/items/6705800853b2869fec80)
[NestJSで簡単なtudoリストを実装するチュートリアル](https://taroosg.io/nestjs-tutorial)
[Mac に Docker Desktop をインストール](https://docs.docker.jp/docker-for-mac/install.html)