Open21

NestJS+PrismaでRESTAPIをつくる

takaha4ktakaha4k

準備

空のGitリポジトリをクローンしたあと、projectを新規作成

nest new .

無視リスト一部修正

gitignore
node_modules

REST APIのリソース自動生成

nest g resource [Endpoint Name]
# REST APIを選択
# yを入力
takaha4ktakaha4k

DBコンテナおよびWEBクライアント用意する

docker-compose.ymlを用意して、docker compose upを実行する

docker-compose.yml
version: '3'

services:
  db:
    image: mysql:5.7 # プロダクトで使うならバージョンはlatestじゃないほうが良いです
    container_name: mysql_db
    environment:
      MYSQL_ROOT_PASSWORD: root # MySQLパスワード設定
      MYSQL_DATABASE: test # MySQLデータベース名設定
      TZ: 'Asia/Tokyo'
    ports:
      - '3306:3306'

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: mysql_web
    ports:
      - 8080:80
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=db
      - PMA_USER=root
      - PMA_PASSWORD=root

takaha4ktakaha4k

DB接続情報を.envに記載

.env
DATABASE_URL="mysql://root:root@localhost/test"

DBモデリング

いくつかモデルを追加してみる。

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

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

model User {
  id       Int        @id @default(autoincrement())
  name     String
  phone    String
  Call     Call[]
  Response Response[]
}

model Call {
  id        Int        @id @default(autoincrement())
  email     String     @unique
  name      String?
  responses Response[]
  patient   User?      @relation(fields: [patientId], references: [id])
  patientId Int?
}

model Response {
  id        Int    @id @default(autoincrement())
  request   String
  pushed_no Int
  call      Call?  @relation(fields: [callId], references: [id])
  callId    Int?
  patient   User?  @relation(fields: [patientId], references: [id])
  patientId Int?
}

takaha4ktakaha4k

Prisma Clientのセットアップ

ファイルを作成

touch src/prisma.service.ts

ファイルにコード書く

src/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();
    });
  }
}
takaha4ktakaha4k

ER自動作図

インストール

npm i -D prisma-erd-generator @mermaid-js/mermaid-cli
# or
yarn add -D prisma-erd-generator @mermaid-js/mermaid-cli

shema.prismaに追記。

prisma/schema.prisma
...
generator erd {
  provider = "prisma-erd-generator"
  output = "../scheme.svg"
  theme = "forest"
}
...

実行コマンド

npx prisma generate
takaha4ktakaha4k

huskyを入れてpre-commit処理実装

私の過去の記事を参考に以下

.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint-staged
npx prisma generate

takaha4ktakaha4k

AppRunnerにデプロイ

ルートディレクトリにapprunner.yamlを作成する

※--unsafe-permをオプション引数に渡さないとnpmインスールでコケる

apprunner.yaml
version: 1.0
runtime: nodejs14
build:
  commands:
    build:
      - npm ci --unsafe-perm
      - npx prisma generate
      - npx prisma migrate deploy
      - npm run build
run:
  command: npm prisma migrate deploy && npm run start
  network:
    port: 3000

AppRunnerのセットアップを行う。

  1. サービスの作成
  2. ソースコードリポジトリ を選択
  3. GitHubに接続 新規追加 を選択
  4. 別のアプリケーションをインストールする を選択
  5. GitHubにログインして認可
  6. 接続名 apprunner と入力
  7. リポジトリ及びブランチを選択
  8. デプロイ設定 自動 を選択 次へ
  9. 設定ファイルを使用 を選択 次へ
  10. アプリ名を入力して 次へ
  11. 作成とデプロイをクリック。
takaha4ktakaha4k

AppRunnerビルドはうまくいったが、Failする・・・
Dockerイメージで対応するか・・・

takaha4ktakaha4k

AppRunnerで上手く行く例。
NPMライブラリが原因で--unsafe-permオプションがいるっぽい。

apprunner.yaml
version: 1.0
runtime: nodejs14
build:
  commands:
    build:
      - npm i --unsafe-perm
      - npx prisma generate
      - npx prisma migrate deploy
      - npm run build
run:
  command: npm run start
  network:
    port: 3000

takaha4ktakaha4k

Seeding your database

公式を参考にSEED処理実装する。

package.jsonに追記する。

package.json
"prisma": {
  "seed": "ts-node prisma/seed.ts"
},

prismaディレクトリにseed.tsファイルを作成する。
適当なユーザデータをインサートするスクリプトを書く。

prisma/seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

async function main() {
  const alice = await prisma.user.create({
    data: {
      hospitalId: 1,
      name: 'Alice',
      email: 'alice@prisma.io',
      phone: '0000',
    },
  });

  console.log({ alice });
}

main()
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

seed.tsを実行する。

npx prisma db seed

実行後のメッセージ

Running seed command `ts-node prisma/seed.ts` ...
{
  alice: {
    id: 3,
    hospitalId: 1,
    name: 'Alice',
    email: 'alice@prisma.io',
    phone: '0000',
    createdAt: 2022-05-29T23:30:04.762Z,
    updatedAt: 2022-05-29T23:30:04.763Z
  }
}

🌱  The seed command has been executed.

Prisma Studioを起動してデータを確認しておく。

npx prisma studio
takaha4ktakaha4k

Create multiple records

Userデータをまとめて作りたい。

しかし、createManyメソッドが存在しない。
調べる。

SQLliteはcreateManyには対応していない模様

以下で対応する

categories.forEach(async (category) => {
    console.log(`Creating category ${category.categoryName}...`);
    await prisma.category.create({
        data: {
            categoryName: category.categoryName,
            categoryDescription: category.categoryDescription
        }
    })
    console.log(`Category ${category.categoryName} created!~`);

})
takaha4ktakaha4k

Json読み込みでエラー解消できず。
MySQLへ変更を試みる。

takaha4ktakaha4k

Prisma reset

SQLiteからMySQLへ変更する。

prisma/schema.prisma
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
  // provider = "sqlite"
  // url      = "file:./dev.db"
}
.env
DATABASE_URL="mysql://root:root@localhost:3306/test"
npx prisma migrate reset
takaha4ktakaha4k

エラーするため、prismaディレクトリのmigrationsディレクトリを削除する

rm -rf migrations

再度マイグレーションを実施。

npx prisma migrate dev --name init
takaha4ktakaha4k

エラートラブルシュート関連

m ERR! code 1
npm ERR! path /root/repos/reborn/node_modules/puppeteer
npm ERR! command failed
npm ERR! command sh -c node install.js
npm ERR! ERROR: Failed to set up Chromium r991974! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download.
npm ERR! Error: self signed certificate in certificate chain
npm ERR!     at TLSSocket.onConnectSecure (_tls_wrap.js:1515:34)
npm ERR!     at TLSSocket.emit (events.js:400:28)
npm ERR!     at TLSSocket._finishInit (_tls_wrap.js:937:8)
npm ERR!     at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:709:12) {
npm ERR!   code: 'SELF_SIGNED_CERT_IN_CHAIN'
npm ERR! }

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2022-05-30T07_45_29_989Z-debug-0.log

調べた所、@mermaid-js/mermaid-cliの依存ライブラリで問題がり。
mermaidはER作図用途。アンインストールする

npm rm @mermaid-js/mermaid-cli

NestJSアプデ

4 high severity vulnerabilitiesとの警告が出ている。
NestJSをアップデート試みる

npm i -g @nestjs/cli npm-check-updates
nest update --force
takaha4ktakaha4k

DBとの連係

Herokuで用意したMySQLの場合、権限問題でMigrationに失敗する

Error: P3014

Prisma Migrate could not create the shadow database. Please make sure the database user has permission to create databases. Read more about the shadow database (and workarounds) at https://pris.ly/d/migrate-shadow

Original error: Error code: P1010

仕方がないのでAWS RDSを作る。

  1. RDS→サブネットグループ選択して、DBサブネットグループを作成
  2. AZを選択して、Publicアクセス可能なVPCを選択
  3. データベース→データベースの作成をクリック
  4. 標準作成、エンジンのタイプをAuroraを選択
  5. エディションはMySQL互換
  6. バージョンは2.10.2
  7. テンプレは開発/テスト
  8. DBインスタンスクラスはバースト可能クラスを選択←選択しないとデフォルトでlargeが選択される
  9. Auroraレプリカは作成しない をチェック
  10. VPCを選択後、先程作成したサブネットグループを選択
  11. パブリックアクセスあり を選択
  12. VPCセキュリティグループは新規作成
  13. defaultは選択解除
  14. あとは初期値でデータベースの作成 をクリック