m_shige1979のときどきITブログ

プログラムの勉強をしながら学習したことや経験したことをぼそぼそと書いていきます

Github(変なおっさんの顔でるので気をつけてね)

https://github.com/mshige1979

AWSのECSを構築するその1(ネットワーク構築)

完成予定

f:id:m_shige1979:20211229103312p:plain

今回

f:id:m_shige1979:20211229103354p:plain

今回やること

  1. VPCの作成
  2. サブネットの作成
  3. ルートテーブルの作成&サブネットの関連づけ
  4. インターネットゲートウェイの作成

VPCの作成

コンソールよりVPCの作成を押下

f:id:m_shige1979:20211229103745p:plain

VPCのパラメータを設定

f:id:m_shige1979:20211229104128p:plain f:id:m_shige1979:20211229104238p:plain

VPC作成完了

f:id:m_shige1979:20211229104412p:plain

サブネットを作成

サブネットを作成を押下

f:id:m_shige1979:20211229104658p:plain

パラメータを設定

f:id:m_shige1979:20211229105043p:plain f:id:m_shige1979:20211229105130p:plain f:id:m_shige1979:20211229105212p:plain f:id:m_shige1979:20211229105301p:plain f:id:m_shige1979:20211229105337p:plain

サブネットが作成されたことを確認

f:id:m_shige1979:20211229105628p:plain

ルートテーブルの作成

ルートテーブルの作成を選択

f:id:m_shige1979:20211229105933p:plain

パブリック用のルートテーブルを作成

f:id:m_shige1979:20211229110145p:plain

パブリック用のサブネットの関連付けを選択

f:id:m_shige1979:20211229110332p:plain

パブリック用のサブネットを関連づけ

f:id:m_shige1979:20211229110624p:plainf:id:m_shige1979:20211229110802p:plain

プライベート用のルートテーブルを作成

f:id:m_shige1979:20211229111026p:plain

プライベート用のサブネットの関連付けを選択

f:id:m_shige1979:20211229111228p:plain

プライベート用のサブネットを関連づけ

f:id:m_shige1979:20211229111537p:plainf:id:m_shige1979:20211229111710p:plain

インターネットゲートウェイの作成

インターネットゲートウェイの作成を選択する

f:id:m_shige1979:20211229111923p:plain

パラメータを設定

f:id:m_shige1979:20211229112046p:plain

VPCへアタッチを選択する

f:id:m_shige1979:20211229112258p:plain

VPCを指定してアタッチ

f:id:m_shige1979:20211229112449p:plain

パブリック用のルートテーブルのルートを編集する

f:id:m_shige1979:20211229112743p:plain

インターネットゲートウェイを割り当て

f:id:m_shige1979:20211229112943p:plain

関連記事

m-shige1979.hatenablog.com m-shige1979.hatenablog.com m-shige1979.hatenablog.com m-shige1979.hatenablog.com

EC2で出力するログをcloudwatchlogsに連携する

やりたいこと

nginxやバックエンドのアプリログをcloudwatchlogsへ流す

参考にしたやつ

【AWS】Amazon CloudWatch Logs でログ収集をやってみた|コラム|クラウドソリューション|サービス|法人のお客さま|NTT東日本

クイックスタート: 実行中の EC2 Linux インスタンスに CloudWatch Logs エージェントをインストールして設定する - Amazon CloudWatch Logs

流れ

  1. EC2インスタンスを起動
  2. EC2インスタンスにnginxを入れてログの出力を確認する
  3. cloudwatchへの出力用ポリシーを作成する
  4. IAMロールを作成し、3で作成したポリシーを割り当て
  5. EC2にロールを設定する
  6. awslogsのインストール

1.EC2インスタンスを起動

こんな感じ f:id:m_shige1979:20211211232941p:plain

2. EC2インスタンスにnginxを入れてログの出力を確認する

nginxのインストール

$ sudo yum update -y
$ sudo amazon-linux-extras install nginx1 -y
$ nginx -v
nginx version: nginx/1.20.0
$ sudo systemctl start nginx
$ sudo systemctl enable nginx
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.
$

ブラウザで確認

f:id:m_shige1979:20211211233741p:plain

ログファイルを確認

/etc/nginx/conf.d/default.conf

server {
    listen       80;
    listen       [::]:80;
    server_name  _;
    root         /usr/share/nginx/html;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    error_page 404 /404.html;
    location = /404.html {
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }
}

再起動

$ sudo systemctl restart nginx
$

$ sudo ls -la /var/log/nginx/
合計 8
drwx------ 2 root root   41 12月 11 14:33 .
drwxr-xr-x 8 root root  333 12月 11 14:32 ..
-rw-r--r-- 1 root root 1075 12月 11 14:46 access.log
-rw-r--r-- 1 root root  604 12月 11 14:45 error.log
$

3. cloudwatchへの出力用ポリシーを作成する

①ポリシーを作成を選択

f:id:m_shige1979:20211211234944p:plain

②cloudwatchlog用のアクションを選択

f:id:m_shige1979:20211211235411p:plain

タグをつける

f:id:m_shige1979:20211211235632p:plain

ポリシー名をつけて作成

f:id:m_shige1979:20211211235840p:plain

4. IAMロールを作成し、3で作成したポリシーを割り当て

f:id:m_shige1979:20211212000044p:plainf:id:m_shige1979:20211212000235p:plainf:id:m_shige1979:20211212000422p:plainf:id:m_shige1979:20211212000548p:plainf:id:m_shige1979:20211212000732p:plain

5. EC2にロールを設定する

f:id:m_shige1979:20211212001047p:plainf:id:m_shige1979:20211212001218p:plain

6. awslogsのインストール

yum install

$ sudo yum install awslogs -y

②backup

$ sudo cp -p /etc/awslogs/awscli.conf /etc/awslogs/awscli.conf.org
$ sudo cp -p /etc/awslogs/awslogs.conf /etc/awslogs/awslogs.conf.org

③/etc/awslogs/awscli.confを編集

※東京リージョンに変更

[plugins]
cwlogs = cwlogs
[default]
region = ap-northeast-1

④/etc/awslogs/awslogs.conf

※末尾に追加

[HttpdAccessLog]
datetime_format = %d/%b/%Y:%H/%M/%S
file = /var/log/nginx/access.log
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = /HttpdAccessLog

[HttpdErrorLog]
datetime_format = %d/%b/%Y:%H/%M/%S
file = /var/log/nginx/error.log
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = /HttpdErrorLog

⑤awslogs起動、自動起動設定

sudo systemctl start awslogsd.service
sudo systemctl enable awslogsd.service

f:id:m_shige1979:20211212003329p:plain

できた

所感

手順はそんなに難しくない ec2のログを手間なく送信したい場合は使えると思われる。 そういえばローテートされたらどうなるのかな・・・

PrismaでDBのhasOneを試す

前の記事

m-shige1979.hatenablog.com

今回

f:id:m_shige1979:20211123190202p:plain

外部キーの定義って

後付けや変更ができないのかな? 普段SQLを使うことが多かったので今回prisma migrate devでエラーでできないから分かっていない・・・

設定したもの

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")
}

// ER図作成用
generator erd {
  provider = "prisma-erd-generator"
  output   = "../doc/ERD.md"
}

// ユーザーデータ管理用
model User {
  // ID
  id    Int     @id @default(autoincrement())
  // メールアドレス
  email String  @unique
  // 氏名
  name  String?
  // 年齢
  age   Int?

  // 登録日時
  createdAt DateTime @default(now()) @map("created_at")
  // 更新日時
  updatedAt DateTime @default(now()) @map("updated_at")

  // プロフィール(One to One)
  profile Profile?
  // 記事(One to Many)
  posts   Post[]

  // テーブルの物理名
  @@map("users")
}

// プロフィール管理用
model Profile {
  // ID
  id       Int    @id @default(autoincrement())
  // ニックネーム
  nickName String @map("nick_name")

  // 登録日時
  createdAt DateTime @default(now()) @map("created_at")
  // 更新日時
  updatedAt DateTime @default(now()) @map("updated_at")

  // 親テーブルの関連づけ
  userId Int  @unique
  user   User @relation(fields: [userId], references: [id])

  // テーブルの物理名
  @@map("profile")
}

// 記事管理用
model Post {
  // ID
  id    Int    @id @default(autoincrement())
  // タイトル
  title String @map("title")
  // 登録日時
  createdAt DateTime @default(now()) @map("created_at")
  // 更新日時
  updatedAt DateTime @default(now()) @map("updated_at")
  
  // 親テーブルの関連づけ
  userId Int
  user   User @relation(fields: [userId], references: [id])

  // テーブルの物理名
  @@map("posts")
}

migrate後のCREATE文

CREATE TABLE `users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `age` int DEFAULT NULL,
  `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_key` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `posts` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `userId` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `posts_userId_fkey` (`userId`),
  CONSTRAINT `posts_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

データ取得方法

基本は前回と同じ、今回は1対多のため、関連テーブルの条件絞り込みが可能

    const user3 = await prisma.user.findFirst({
        where: {
            name: 'hoge1'
        },
        include: {
            profile: {
                select: {
                    nickName: true,
                    createdAt: true,
                },
            },
            posts: {
                select: {
                    title: true
                },
                where: {
                    title: "test1"
                }
            }
        },
    })    
    console.log("case3: ", user3)

配下を全件取得した場合

  id: 16,
  email: 'hoge1@example.com',
  name: 'hoge1',
  age: 20,
  createdAt: 2021-11-23T09:58:03.729Z,
  updatedAt: 2021-11-23T09:58:03.729Z,
  profile: {
    id: 5,
    nickName: 'aaaa',
    createdAt: 2021-11-23T09:58:03.729Z,
    updatedAt: 2021-11-23T09:58:03.729Z,
    userId: 16
  },
  posts: [
    {
      id: 1,
      title: 'test1',
      createdAt: 2021-11-23T09:58:03.729Z,
      updatedAt: 2021-11-23T09:58:03.729Z,
      userId: 16
    },
    {
      id: 2,
      title: 'test2',
      createdAt: 2021-11-23T09:58:03.729Z,
      updatedAt: 2021-11-23T09:58:03.729Z,
      userId: 16
    },
    {
      id: 3,
      title: 'test3',
      createdAt: 2021-11-23T09:58:03.729Z,
      updatedAt: 2021-11-23T09:58:03.729Z,
      userId: 16
    }
  ]
}

一部のみ取得した場合

case3:  {
  id: 16,
  email: 'hoge1@example.com',
  name: 'hoge1',
  age: 20,
  createdAt: 2021-11-23T09:58:03.729Z,
  updatedAt: 2021-11-23T09:58:03.729Z,
  profile: { nickName: 'aaaa', createdAt: 2021-11-23T09:58:03.729Z },
  posts: [ { title: 'test1' } ]
}

所感

ある程度のことはやってくれるイメージ
外部キーのこともあるから頻繁にデータ構造が変わる場合や複雑なSQLがある場合は不向き
次は多対多group by辺りを見てみる

できたもの

github.com

PrismaでDBのhasOneを試す

Prisma

www.prisma.io

hasOneについて

2つのテーブルが1対1、または0の関係のリレーショナル構造のこと

イメージ

f:id:m_shige1979:20211123095306p:plain

Prismaでの定義の方法

prisma/schema.prisma

// ユーザーデータ管理用
model User {
  // ID
  id    Int     @id @default(autoincrement())
  // メールアドレス
  email String  @unique
  // 氏名
  name  String?
  // 年齢
  age   Int?

  // 登録日時
  createdAt DateTime @default(now()) @map("created_at")
  // 更新日時
  updatedAt DateTime @default(now()) @map("updated_at")

  // プロフィール(One to One)
  profile Profile?

  // テーブルの物理名
  @@map("users")
}

// プロフィール管理用
model Profile {
  // ID
  id       Int    @id @default(autoincrement())
  // ニックネーム
  nickName String @map("nick_name")

  // 登録日時
  createdAt DateTime @default(now()) @map("created_at")
  // 更新日時
  updatedAt DateTime @default(now()) @map("updated_at")

  // 親テーブルの関連づけ
  userId Int  @unique
  user   User @relation(fields: [userId], references: [id])

  // テーブルの物理名
  @@map("profile")
}

親モデルへの追加定義

  // プロフィール(One to One)
  profile Profile?

これを追加することで関連するモデルが1または0の状態となる

子モデルへの追加定義

  // 親テーブルの関連づけ
  userId Int  @unique
  user   User @relation(fields: [userId], references: [id])

紐付け用のカラムを追加し親と関連づける

マイグレーション後のCREATE文

CREATE TABLE `users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `age` int DEFAULT NULL,
  `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_key` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `profile` (
  `id` int NOT NULL AUTO_INCREMENT,
  `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `userId` int NOT NULL,
  `nick_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `profile_userId_key` (`userId`),
  CONSTRAINT `profile_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

データ処理

親のみ取得

    const user1 = await prisma.user.findFirst({
        where: {
            name: 'hoge1'
        },
    })    
    console.log("case1: ", user1)

親子のデータを同時に取得

    const user2 = await prisma.user.findFirst({
        where: {
            name: 'hoge1'
        },
        include: {
            profile: true,
        },
    })    
    console.log("case2: ", user2)

子のデータを一部のみ取得

    const user3 = await prisma.user.findFirst({
        where: {
            name: 'hoge1'
        },
        include: {
            profile: {
                select: {
                    nickName: true,
                    createdAt: true,
                },
            },
        },
    })    
    console.log("case3: ", user3)

子のデータより親を引き出す

    const profile1 = prisma.profile.findFirst({
        where: {
            nickName: 'aaaa'
        },
    });
    const user4 = await profile1.user();
    profile1.then((result) => {
        console.log("case4: ", result, user4);
    })

case1:  {
  id: 10,
  email: 'hoge1@example.com',
  name: 'hoge1',
  age: 20,
  createdAt: 2021-11-23T01:14:46.450Z,
  updatedAt: 2021-11-23T01:14:46.450Z
}
case2:  {
  id: 10,
  email: 'hoge1@example.com',
  name: 'hoge1',
  age: 20,
  createdAt: 2021-11-23T01:14:46.450Z,
  updatedAt: 2021-11-23T01:14:46.450Z,
  profile: {
    id: 3,
    nickName: 'aaaa',
    createdAt: 2021-11-23T01:14:46.450Z,
    updatedAt: 2021-11-23T01:14:46.450Z,
    userId: 10
  }
}
case3:  {
  id: 10,
  email: 'hoge1@example.com',
  name: 'hoge1',
  age: 20,
  createdAt: 2021-11-23T01:14:46.450Z,
  updatedAt: 2021-11-23T01:14:46.450Z,
  profile: { nickName: 'aaaa', createdAt: 2021-11-23T01:14:46.450Z }
}
case4:  {
  id: 3,
  nickName: 'aaaa',
  createdAt: 2021-11-23T01:14:46.450Z,
  updatedAt: 2021-11-23T01:14:46.450Z,
  userId: 10
} {
  id: 10,
  email: 'hoge1@example.com',
  name: 'hoge1',
  age: 20,
  createdAt: 2021-11-23T01:14:46.450Z,
  updatedAt: 2021-11-23T01:14:46.450Z
}

親だけ削除とかはできんかった

多分、リレーションの設定で削除も連動するようにしてないせいと思うが seedファイルを改修する際に、削除⇨作成したらエラーになる。 ちょっと気をつけよう

できたやつ

github.com

参考

Relation queries (Concepts) | Prisma Docs

ExpressとPrismaでREST API実装1

前々回

m-shige1979.hatenablog.com

前回

m-shige1979.hatenablog.com

今回は

NodeJSのフレームワークExpressPrismaを使用したREST APIを実装
今回は触りだけなのでシンプルな構成

参考資料

github.com

これ

src/index.ts

import express from 'express'

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

// express server
const app = express()

// リクエストデータをjsonで受け取れるようにする
app.use(express.json())
app.use(express.urlencoded({ extended: true }));

// ユーザー一覧
app.get('/users', async (req, res) => {
    // ユーザー一覧を取得
    const users = await prisma.user.findMany({})

    // 返却
    res.json(users)
})

// ユーザー詳細
app.get('/user/:id', async (req, res) => {
    // パラメータを取得
    const { id } = req.params;
    // ユーザー情報を取得
    const user = await prisma.user.findFirst({
        where: {
            id: Number(id)
        }
    })

    // 返却
    res.json(user)
})

// ユーザー登録
app.post('/user', async (req, res) => {
    // パラメータ取得
    console.log(req.body);
    const { email, name } = req.body;

    // この辺でバリデーション

    // 登録
    const user = await prisma.user.create({
        data: {
            "email": email,
            "name": name
        }
    })

    // 返却
    res.json({
        "status": 0,
        "result": user
    })
})

// ユーザー更新
app.put('/user/:id', async (req, res) => {
    // パラメータ取得
    console.log(req.body);
    const { id } = req.params;
    const { email, name } = req.body;

    // この辺でバリデーション


    // 登録
    const user = await prisma.user.update({
        where: {
            "id": Number(id)
        },
        data: {
            "email": email,
            "name": name,
            "updatedAt": new Date()
        }
    })

    // 返却
    res.json({
        "status": 0,
        "result": user
    })
})


// ユーザー削除
app.delete('/user/:id', async (req, res) => {
    // パラメータを取得
    const { id } = req.params;
    // ユーザー情報を削除
    const user = await prisma.user.deleteMany({
        where: {
            id: Number(id)
        }
    })

    // 返却
    // 返却
    res.json({
        "status": 0,
        "result": user
    })
})


// server listen
const server = app.listen(3000)

prismaオブジェクト配下から各モデルを引っ張ってくる

リレーションでhasOnehasManyの構成をしていてついでに取得とかもやってくれるよう 今回はデータ考えてなかったので実装はしていないです。

VSCodeで書くとエラーが検知しやすい

Typescriptの拡張かPrismaの拡張か判断に困ることはありますが、 「そんな項目ないよ」的な指摘をピンポイントで出してくれるので 誤りに早く気づく

ts-node-devを利用したホットリロード

なんか初回しか検知しないのでおかしいと思って起動時のパラメータを追加 最終的にはこんな感じ

package.json

  "scripts": {
    "start": "ts-node-dev --respawn --debug --exit-child src/index.ts",
  },

今回のもの

github.com

PrismaでSeedでデータを投入する

前回

m-shige1979.hatenablog.com

Prisma

www.prisma.io

今回

Seedでデータを投入する

やり方

なんかprisma/seed.tsを作成して、データを登録する処理を書くみたい

やってみる

プロジェクト構成は前回のまま

package.json

{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@prisma/client": "^3.5.0",
    "@types/express": "^4.17.13",
    "@types/node": "^16.11.9",
    "express": "^4.17.1",
    "ts-node": "^10.4.0",
    "typescript": "^4.5.2"
  },
  "devDependencies": {
    "prisma": "^3.5.0"
  }
}

スクリプト作成

参考になりそうなもの

github.com

prisma/seed.ts

import { PrismaClient, Prisma } from '@prisma/client'
const prisma = new PrismaClient()

// モデル投入用のデータ定義
const userData: Prisma.UserCreateInput[] = [
    {
        name: 'hoge1',
        email: 'hoge1@example.com',
    },
    {
        name: 'hoge2',
        email: 'hoge2@example.com',
    },
    {
        name: 'hoge3',
        email: 'hoge3@example.com',
    },
]

const transfer = async () => {
    const users = [];
    for (const u of userData) {
        const user = prisma.user.create({
            data: u,
        })
        users.push(user);
    }
    return await prisma.$transaction(users);
}

// 定義されたデータを実際のモデルへ登録する処理
const main = async () => {
    console.log(`Start seeding ...`)

    await transfer();

    console.log(`Seeding finished.`)
}

// 処理開始
main()
    .catch((e) => {
        console.error(e)
        process.exit(1)
    })
    .finally(async () => {
        await prisma.$disconnect()
    })

seedコマンドを追加

package.json

以下を追加

  "prisma": {
    "seed": "ts-node prisma/seed.ts"
  }

実行

app# npx prisma db seed
Environment variables loaded from .env
Running seed command `ts-node prisma/seed.ts` ...
Start seeding ...
Seeding finished.

🌱  The seed command has been executed.
app#

f:id:m_shige1979:20211120212558p:plain

OK

所感

seed.tsに共通処理を記載して、あとはデータやテーブルは別ファイルで管理すればそこまで肥大化しないのでは と思っている。 次はそうだな…expressと連携してAPI経由でデータを取得するところをやってみよう。

github

github.com

Typescriptで動作するDBのマイグレーションツールPrismaを触ってみた

Prisma

www.prisma.io

いくつかマイグレーションツール探していてなんか良いのがなかった

最悪、sqlでどうにかしようかなぁと思っていた。

お試し環境

node16のdockerコンテナ

docker-compose

version: "3"

services:

  # backend
  backedn:
    # コンテナ名
    container_name: node-dev
    # build
    image: node:16
    # コンテナの中に入る
    tty: true
    # 他のコンテナ起動後に起動するように制御
    depends_on:
      - db
    # ボリューム
    volumes:
      - "./app"
    # WORKDIR
    working_dir: /app
    # 環境変数
    environment:
      TZ: "Asia/Tokyo"

  # DBサーバ
  db:
    # コンテナ名
    container_name: db
    # build
    image: mysql
    # 環境設定
    environment:
      TZ: Asia/Tokyo
      MYSQL_ROOT_USER: root
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: sample1
      MYSQL_USER: app
      MYSQL_PASSWORD: password
    # コンテナの中に入る
    tty: true
    # ボリューム
    volumes:
      - mysql_data:/var/lib/mysql
    # ポート開放
    ports:
      - 3306:3306

# 名前付きボリュームをdockerホストの管理下で作成
volumes:
  #
  mysql_data: {}

build, up

docker-compose build
docker-compose run --rm backend bash

# node -v
v16.13.0
# npm -v
8.1.0
#

手順

プロジェクト初期化

# npm init -y
Wrote to /app/package.json:

{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}


#

関連パッケージをインストール

npm install --save typescript
npm install --save @types/node
npm install --save ts-node
npx tsc --init
npm install --save express
npm install --save @types/express
npm install --save-dev prisma
npm install --save @prisma/client

# cat package.json
{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@prisma/client": "^3.5.0",
    "@types/express": "^4.17.13",
    "@types/node": "^16.11.9",
    "express": "^4.17.1",
    "ts-node": "^10.4.0",
    "typescript": "^4.5.2"
  },
  "devDependencies": {
    "prisma": "^3.5.0"
  }
}
#

prismaの初期化

npx prisma init

.
|-- package-lock.json
|-- package.json
|-- prisma
|   `-- schema.prisma
`-- tsconfig.json

.envprisma/schema.prismaが生成されるため、DBの情報を修正する

.env

DATABASE_URL="mysql://root:password@db:3306/sample1"

prisma/schema.prisma

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

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

モデルを作成する

prisma/schema.prisma

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

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

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?

  @@map("users")
}

マイグレーション実行

npx prisma migrate dev --name init

↓ 新規の場合はmigrationsディレクトリが作成され、SQLファイルができる

migrations/
  └─ 20211120075456_init/
    └─ migration.sql

DB構成

f:id:m_shige1979:20211120165744p:plain

適当なデータを準備

INSERT INTO users (
    `email`, `name`
) VALUES 
    ('test1@test.com', 'test1'),
    ('test2@test.com', 'test2'),
    ('test3@test.com', 'test3')
;

サンプルコード

index.ts

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

async function main() {
    const allUsers = await prisma.user.findMany({})
    console.dir(allUsers, { depth: null })
}

main()
    .catch((e) => {
        throw e
    })
    .finally(async () => {
        await prisma.$disconnect()
    })

 npx ts-node index.ts

[
  { id: 1, email: 'test1@test.com', name: 'test1' },
  { id: 2, email: 'test2@test.com', name: 'test2' },
  { id: 3, email: 'test3@test.com', name: 'test3' }
]

うん、いい感じ

所感

最初はSequelizeでいく予定でしたがテーブル名を別名で定義するところとかがうまくできなくて 困っていました。 こちらは結構わかりやすい感じ ゴリゴリ対応は最悪SQL直がきにしかないかもですがシンプルな構成の場合はやりやすいかも 次はSeedを試す

参考

Prisma - Next-generation Node.js and TypeScript ORM for Databases

次世代の Node.js ORM 、Prisma を試す

5分で終わるPrisma 2 Tutorial - valid,invalid