NestJSのTypeORMの設定(Postgres)

# NestJS TypeORMの設定 ※postgresql
typeormのバージョンがアップしたので更新

## 1.構成
#### 1-1.フォルダ構成
```
.
├── app/ # NestJSのフォルダ
├── .env
├── docker-compose.yml
└── Dockerfile
```

#### 1-2. ファイル編集
``` Dockerfile:Dockerfile
FROM node:18.12.1
RUN npm i -g @nestjs/cli
WORKDIR /app
```
``` docker:docker-compose.yml
version: "3.7"

services:
  app:
    build: .
    env_file:
      - .env
    environment:
      TZ: "Asia/Tokyo"
    ports:
      - 3000:3000
    tty: true
    volumes:
      - ./app:/app

  db:
    image: postgres:15.1
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
      POSTGRES_DB: test
      TZ: "Asia/Tokyo"
    ports:
      - 5432:5432
    volumes:
      - "./dbdata:/var/lib/postgresql/data"
```
``` bash:.env
NODE_ENV=local
# db関連の設定
DATABASE_HOST=db
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres
DATABASE_PORT=5432
DATABASE_DB=test
```

## 2. NestJSのプロジェクト生成
```
docker-compose up -d
docker-compose exec app bash
# container内作業
nest new .
yarn start:dev # localhost:3000で動作確認
```

## 3. Configurationのパッケージインストール&確認
#### 3-1. Configurationのパッケージインストール
```
# container内で作業
yarn add @nestjs/config
```
#### 3-2. ファイル編集
```diff_typescript:app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';

@Module({
- imports: [],
+ imports: [ConfigModule.forRoot({ isGlobal: true})],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```
```diff_typescript:app.service.ts
import { Injectable } from '@nestjs/common';
+ import { ConfigService } from '@nestjs/config';

@Injectable()
export class AppService {
+ constructor(private configService: ConfigService) {}
  getHello(): string {
+   console.log(this.configService.get('DATABASE_HOST'), this.configService.get('DATABASE_DB'));
    return 'Hello World!';
  }
}
```
#### 3-3. 確認
```
yarn start:dev
```
※localhost:3000にアクセス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296391/c4616ebc-f735-036f-8cf8-854b732450aa.png)
※環境変数が取得できていることを確認

## 4. Typeormの設定
#### 4-1. packageインストール
```
yarn add @nestjs/typeorm typeorm pg
```

#### 4-2. Typeormの設定反映
``` typescript:app/src/db/database.config.ts
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from "@nestjs/typeorm";

@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
  createTypeOrmOptions(): TypeOrmModuleOptions {
    const configService = new ConfigService();
    return {
      type: 'postgres',
      host: configService.get('DATABASE_HOST'),
      port: configService.get('DATABASE_PORT'),
      username: configService.get('DATABASE_USER'),
      password: configService.get('DATABASE_PASSWORD'),
      database: configService.get('DATABASE_DB'),
      entities: [],
      synchronize: false, // 本番環境では必ずfalse
    };
  }
}
```
```diff_typescript:app/src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
+import { ConfigModule } from '@nestjs/config';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { TypeOrmConfigService } from './db/database.config';


@Module({
- imports: [ConfigModule.forRoot({ isGlobal: true})],
+ imports: [
+   ConfigModule.forRoot({ isGlobal: true}), 
+   TypeOrmModule.forRootAsync({ 
+     imports: [ConfigModule], 
+     useClass: TypeOrmConfigService
+   })
+ ], 
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```
``` typescript:app/src/db/data-source.ts
import { DataSource } from "typeorm";
import { User } from "./entities/user.entity";

const DATABASE_HOST = process.env.DATABASE_HOST;
const DATABASE_USER = process.env.DATABASE_USER;
const DATABASE_PASSWORD = process.env.DATABASE_PASSWORD;
const DATABASE_PORT = process.env.DATABASE_PORT;
const DATABASE_DB = process.env.DATABASE_DB;
if (!DATABASE_HOST || !DATABASE_USER || !DATABASE_PASSWORD || !DATABASE_PORT || !DATABASE_DB) {
  throw new Error('Enviroment Database Error');
}

const source = new DataSource({
  type: 'postgres',
  host: DATABASE_HOST,
  port: Number(DATABASE_PORT),
  username: DATABASE_USER,
  password: DATABASE_PASSWORD,
  database: DATABASE_DB,
  entities: [],
  migrations: ['src/db/migrations/**/*.ts'], // フォルダを作成すること
  synchronize: false,
})
export default source;
```

## 5. Entity作成と設定追加
#### 5-1. Entity作成
```typescript:app/src/db/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column({ default: true })
  isActive: boolean;
}
```
#### 5-2. 設定に反映
```diff_typescript:app/src/db/data-source.ts
+import { User } from "./entities/user.entity";

const source = new DataSource({
  type: 'postgres',
  host: DATABASE_HOST,
  port: Number(DATABASE_PORT),
  username: DATABASE_USER,
  password: DATABASE_PASSWORD,
  database: DATABASE_DB,
- entities: [],
+ entities: ['src/db/**/*.entity.ts'],
  migrations: ['src/db/migrations/**/*.ts'], // フォルダを作成すること
  synchronize: false,
})
```
```diff_typescript:app/src/db/database.config.ts
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from "@nestjs/typeorm";
+import { User } from "./entities/user.entity";

@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
  createTypeOrmOptions(): TypeOrmModuleOptions {
    const configService = new ConfigService();
    return {
      type: 'postgres',
      host: configService.get('DATABASE_HOST'),
      port: configService.get('DATABASE_PORT'),
      username: configService.get('DATABASE_USER'),
      password: configService.get('DATABASE_PASSWORD'),
      database: configService.get('DATABASE_DB'),
-     entities: [],
+     entities: [User],
      synchronize: false, // 本番環境では必ずfalse
    };
  }
}
```
```diff_json:package.json
  "scripts": {
    ...
+   "typeorm": "npx typeorm-ts-node-commonjs",
+   "migration:generate": "yarn typeorm -d src/db/data-source.ts migration:generate",
+   "migration:run": "yarn typeorm -d ./src/db/data-source.ts migration:run",
+   "migration:revert": "yarn typeorm -d src/db/data-source.ts migration:revert"
  },

```

## 6. migration実行確認
#### 6-1. migration:generateの実行
```shell
 yarn migration:generate src/db/migrations/init
```
※実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296391/89f3519a-9050-bdfe-1267-329cb741989f.png)

#### 6-2. migration:runの実行
```shell
yarn migration:run
```
※実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296391/c48bd909-6a8b-0f49-fdd9-ce90e37ea065.png)

#### 6-3. migration:revertの実行
```shell
yarn migration:revert
```
※実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296391/73968b6e-386f-d633-1471-3b0fe011cbc5.png)