📐

Rails エンジニアが Prisma に入門する:Getting Started 編

2022/07/20に公開

前回

https://zenn.dev/gabu/articles/dfba06b43ea3a5

の続きです。

はじめに

NestJS に入門するはずが、データストアについては自分で決める必要があることが分かったので、ここ数日いろいろ調べて先人たちの経験を読み漁った結果 Prisma に入門します。その調査のまとめは別途書きたいと思いますが、今日はとりあえず触っていきましょう。

Getting started

Getting started にはいくつかメニューがあるのですが、普通の Rails エンジニアとしては Relational database を使った開発フローを知っておきたいので Start from scratch > Relational databases をやっていきます。

$ mkdir hello-prisma
$ cd hello-prisma
$ npm init -y
$ npm install prisma typescript ts-node @types/node --save-dev
$ code .

tsconfig.json を作ります。

tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": ["esnext"],
    "esModuleInterop": true
  }
}

Prisma CLI

ドキュメントによると、Prisma CLI は npx で実行するようです。一度実行してみましょう。コマンドを指定せずに実行するとヘルプが見られますね。

$ npx prisma

◭  Prisma is a modern DB toolkit to query, migrate and model your database (https://prisma.io)

Usage

  $ prisma [command]

Commands

            init   Set up Prisma for your app
        generate   Generate artifacts (e.g. Prisma Client)
              db   Manage your database schema and lifecycle
         migrate   Migrate your database
          studio   Browse your data with Prisma Studio
          format   Format your schema

Flags

     --preview-feature   Run Preview Prisma commands

Examples

  Set up a new Prisma project
  $ prisma init

  Generate artifacts (e.g. Prisma Client)
  $ prisma generate

  Browse your data
  $ prisma studio

  Create migrations from your Prisma schema, apply them to the database, generate artifacts (e.g. Prisma Client)
  $ prisma migrate dev

  Pull the schema from an existing database, updating the Prisma schema
  $ prisma db pull

  Push the Prisma schema state to the database
  $ prisma db push

分かりやすいですね〜。

prisma init

Prisma をセットアップします。

$ npx prisma init

✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb.
3. Run prisma db pull to turn your database schema into a Prisma schema.
4. Run prisma generate to generate the Prisma Client. You can then start querying your database.

More information in our documentation:
https://pris.ly/d/getting-started
  • prisma/schema.prisma に Prisma スキーマが作られました。ここにモデルなどを定義していくようです。楽しみ!
  • .env ファイルが作られました。DATABASE_URLなどを設定していくようです。

DB の設定

引き続き、次のページ Connect your database をやっていきます。

prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

見たままですが provider は postgresql url は env から取得するようになっています。ということで .envDATABASE_URL を設定しましょう。
生成された .env では johndoe さんのサンプルになっているので、自身の環境に合わせたURLに変更します。私はローカルの開発環境では Homebrew でサクッと入れた PostgreSQL をデフォルト状態で使っているので、以下で接続できちゃいます。

.env
DATABASE_URL="postgresql://gabu@localhost/hello_prisma"

DB の作成

Rails であればこの後 rails db:create で DB の作成をコマンドから行いますが、Prisma には同様のコマンドがなく、どうやら prisma migrate dev で DB がなければ自動的に CREATE してくれるようです。つまり、CREATE DATABASE だけを実行するコマンドがないだけのようです。

Prisma Migrate

引き続き、次のページ Using Prisma Migrate をやっていきます。

schema.prisma にモデルを追加します。

prisma/schema.prisma
model Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String   @db.VarChar(255)
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

model Profile {
  id     Int     @id @default(autoincrement())
  bio    String?
  user   User    @relation(fields: [userId], references: [id])
  userId Int     @unique
}

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  posts   Post[]
  profile Profile?
}

次に、ドキュメントにある prisma migrate dev を実行しますが、このコマンドは

  1. マイグレーションファイルの作成
  2. DBへの反映

を一気に行うようです。

さらに、自動的に prisma generate が実行され、 schema.prismagenerator clientprisma-client-js が設定されている場合は Prisma Client のコードが生成され、@prisma/client パッケージが見つからない場合は自動的にインストールされます。

$ npx prisma migrate dev --name init
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "hello_prisma", schema "public" at "localhost:5432"

Applying migration `20220720022636_init`

The following migration(s) have been created and applied from new schema changes:

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

Your database is now in sync with your schema.

Running generate... (Use --skip-generate to skip the generators)

added 2 packages, and audited 24 packages in 3s

found 0 vulnerabilities

✔ Generated Prisma Client (4.1.0 | library) to ./node_modules/@prisma/client in 50ms

Prisma Client が気になりますが、先に生成されたマイグレーションファイルを見てみましょう。

prisma/migrations/20220720022636_init/migration.sql
-- CreateTable
CREATE TABLE "Post" (
    "id" SERIAL NOT NULL,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" TIMESTAMP(3) NOT NULL,
    "title" VARCHAR(255) NOT NULL,
    "content" TEXT,
    "published" BOOLEAN NOT NULL DEFAULT false,
    "authorId" INTEGER NOT NULL,

    CONSTRAINT "Post_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Profile" (
    "id" SERIAL NOT NULL,
    "bio" TEXT,
    "userId" INTEGER NOT NULL,

    CONSTRAINT "Profile_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "email" TEXT NOT NULL,
    "name" TEXT,

    CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId");

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

-- AddForeignKey
ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

SQL を書かずに済むのは最高ですね!

テーブルができているかも見てみます。おっと、テーブル名はモデル名そのままになるんですね〜。

ちなみに私は TablePlus を愛用しています。GUI としての使い勝手も良いですし、SQL を書いてデータを確認したり、本番環境のデータのメンテナンスを行ったり(GUI で行った操作をコミットするまで溜めておける機能が最高です。もちろん SQL でメンテナンスすることも多いです)、Redash のクエリーを書く前の下書きをローカル環境でやったりと、大活躍です。

prisma generate

prisma generate コマンドはこのページの図が分かりやすいです。

ここは理解するだけで特に作業はないので、やっていることが想像できたら次へ進みましょう。

Prisma Client

引き続き、次のページ Querying the database をやっていきます。

やっとコードを使ってDBの操作ができますね!!

プロジェクト直下に index.ts を作ります。

index.ts
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // ... you will write your Prisma Client queries here
}

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

書いてあるとおりなので解説は公式ドキュメントに任せます!

そしたら、 main で全ユーザーを取得してコンソールに出力してみます。

index.ts
async function main() {
  // ... you will write your Prisma Client queries here
+  const allUsers = await prisma.user.findMany()
+  console.log(allUsers)
}

ts-node で実行。

$ npx ts-node index.ts
[]

次に create ですね。リレーションのある postsprofile もせーので作れるようです。便利〜

  await prisma.user.create({
    data: {
      name: 'Alice',
      email: 'alice@prisma.io',
      posts: {
        create: { title: 'Hello World' },
      },
      profile: {
        create: { bio: 'I like turtles' },
      },
    },
  })

findMany でリレーションを include して取得することもできるようです。便利〜

  const allUsers = await prisma.user.findMany({
    include: {
      posts: true,
      profile: true,
    },
  })

main 全体はこんな感じで。

index.ts
async function main() {
  await prisma.user.create({
    data: {
      name: 'Alice',
      email: 'alice@prisma.io',
      posts: {
        create: { title: 'Hello World' },
      },
      profile: {
        create: { bio: 'I like turtles' },
      },
    },
  })

  const allUsers = await prisma.user.findMany({
    include: {
      posts: true,
      profile: true,
    },
  })
  console.dir(allUsers, { depth: null })
}

もう一度実行してみます。

$ npx ts-node index.ts
[
  {
    id: 1,
    email: 'alice@prisma.io',
    name: 'Alice',
    posts: [
      {
        id: 1,
        createdAt: 2022-07-20T03:06:04.300Z,
        updatedAt: 2022-07-20T03:06:04.301Z,
        title: 'Hello World',
        content: null,
        published: false,
        authorId: 1
      }
    ],
    profile: { id: 1, bio: 'I like turtles', userId: 1 }
  }
]

このサンプルコードで使った、リレーションを同時に書き込む Nested write と、リレーションフィールド Relation fields についてリンク先を読んでおくと良さそうです。

保存されたレコードはこんな感じで、Post.authorIdProfile.userIdUser.id が入っていて、ActiveRecord の belongs_to と同じ考え方で良さそうです。逆向きに考えると has_many has_one なリレーションも同様ですね。

最後に update ですね。main を書き換えます。

index.ts
async function main() {
  const post = await prisma.post.update({
    where: { id: 1 },
    data: { published: true },
  })
  console.log(post)
}

これまたコードに書いてあるとおりですが、where で指定された { id: 1 } なレコードを data で指定された { published: true } で更新するということですね。

$ npx ts-node index.ts
{
  id: 1,
  createdAt: 2022-07-20T03:06:04.300Z,
  updatedAt: 2022-07-20T03:18:57.294Z,
  title: 'Hello World',
  content: null,
  published: true,
  authorId: 1
}

publishedtrue に更新できました!

これで create,read,update ができました。delete もまぁ普通にできるでしょう!長くなってきたので飛ばします。。

おわり

チュートリアルとしては以上でした。次は、Prisma Client を掘っていくでもいいですが、実装自体はリファレンスを見ながら何とでもできそうな気がしているので、実サービスのことを考えると先に Prisma Migrate の方を掘っていきたいと思います。Webサービスを運用する身としては、DBの運用は慎重に行いたいですしね。

とはいえ、

  • ALTER系の作法
  • 本番DBへの反映

ぐらいでしょうか。

それでは!

株式会社モニクル

Discussion