Dockerを利用してNestJS＋Prisma+MySQLの環境構築をしてテーブル作成をしてみる

## はじめに

この記事では、NestJsを使ってAPI開発したいという方に向けて記事を作成しています。
ソースを載せておくのでご自由にお使いください。

https://github.com/Yosuke23/nestjs_demo_api

## 目次
[1.ディレクトリイメージ](#1-ディレクトリイメージ)
[2.Dockerfileとdocker-compose.ymlの作成](https://qiita.com/Yosuke_Narumi/items/5dd9225bb71d30b1890f#2-dockerfile%E3%81%A8docker-composeyml%E3%81%AE%E4%BD%9C%E6%88%90)
[3.Nestプロジェクトをdockerコンテナのワーキングディレクトリに作成](https://qiita.com/Yosuke_Narumi/items/5dd9225bb71d30b1890f#3-nest%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92docker%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%AE%E3%83%AF%E3%83%BC%E3%82%AD%E3%83%B3%E3%82%B0%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%81%AB%E4%BD%9C%E6%88%90)
[4.MySQLイメージ追加](https://qiita.com/Yosuke_Narumi/items/5dd9225bb71d30b1890f#4-mysql%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E8%BF%BD%E5%8A%A0)
[5.Prismaを利用してMySQLと接続する](https://qiita.com/Yosuke_Narumi/items/5dd9225bb71d30b1890f#5-prisma%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6mysql%E3%81%A8%E6%8E%A5%E7%B6%9A%E3%81%99%E3%82%8B)

## 1. ディレクトリイメージ
```
project-path
    |- docker-compose.yml
    |- Dockerfile
    |- .dockerignore
    |- nestのmodule達
```

## 2. Dockerfileとdocker-compose.ymlの作成

```Dockerfile:Dockerfile
FROM node:14.17

RUN npm i -g @nestjs/cli

WORKDIR /api

```


```yml:docker-compose.yml

version: '3.7'
services:
  nest:
    container_name: nestjs_demo
    build: .
    tty: true
    ports:
      - '3000:3000'
    volumes:
      - type: bind
        source: .
        target: /api
```
```.dockerignore
node_modules
```
nodeイメージをベースにして、@nestjs/cliをインストール

### ビルド
```terminal
% docker compose up -d --build
% docker compose ps
```
## 3. Nestプロジェクトをdockerコンテナのワーキングディレクトリに作成

```terminal
% docker compose exec nest nest new .

? Which package manager would you ❤️  to use? (Use arrow keys)
❯ npm
  yarn
  pnpm
```
と聞かれるので、今回はnpmを選択します。

```terminal
% docker compose exec nest npm run start:dev
```
http://localhost:3000/
にアクセスして、Hello World!と表示されたら、起動成功です。

このコマンドを毎回打つのは面倒なのでnestコンテナに
このコマンドを追加してビルドし直します。
```Dockerfile:Dockerfile
FROM node:14.17

RUN npm i -g @nestjs/cli

WORKDIR /api
# ここから追加
COPY package*.json /api/ 

RUN npm i
CMD [ "npm", "run", "start:dev"]
#　ここまで追加

```
```terminal
docker compose up -d --build
```


## 4. MySQLイメージ追加

### docker-compose.ymlに追記して、MySQLコンテナを立ち上げる

```yml:docker-compose.yml

version: '3.7'
services:
  nest:
    container_name: nest
    build: .
    tty: true
    ports:
      - '3000:3000'
    volumes:
      - type: bind
        source: .
        target: /api
    depends_on: # 追加
      - db

  db:
    platform: linux/x86_64
    image: mysql:5.7
    container_name: nestjs-demo-db
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: nestjs_demo
      MYSQL_PASSWORD: root
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
    ports:
      - 3306:3306

  phpmyadmin:
    container_name: nestjs-demo-phpmyadmin
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOSTS=nestjs-demo-db
      - PMA_USER=root
      - PMA_PASSWORD=root
    ports:
      - 8080:80
```
### DB設定のためのmy.cnfを作成
ルートディレクトにdockerディレクトリ、その中にdbディレクトリを作成してその中に作成します。
これが、docker volumeマウントされます

```docker/db/my.cnf
[mysqld]
default-authentication-plugin=mysql_native_password
character-set-server=utf8mb4

[mysql]
default-character-set=utf8mb4

[client]
default-character-set=utf8mb4
```
### .env追加・編集
```.env
DATABASE_URL="mysql://root:root@nestjs-demo-db:3306/nestjs_demo"
```
DBが作成されているか確認
```terminal
% docker compose up
```
MySQLの管理ツールphpmyadminのimageを入れたので
http://localhost:8080/
にアクセスすればDBの確認ができます。
![【環境構築＃２DockerPrisma+MySQLで土台構築】.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549258/b70dfc01-eb75-ca27-2485-caa329bd9b5d.jpeg)
「nestjs_demo」というDBができていれば成功です。


## 5. Prismaを利用してMySQLと接続する

続いて、Prismaを利用するためのセットアップを行います。
```:Prismaのパッケージをインストール
 % docker compose run nest npm install --save-dev prisma
```
```:Prismaの初期化
 % docker compose run nest npx prisma init
```
この時点で、prismaというフォルダが作成されます。 prismaフォルダの中にschema.prismaがあり、
それを編集していくことで、DBのモデルを作成していきます。


### prismaスキーマファイル編集
```prisma:prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  id            Int      @id @default(autoincrement())
  name          String
  email         String   @unique
  password      String
  registered_at DateTime @default(now())
  updated_at    DateTime @updatedAt

  roles AdminAccountRole[]

  @@map("users")
}

```
mysqlを使うのでpostgresqlから変更
usersという名前でmysqlのデータベースに作成

### prismaスキーマファイルのマイグレート
```
 % docker compose run nest npx prisma migrate dev --name init
```

```sql:prisma/migrations/2022XXXXXXXXXXX_init/migration.sql]
-- CreateTable
CREATE TABLE `users` (
    `id` INTEGER NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(191) NOT NULL,
    `email` VARCHAR(191) NOT NULL,
    `password` VARCHAR(191) NOT NULL,
    `registered_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `updated_at` DATETIME(3) NOT NULL,

    UNIQUE INDEX `users_email_key`(`email`),
    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
生成されたマイグレーションファイル

### PrismaClientのインストール

```
% docker compose run nest npm install @prisma/client
```
Prisma Clientは、Prismaのモデル定義から生成されるタイプセーフのデータベースクライアントです。
インストール時に、Prismaは自動的にprisma generateコマンドを呼び出します。
なので、次回以降Prismaモデルを変更するたびにこのコマンドを実行し、生成されたPrismaクライアントを更新する必要があります。

### Prisma Client service作成
```
docker-compose run nest nest generate service Prisma
```
PrismaClientのインスタンス化とデータベースへの接続を行うPrismaService
srcディレクトリ内にprisma.service.tsというファイルを新規に作成し、下記のコードを追加します。

```ts:src/prisma/prisma.service.ts
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close();
    });
  }
}
```

### Users service作成
```
docker-compose run nest nest generate service users
```
Prismaスキーマから、先程作成したUserモデルのデータベースを呼び出すために使用するUsersServiceを作成します。
srcディレクトリの中にusersディレクトリとusers.service.tsというファイルを新規に作成し、下記のコードを追加します。
```ts:src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from './../prisma/prisma.service';
import { User, Prisma } from '@prisma/client';

@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  async user(id: number): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { id },
    });
  }

  async users(): Promise<User[]> {
    return this.prisma.user.findMany();
  }

  async createUser(data: Prisma.UserCreateInput): Promise<User> {
    return this.prisma.user.create({
      data,
    });
  }
}
```


### ルーティングとコントローラを追加
```
% docker-compose run nest nest g controller users
```
続いて、先程作成したサービスを使用して、ルーティングとコントローラを定義します。
usersディレクトリの中にusers.controller.tsというファイルを新規に作成し、下記のコードを追加します。

```ts:src/users/users.controller.ts
import { Controller, Get, Param, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from '@prisma/client';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':id')
  async findUserById(@Param('id') id: string): Promise<User> {
    return this.usersService.user(Number(id));
  }

  @Get()
  async users(): Promise<User[]> {
    return this.usersService.users();
  }

  @Post()
  async createUser(
    @Body() userData: { name: string; email: string; password: string },
  ): Promise<User> {
    return this.usersService.createUser(userData);
  }
}
```

### Usersモジュールを追加

```
% docker-compose run nest nest g module users
```
最後にUsersModuleを作成し、AppModuleに追加します。
users配下にusers.module.tsというファイルを作成し、下記コードを追加します。

```ts:src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaService } from './../prisma/prisma.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService, PrismaService],
})
export class UsersModule {}
```

### app.module.tsにUsersモジュールをimport
※基本generateコマンドでファイルを生成した場合はその時点で自動的にここに追加されます

```ts:src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaService } from './prisma/prisma.service';
import { UsersService } from './users/users.service';
import { UsersController } from './users/users.controller';
import { UsersModule } from './users/users.module';

@Module({
  imports: [UsersModule], // 追加
  controllers: [AppController, UsersController],
  providers: [AppService, PrismaService, UsersService],
})
export class AppModule {}
```
これでNest.jsにPrismaを導入し、アプリケーションからDBを操作できるようになりました

###  curl コマンドでAPIの動作確認

```
% curl -H "content-type: application/json" -X POST -d'{"name":"test", "email":"test@sample.com", "password":"12345678"}' http://localhost:3000/users
→usersコントローラcreateUserアクション実行

% curl -X GET http://localhost:3000/users/1
→usersコントローラfindUserByIdアクション実行

% curl -X GET http://localhost:3000/users
→usersコントローラusersアクション実行
```

POST後のデータ確認時はMySQLの管理ツールphpmyadminでも確認できます。
http://localhost:8080/

以上です