Prisma というデータベースに関する便利な機能を提供するツールをずっと触ってみようと思っていたのですが、次期バージョンが鋭意開発中でプレビュー版として使えるようなので簡単なREST APIを開発しながら使い勝手を見てみました。(現行のバージョン1は全く触れたことがありません)


Prisma2について

READMEによると Prisma2 は以下のツールで構成されます。

  • Prisma Client JS: Type-safe and auto-generated database client (“ORM replacement”)
  • Prisma Migrate: Declarative data modeling and migrations
  • Studio: Admin UI to support various database workflows

この中で Prisma Migrate については試してみたもののエラーが多く今回使うのは諦めました。(2.0.0-preview020.1を使った場合)
うまくいくケースもあったのでその時の感触としては、Prismaのスキーマをベースにマイグレーションができるのは便利そうだなという感じでした。
これについてはIssueも積極的に対応されているようなので、もう少し後にまた試してみようかと思います。

  • この記事を書いた直後に 2.0.0-preview020.2リリースされており、こちらを使うとPrisma Migrateもうまく動くようだったので試しました。(「Prisma Migrateを使う場合」を追記しました)
  • 2020年1月時点では頻繁にバグフィックスなどが対応されリリースされているようです。正式リリースの時点でこの内容が有効かどうかはわかりませんが、様子を追っていきたいと思っています。

以下ではPrisma2を試すにあたってよくあるTODOを管理するREST APIを Prisma Clinet JS で生成したクライアントとExpressを使って実装してみました。

最終的なコードは以下のリポジトリにあるので、実際にコマンドを実行する際などに参考にしてください。

データベースのセットアップ(Prisma Migrateを使わない場合)

先述の通りPrisma Migrateを使うのは諦めたので、DockerでPostgreSQLのコンテナを立ててあらかじめテーブルを作っておきます。
(Prisma Migrateを使いたい場合は記事の最後に追記した内容を見てください)

docker-composeでコンテナを立てるとき init.sql が実行され UserTodoItem というテーブルが作成されます。

$ docker-compose up -d
CREATE TABLE "User" (
    user_id bigserial PRIMARY KEY,
    name varchar(100) NOT NULL
);

CREATE TABLE "TodoItem" (
    todo_id bigserial PRIMARY KEY,
    user_id bigint NOT NULL REFERENCES "User" (user_id) ON DELETE CASCADE,
    text text NOT NULL
);

CREATE INDEX todo_item_user_id_idx ON "TodoItem" (user_id);

これで実行したPostgreSQLコンテナへlocalhost:5432でアクセスできるようになります。

データベースはローカルで立ててもいいですし、MySQLやSQLiteも使えるのでご自分の環境に合わせて読み替えてください。

Prismaプロジェクトのセットアップ

初期化

以下のコマンドでPrismaプロジェクトの初期化を行います。

$ npx prisma2 init .

これはカレントディレクトリに対して実行していますが、新しくディレクトリを作成する場合は init new-project のように指定できます。

また prisma2 をグローバルインストールしている場合は以下のようにも実行できます。

$ prisma2 init .

schemaの更新とクライアント生成

初期化時に prisma/schema.prisma が生成されるので、セットアップしたデータベースに接続するために以下のように書き換えます。

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

datasource db {
  provider = "postgresql"
  url      = "postgresql://prisma:prisma@localhost:5432/prisma?schema=public"
}

この状態で introspect コマンドを実行するとデータベースのテーブルからモデルを生成しSchemaが更新されます。

$ prisma2 introspect
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = "postgresql://prisma:prisma@localhost:5432/prisma?schema=public"
}

model TodoItem {
  text    String
  todo_id Int    @id
  user_id User
}

model User {
  name      String
  user_id   Int        @id
  todoItems TodoItem[]
}

更新されたSchemaを見るとあらかじめ作成してあった UserTodoItem がモデルとして取り込まれています。

この状態でschemaを元にクライアントコードを生成します。

$ prisma2 generate

クライアントコードは node_modules 以下に生成され、普通はignoreされていると思うのでgitでは差分はでません。

ここまできたらクライアントコードを使えるようになっているはずです。

API実装

基本的な構成やコードは以下のサンプルプロジェクトを参考にしていますので、実装についてはかいつまんで紹介します。

PRIMARY KEYについて

例としてUserを追加する場合、サンプルプロジェクトでは以下のようなコードが書かれています。

  const result = await prisma.users.create({
    data: { ...req.body },
  })

user_id は自動的に割り振られてほしいのですが、 name のみをリクエストで与えると user_id がありませんというエラーになってしまいます。

これについてはschemaでPRIMARY KEYに対応するフィールドに @default(autoincrement()) を追加することで解決できました。
ツールで生成されたファイルを直接編集するのはマナーとしてよくないのですが、機能として開発される見込みはありそうなのでこの先に期待というところでしょうか。

エンドポイント追加

Userを追加するエンドポイントは以下のように書くことができます。
(エラーハンドリングや認証は実際はもっとちゃんとしたほうがいいですが)

app.post(`/users`, async (req, res) => {
  try {
    const result = await prisma.users.create({
      data: { ...req.body },
    })
    res.json(result)
  } catch (err) {
    console.error(err)
    res.sendStatus(400)
  }
})

基本的に生成したクライアントによってモデルに対して prisma.models.createprisma.models.findOne のようなメソッドが使えるので他のORMを使ったことがあればある程度使い方はイメージできそうです。

リレーションに関する表現は慣れが必要そうですが、全体的には結構使いやすそうだなと思いました。

Prisma Studio

最後にデータベースの管理画面として使える Prisma Studio を試してみました。

これまでの設定ができていればコマンド一発で管理画面を開くことができます。
(現在は --experimental オプションの指定が必要です)

$ prisma2 studio --experimental

コマンドを実行するとlocalhost:5555で Prisma Studio を開くことができます。

既存のデータを確認したり、新しいレコードを作成したりすることができました。
見たところよくあるGUIツールとそこまで変わらないんじゃないかという感じですが、この先便利な機能が使えるようになるかもしれません。


まとめ

Prisma2を使って簡単でありますがREST APIを実装してみました。

現状プレビュー版なのでバグが残っていたり機能が不足しているような印象はありますが、それぞれのツールが提供する機能はAPI開発のプロセスで役に立ちそうだなという感覚もありました。

過去にExpressを試そうと思った時マイグレーションやORMのツールを選ぶだけで手が止まってしまった記憶がありますが、Prisma2はこのあたりの面倒なところを解決してくれるかもしれません。

一応時期は違いますが2020年中にはそれぞれのツールが正式にリリースされる予定とのことなので、状況を追いつつ小さなプロジェクトで試してみようかと思います。


Prisma Migrateを使う場合

Prisma Migratre を使ってスキーマの定義、マイグレーションの実行をするパターンも試してみました。

基本的な使い方は他のマイグレーションツールに近いかと思います。

  1. マイグレーションファイルの生成
    schema.prisma を更新する(モデルの追加、フィールドの追加など)
    prisma2 migrate save --experimental でスキーマの差分をもとに prisma/migrations 以下にマイグレーションファイルを生成される

  2. マイグレーションを適用
    prisma2 migrate up --experimental でマイグレーションの差分が適用される

  3. マイグレーションのロールバック
    prisma2 migrate down --experimental でマイグレーションをロールバック

マイグレーション後は generate コマンドでクライアントを生成して使うだけです。

スキーマの書き方自体はまだあまり見ていませんが、スキーマの表現力が高くなればこのパターンでも運用していけるかもしれません。