アプリケーションを構築する場合にはデータを保存するため必ずデータベースが必要となります。データベースを効率的に管理し、簡単に操作を行うためのツールのがPrismaです。本文書はこれまでPrismaを利用したことがない人を対象にPrismaを利用する上での基本的な操作と設定方法について説明を行なっています。

Prismaはサーバからデータベースを操作する際に仲介する役目を持っており、本文書ではサーバにExpress.js、最初はデータベースのSQLiteを利用します。一通り動作を確認した後にクラウドサービスのSupabase(PostgreSQL)に変更を行なってもSQLiteで行なった処理と同じ操作で動作できるか確認します。PrismaはSQLite、PostgreSQL以外にもMySQL, SQL Server, MongoDB and CockroachDBをサポートしています。

動作確認はmacOSで行い、Node.jsとnpmのインストールは完了した状態から開始しています。またエディターにVisual Studio Code(VSCode)を利用しています。


 % node -v
v16.15.0
 % npm -v
8.5.5
 

Prismaとは

PrismaはNode.js/TypeScript環境で利用できるオープンソースのORM(Object Relational Mapping)です。ORMはオブジェクトとデータベースをマッピングするための技術です。最初はORMのイメージが湧かないので難しい技術なのではと思うかもしれませんがORMによりデータベースの操作をSQLの代わりにオブジェクトのメソッド(create, update, delete, etc..)を通して行うことができます。

Prismaは3つの機能から構成させれています。

  • Prisma Client

    通常データベースの操作にはSQLを記述して実行する必要がありますがPrismaではSQLではなくJavaScript/TypeScriptのメソッドを利用してデータベースを操作することができます。データベースの操作に利用するのがPrisma Clientです。

  • Prisma Migrate

    Prismaの設定ファイル(schema.prisma)にデータモデルを記述しマイグレーションを行うことでデータベースにテーブルを作成することができます。データベースのマイグレーションの履歴も保存することができ操作を取り消すことも再実行することも簡単に行うことができます。テーブルを作成するためのSQLを直接記述することはありません。

  • Prisma Studio

    テーブルをブラウザ上で閲覧するためのUIも提供しておりブラウザ上からテーブル内のデータを編集することができます。モデル間のリレーションの関係もブラウザ上から把握することができます。

Prismaが持つPrisma Client, Prisma Migrate, Prisma Studioを活用することでデータベースに操作に関わるアプリケーション開発のスピードを飛躍的に向上させることができます。

環境の構築

Prismaの動作確認するため環境の構築を行います。TypeScript, Prisma, Expressのインストールを行います。フロントエンドを利用しない代わりにEditorのVisual Studio CodeのExtensionsのREST Clientを利用しています。

任意の名前のフォルダnodejs-prismaを作成し移動後にnpm init -yコマンドでpackage.jsonファイルを作成します。


% mkdir nodejs-prisma
% cd nodejs-prisma 
% npm init -y
 

TypeScriptのインストール

TypeScriptを利用するためにTypeScriptのインストールを行います。


 % npm install typescript ts-node @types/node --save-dev 
 

TypeScriptの設定ファイルであるtsconfig.jsonファイルを作成するためtsc –initコマンドを実行します。本文書では設定の変更は行いません。


 % npx tsc --init

Created a new tsconfig.json with:                                                                                       
                                                                                                                     TS 
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig.json
 

Prismaのインストール

Prismaのインストールを行います。


 % npm install prisma --save-dev
 

インストールが完了するとnpx prismaコマンドを実行することができます。npx prismaPrismaで利用できるコマンドのオプションを確認することができます。


 % 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用の設定ファイルを作成するためにnpx prisma initコマンドを実行します。実行するとプロジェクトフォルダにはprismaフォルダと.envファイルが作成されます。prismaフォルダにはPrismaの設定ファイルであるschema.prismaファイルが作成されています。.envファイルはデータベースに接続するために必要となる環境変数を設定するために利用します。


% 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 (Preview).
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
 

schema.prismaファイルを見るとgeneratorとdatasourceの2つのパートが確認です。

generator clientはschema.prismaファイルの内容を元にデータベースの操作に利用するPrisma Clientを作成するために利用されます。schema.prismaファイルの内容を更新してマイグレーションを実行すると毎回再作成されます(prisma generateコマンドでも作成されます)。作成されたファイルはmode_modulesの./prisma/clientに保存されます。

マイグレーションはコマンドを利用してschema.prismaファイルの内容からデータベース上のテーブルの作成/更新を行う処理です。後ほど説明を行なっています。

datasource dbのproviderにデータベースを設定することができます。先程に表示されているメッセージを確認するとpostgresql, mysql, sqlite, sqlserverとPreviewとしてmongodbとcockroachdbがサポートされていることがわかります。デフォルトはpostgresqlが設定されています。


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

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

Expressのインストール

バックエンドのサーバにはExpress.jsを利用するためexpressのインストールを行います。


 % npm install express
 

TypesScript用にExpress.jsの型定義をインストールします。


 % npm install @types/express --save-dev
 

データベースの設定

データベースには簡易的に利用することができるSQLiteデータベースを利用します。本文書の後半でSupabaseのPostgreSQLに切り替えます。

データベースの設定はschema.prismaファイルで行います。schema.prismaは3つのパート(Data sources, Generators, Data model Definition)で構成されます。デフォルトではData model Definitionの記述はありません。datasourceでデータベースの設定を行うことができます。SQLiteを利用する場合はproviderにsqliteを設定し、urlのDATABASE_URLは.envファイルに設定する必要があります。


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

SQLiteの場合はDATABASE_URLにはファイルのパスを設定します。schema.prismaと同じフォルダにdev.dbファイルが作成されます。


DATABASE_URL="file:./dev.db"

SQLiteはファイルベースのデータベースなのでデータはすべてdev.dbに保存されます。

データベースにUserテーブルを作成するためデータモデルの定義(Data model Definition)を記述する必要があります。モデルの記述はデフォルトのschema.prismaに存在しないので追加で記述する必要があります。modelの後にテーブル名(User)を指定してフィールドとデータタイプ、フィルールドによってオプションを設定します。


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

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

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

UserモデルとUserテーブルは1対1に対応するためUserテーブルはid, email, nameの列を持つことになります。idはInt型でemailとnameは文字列なのでString型を設定しています。Stringの横に?がついているのはnameはオプションでnullでもいいことを表しています。

@idはSQLiteのようなリレーショナルデータベースではプライマリキーになることを表しています。@defaultはデフォルト値を設定することができ、autoincrement()を引数に設定しているのでデータを追加する度にidの値が自動で加算されます。@uniqueはemail列で一意の値のみ登録することができることを意味します。

autoincrement()が利用されていますが必ずしも数字である必要ではなくuuid()を利用することができます。その場合の型はIntではなくStringになります。

マイグレーションの実行

データベースの接続とモデルの設定が完了したのでマイグレーションの実行を行います。マイグレーションを実行することでshema.prismaに記述したモデルを元にデータベースにテーブルを作成することができます。コマンドを実行するとマイグレーションに名前をつける必要があるのでここでは”init”という名前をつけています。任意の名前をつけることができます。


% npx prisma migrate dev
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"

✔ Enter a name for the new migration: … init
Applying migration `20220517073917_init`

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

migrations/
  └─ 20220517073917_create_user/
    └─ 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 87 packages in 4s

7 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

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

コマンド実行時にマイグレーションの名前をつけることができます。その場合は–nameの後に名前を入力します。実行後に名前を聞かれることはなくなります。


% npx prisma migrate dev --name init
 

実行が完了するとprismaフォルダの下にmigrationとさらにその中に日付とマイグレーション実行時に入力した名前のついたフォルダが作成されます。そのフォルダの中にはmigration.sqlファイルがありテーブル作成のSQLが保存されています。


-- CreateTable
CREATE TABLE "User" (
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "email" TEXT NOT NULL,
    "name" TEXT
);

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

データベースに作成されたテーブルへのアクセスはPrisma Studioから行うことができます。Prisma Studioを起動するために”npx prisma sudio”コマンドを実行します。


 % npx prisma studio
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Prisma Studio is up on http://localhost:5555
 

ブラウザが自動で起動しhttp://localhost:5555にアクセスが行われます。画面には設定したUserモデルが表示されます。

Prisma Studioの起動直後の画面
Prisma Studioの起動直後の画面

Userをクリックするとデータは入っていませんがUserテーブルの列名を確認することができます。

Userテーブルの列名の確認
Userテーブルの列名の確認

schema.prismaファイルの設定を行い、migrateコマンドを実行するだけでデータベースの作成からテーブルの作成まで行うことができました。

マイグレーションについては他にもさまざまな機能を持っているのでその一部を確認していきます。

マイグレーションのreset

開発環境であれば作成したテーブルをリセットすることができます。一度テーブルが削除され再作成されます。もしデータが保存されている場合にはなくなるので注意してください。


 % npx prisma migrate reset
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"

✔ Are you sure you want to reset your database? All data will be lost. … yes

Applying migration `20220517073917_create_user`

Database reset successful

The following migration(s) have been applied:

migrations/
  └─ 20220517073917_create_user/
    └─ migration.sql

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

マイグレーションのpushとpull

schema.prismaファイルのモデルを変更した場合(新たなフィールドを追加した削除したり)に”prisma db push”コマンドを実行するとマイグレーションファイルの作成なしにデータベースのテーブルに変更を加えることができます。


 % npx prisma db push
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"

🚀  Your database is now in sync with your schema. Done in 20ms

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

modelのフィールド(テーブルの列)を変更(削除したい場合)にすでにその列にデータが入っている場合は変更によってデータロスにつながるので警告が表示されます。警告を無視して実行すると列が削除されるのでデータは消えます。

prisma db pullコマンドを実行した場合は既存のデータベースの状態をschema.prismaファイルに反映されせることができます。


 % npx prisma db pull
Prisma schema loaded from prisma/schema.prisma
Environment variables loaded from .env
Datasource "db": SQLite database "dev.db" at "file:./dev.db"

✔ Introspected 1 model and wrote it into prisma/schema.prisma in 18ms
      
Run prisma generate to generate Prisma Client.
 

マイグレーションによるSeeding

Seedingファイルを利用することでマイグレーションの実行時にテーブルにデータを挿入することができます。

prismaフォルダにseed.tsファイルを作成し1件のユーザ情報をテーブルに追加する処理を記述します。


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

async function main() {
  const alice = await prisma.user.upsert({
    where: { email: 'alice@prisma.io' },
    update: {},
    create: {
      email: 'alice@prisma.io',
      name: 'Alice',
    },
  });

  console.log({ alice });
}

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

@prisma/clienctからPrismaClientをimportしています。importしたPrismaClientをインスタンス化してprisma.user.upsertで追加を行なっています。SQLではなくprisma.userが持つオブジェクトのupsertメソッドを利用しています。

upsertメソッドはwhereで設定した条件でデータが存在する場合はupdateを行い、存在しない場合createを行います。

マイグレーションの実行やresetを行なった時にseedingを自動で行ってくれますが手動で行いたい場合には、”prisma db seed”コマンドを利用することができます。


 % npx prisma db seed
Environment variables loaded from .env
Error: To configure seeding in your project you need to add a "prisma.seed" property in your package.json with the command to execute it:

1. Open the package.json of your project
2. Add the following example to it:
```
"prisma": {
  "seed": "ts-node prisma/seed.ts"
}
```
If you are using ESM (ECMAScript modules):
```
"prisma": {
  "seed": "node --loader ts-node/esm prisma/seed.ts"
}
```

3. Install the required dependencies by running:
npm i -D ts-node typescript @types/node

More information in our documentation:
https://pris.ly/d/seeding
 

しかし実行するとエラーが発生します。TypeScriptに関係するエラーが発生しており、メッセージの中にエラーの解消方法が表示されているのでその指示に従ってpackage.jsonファイルを更新します。


{
  "name": "nodejs-prisma",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "prisma": {
    "seed": "ts-node prisma/seed.ts" //追加
  },
//略
}
 

設定後再度”prisma db seed”コマンドを実行します。


 % npx prisma db seed
Environment variables loaded from .env
Running seed command `ts-node prisma/seed.ts` ...
{ alice: { id: 1, email: 'alice@prisma.io', name: 'Alice' } }

🌱  The seed command has been executed.
 

Prisma Studioを利用してSeedingで追加したデータを確認することができます。

Prisma StudioでSeedingしたデータの確認
Prisma StudioでSeedingしたデータの確認

“prisma migrate reset”を実行するとSeedingが実行されることも確認できます。


 % npx prisma migrate reset
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"

✔ Are you sure you want to reset your database? All data will be lost. … yes

Applying migration `20220517073917_create_user`

Database reset successful

The following migration(s) have been applied:

migrations/
  └─ 20220517073917_create_user/
    └─ migration.sql

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

Running seed command `ts-node prisma/seed.ts` ...
{ alice: { id: 1, email: 'alice@prisma.io', name: 'Alice' } }

🌱  The seed command has been executed.
 
–skip-seedのオプションをつけることでSeedingをスキップすることもできます。

Express.jsの設定

Express.jsの設定を行いPrismaを利用してデータベースからデータの取得ができるか確認を行っていきます。

Express.jsの起動

プロジェクトフォルダにindex.tsファイルを作成して以下のコードを記述します。Portを3000に設定しているのでhttp://localhost:3000に接続して”Hello World”が表示されるか確認を行います。


import express, { Request, Response } from 'express';

const app = express();
const port = 3000;

app.get('/', (req: Request, res: Response) => res.send('Hello World!'));

app.listen(port, () => console.log(`Example app listening on port ${port}!`));
 

ts-nodeコマンドを利用してExpressサーバの起動を行います。


 % npx ts-node index.ts
Example app listening on port 3000!
 

ブラウザを起動してhttp://localhost:3000にアクセスすると”Hello World”が表示されます。

ブラウザ上にHello Worldが表示
ブラウザ上にHello Worldが表示

index.tsの更新を検知してindex.tsの再読み込みを自動で行えるようにnodemonのインストールを行います。nodemonは必須ではありません。


 % npm install nodemon --save-dev
 

nodemonインストール後にpackage.jsonのscriptに以下を追加します。nodemonがtsファイルを認識してts-code index.tsを実行してくれます。


{
  "name": "nodejs-prisma",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon index.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  //略
}

今後は以下のコマンドを実行します。


 % npm run dev

> nodejs-prisma@1.0.0 dev
> nodemon index.ts

[nodemon] 2.0.16
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node index.ts`
Example app listening on port 3000!
 

ユーザ情報の取得

Expressサーバの起動が確認できたので、Prismを経由してユーザ情報の取得を行います。@prisma/clientからimportしたPrismaClientをインスタンス化したprismaを利用してprisma.user.findMany()でUserテーブルからすべての情報を取得することができます。


import express, { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';

const app = express();
const port = 3000;

const prisma = new PrismaClient();

app.get('/users', async (req: Request, res: Response) => {
  const users = await prisma.user.findMany();
  return res.json(users);
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

getメソッドの場合はブラウザからアクセスしてもjsonデータとして戻されるユーザ情報を確認することができます。Prismaを経由してSQLiteのデータベースからユーザ情報を取得することができました。

ユーザ情報の取得
ユーザ情報の取得

ユーザ情報の登録

Expressサーバからユーザ情報の作成ができるようにルーティングの追加を行います。ユーザ作成にはprisma.user.createメソッドを利用します。クライアントから送信されてくるJSONデータをrequest.bodyから取得するためにexpress.json()のミドルウェアの設定を行なっています。


import express, { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';

const app = express();
const port = 3000;

app.use(express.json());

const prisma = new PrismaClient();

app.get('/users', async (req: Request, res: Response) => {
  const users = await prisma.user.findMany();
  return res.json(users);
});

app.post('/users', async (req: Request, res: Response) => {
  const { name, email } = req.body;
  const user = await prisma.user.create({
    data: {
      name,
      email,
    },
  });
  return res.json(user);
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

/usersに対してPOSTリクエストを行うためにツールを利用します。PostmanやInsomniaなどのツールがありますが本文書ではVisual Studio CodeのExtensionsのREST Clientを利用します。利用する場合はREST Clientをインストールしてください。POSTリクエストが利用できるならばcurlを含めどのようなツールを利用しても構いません。

REST Clientを利用するためにプロジェクトフォルダにtest.httpを作成します。testは任意の名前をつけることができますが拡張子はhttpまたはrestにする必要があります。

test.httpファイルに以下を記述します。


POST http://localhost:3000/users
Content-Type: application/json

{
    "name": "John",
    "email": "john@example.com" 
}
 

上記のコードを記述すると上部に”Send Request”が自動で表示されるのでクリックするとPOSTリクエストが行われます。Expressサーバ側の設定でユーザの作成が完了すると作成したユーザ情報を戻すことになっているので作成されたユーザ情報を右側の画面から確認することができます。右側の画面は”Send Request”を実行すると自動で表示されます。

REST ClientによるPOSTリクエスト
REST ClientによるPOSTリクエスト

ユーザが作成できたか確認するためにREST Clientを利用してGETリクエストを送信します。複数のリクエストをtest.httpファイルに記述する際はリクエストの区切りとして###を記述してその下にリクエストを記述します。


###
GET http://localhost:3000/users
 

実行するとfindManyメソッドを実行しているためUserテーブルのすべての情報が取得できるため2名分のユーザ情報が配列で戻されます。


[
  {
    "id": 1,
    "email": "alice@prisma.io",
    "name": "Alice"
  },
  {
    "id": 2,
    "email": "john@example.com",
    "name": "John"
  }
]

ユーザの作成を行うことができましたが同じメールアドレスをPOSTリクエストで送信した場合の動作確認を行います。現在の設定ではエラーが発生してExpressサーバが停止します。

try, catchを利用してエラーの内容をjsonで戻せるように設定を行います。


app.post('/users', async (req: Request, res: Response) => {
  const { name, email } = req.body;
  try {
    const user = await prisma.user.create({
      data: {
        name,
        email,
      },
    });
    return res.json(user);
  } catch (e) {
    return res.status(400).json(e);
  }
});

作成済みのemailアドレスを設定してPOSTリクエストを行うと下記のエラーメッセージが戻されます。


{
  "code": "P2002",
  "clientVersion": "3.14.0",
  "meta": {
    "target": [
      "email"
    ]
  }
}

emailに関わることだとわかりますがなぜエラーが発生したのかわからないので公式ドキュメントのエラーコードを確認します。

P2002のコードの内容を確認すると””Unique constraint failed on the {constraint}””であることがわかります。emailの列はschema.prismaで@uniqueが設定されているためその制限に引っかかっていることが確認できます。

参考ですが公式ドキュメントのエラーハンドリングでは以下のような例が記載されていました。Prismaから戻される内容とエラーコードによって処理を変えることができます。


import express, { Request, Response } from 'express';
import { PrismaClient, Prisma } from '@prisma/client';

//略

app.post('/users', async (req: Request, res: Response) => {
  const { name, email } = req.body;
  try {
    const user = await prisma.user.create({
      data: {
        name,
        email,
      },
    });
    return res.json(user);
  } catch (e) {
    if (e instanceof Prisma.PrismaClientKnownRequestError) {
      if (e.code === 'P2002') {
        console.log(
          'There is a unique constraint violation, a new user cannot be created with this email'
        );
      }
    }
    return res.status(400).json(e);
  }
});

//略

ユーザ情報の更新

ユーザの作成方法がわかったのでユーザ情報を更新する方法を確認します。ユーザの更新ではidを利用してユーザ情報を取得してnameの更新を行なっています。idの値は変わるので:idを設定しています。idの値はreq.paramsのidから取得できます。値はURLのためStringになるのでNumber関数により数値に変更を行う必要があります。


app.put('/users/:id', async (req: Request, res: Response) => {
  const id = Number(req.params.id);
  const { name } = req.body;
  try {
    const user = await prisma.user.update({
      where: {
        id,
      },
      data: {
        name,
      },
    });
    return res.json(user);
  } catch (e) {
    return res.status(400).json(e);
  }
});

REST Client側のPUTリエクストではテーブルに存在するユーザのidを設定して更新を行うnameの値を送信します。


###
PUT http://localhost:3000/users/2
Content-Type: application/json

{
  "name":"Kevin"
}
 

実行すると更新したユーザ情報が戻されます。


{
  "id": 2,
  "email": "john@example.com",
  "name": "Kevin"
}

もしidに存在しないユーザのidを設定した場合には下記のようなエラー情報が戻されます。


{
  "code": "P2025",
  "clientVersion": "3.14.0",
  "meta": {
    "cause": "Record to update not found."
  }
}

“P2025″のエラーコードの内容を確認すると”An operation failed because it depends on one or more records that were required but not found. {cause}”であることがわかります。

ユーザの削除方法

ユーザを削除する方法を確認します。idを利用して削除するユーザを指定します。削除にはprisma.user.deleteメソッドを利用します。


app.delete('/users/:id', async (req: Request, res: Response) => {
  const id = Number(req.params.id);

  try {
    const user = await prisma.user.delete({
      where: {
        id,
      },
    });
    return res.json(user);
  } catch (e) {
    return res.status(400).json(e);
  }
});

REST ClientのDELETEリエクストではテーブルに存在するユーザのidを設定してリクエストを送信します。


###
DELETE http://localhost:3000/users/2

削除が完了すると削除したユーザの情報が戻されます。


{
  "id": 2,
  "email": "john@example.com",
  "name": "Kevin"
}

削除後に/usersにGETリクエストを送信するとユーザが削除されているので1名分のデータのみ戻されます。

存在しないidを指定してDELETEリクエストを送信するとPUTリクエストと同様にエラーコード”P2025″が戻されます。

ユーザの個別情報の取得

ユーザの個別情報の取得方法を確認します。idを利用して取得するユーザを指定します。idはUserテーブル内で一意な値なのでにはprisma.user.findUniqueメソッドを利用します。


app.get('/users/:id', async (req: Request, res: Response) => {
  const id = Number(req.params.id);
  const user = await prisma.user.findUnique({
    where: {
      id,
    },
  });
  return res.json(user);
});

GETリクエストのURLにUSERテーブルに存在するidを指定してリクエストを送信します。


###
GET http://localhost:3000/users/1

実行するとidで指定したユーザの情報が戻されます。findUniqueの場合はもし存在しないidを指定した場合にはnullが戻されます。nullではなくエラーをthrowしたい場合にはPrismaClient()の引数にrejectOnNotFoundを設定します。


const prisma = new PrismaClient({ rejectOnNotFound: true });
 

エラーをcatchできるようにtry, catchを追加します。


app.get('/users/:id', async (req: Request, res: Response) => {
  const id = Number(req.params.id);
  try {
    const user = await prisma.user.findUnique({
      where: {
        id,
      },
    });
    return res.json(user);
  } catch (e) {
    return res.status(400).json(e);
  }
});

設定後はnullではなくNot Found Errorが戻されます。


{
  "name": "NotFoundError",
  "clientVersion": "3.14.0"
}
nullが戻されるのはfindUniqueとfindFirstメソッドです。

upsert, createManyメソッド

create, update, delete, findUnique, findManyの他にfindFirst, upsert, createMany, updateMany, deleteManyなどのメソッドがあります。

findManyを実行するとUserテーブルのデータをすべて取得しますがfindFirstメソッドを実行するとUserテーブルの中から最近追加されたユーザ情報のみ戻されます。findFirstにはwhereを使って条件をつけることも可能です。


const user = await prisma.user.findFirst({
  where: { name: 'Alice' },
})

upsertについては本文書のseedingの時にすでに利用しています。下記の場合であればidで指定したユーザが存在する場合にはupdateが実行され、存在しない場合にはcreateが実行されます。


const user = await prisma.user.upsert({
  where: { id: 1 },
  create: {
    email: 'alice@prisma.io',
    name: 'Alice',
  },
  update: {
    email: 'alice@prisma.io',
    name: 'AliceB',
  },
})

一度に複数の情報を登録したい場合にはcreateManyメソッドを利用することができます。


const users = await prisma.user.createMany({
  data: [
    { name: 'Sonali', email: 'sonali@prisma.io' },
    { name: 'Alex', email: 'alex@prisma.io' },
  ],
})

リレーションの設定

ここまでの動作確認はUserモデル単独の動作確認でした。通常データベースを利用する場合はモデル間でリレーションシップを持っています。新たにPostモデルを追加してUserモデルとPostモデルにリレーションを持たせて動作確認を行います。

UserモデルとPostモデルはone-to-manyリレーションシップを持っているため図にすると下記のように表すことができます。PKはプライマリキー(Primary Key)をの略で、FKは外部キー(Foreign Key)の略です。PostのauthorIdの中にUserのidが入ることになります。一人のユーザが複数のPostを作成することができますが1つのPostが複数のUserに紐づくことはありません。

UserモデルとPostモデルのリレーションシップ図
UserモデルとPostモデルのリレーションシップ図

schema.prismaファイルにPostモデルを追加します。Prisimaでは外部キーとプライマリキーの関係を@relationを利用して設定することができます。fieldsに設定するauthorIdが外部キー、referencesのidがUserテーブルのプライマリキーです。Userモデル側にもフィールドPostsを追加する必要があります。


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

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

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

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  content String?
  author   User @relation(fields: [authorId], references: [id])
  authorId Int
}
 

モデルの設定が完了したらのでマイグレーションを実行します。–nameオプションをつけることでマイグレーションの名前をコマンド実行時に設定することができます。


 % npx prisma migrate dev --name create_post
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"

Applying migration `20220518035219_create_post`

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

migrations/
  └─ 20220518035219_create_post/
    └─ migration.sql

Your database is now in sync with your schema.

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

Postデータの作成

Postデータを作成するためにindex.tsファイルに新たにルーティングの追加を行います。データの作成の処理についてはuserの作成の時と同じです。


app.post('/posts', async (req: Request, res: Response) => {
  const { title, content, authorId } = req.body;
  try {
    const post = await prisma.post.create({
      data: {
        title,
        content,
        authorId,
      },
    });
    return res.json(post);
  } catch (e) {
    return res.status(400).json(e);
  }
});

ルーティングの追加が完了したのでREST Clientを利用してPostデータを送信します。authorIdにはUserテーブルに存在するユーザのidを設定します。


###
POST http://localhost:3000/posts
Content-Type: application/json

{
    "title": "Prismaの基本",
    "content": "リレーションショッピンの設定を確認中",
    "authorId": 1 
}
 

postリクエストを送信するとPostデータが作成されるので作成されたデータが戻されます。

さらにもう一つデータを作成しておきます。


###
POST http://localhost:3000/posts
Content-Type: application/json

{
    "title": "Prismaの基本2",
    "content": "Postテーブルへの新規データ作成を確認",
    "authorId": 1 
}
 

もし存在しないユーザのidを指定した場合にはエラーコード”P2003(“Foreign key constraint failed on the field: {field_name}“)”が戻されます。


{
  "code": "P2003",
  "clientVersion": "3.14.0",
  "meta": {
    "field_name": "foreign key"
  }
}

2つのPostデータが作成されているかどうかはPrisma Studioで確認することができます。リレーションのauthor列も確認できます。

Prisma StudioによりPostテーブルの確認
Prisma StudioによりPostテーブルの確認

TabliePlusのような他のデータベース管理GUIソフトではauthor列が表示されることはありません。

TablePlusによるPostテーブルの確認
TablePlusによるPostテーブルの確認

Prisma Studioを利用してUserテーブルも確認しておきましょう。Posts列が表示されリレーションを持つPostデータの件数まで表示されます。

Prisma StudioからのUserテーブルの確認
Prisma StudioからのUserテーブルの確認

Postデータの取得

UserモデルとPostモデルはリレーションを持っているのでuser情報を取得することでuserに紐づくpostデータが取得できるか確認します。

REST ClientからGETリクエストを送信します。


###
GET http://localhost:3000/users
 

User情報が戻されるますがその中にはpostの情報はありません。


[
  {
    "id": 1,
    "email": "alice@prisma.io",
    "name": "Alice"
  }
]
 

post情報を取得するためにはfindManyの引数に設定を行う必要があります。findManyの引数にincludeを追加します。includeに設定したPostsはschema.prismaファイルでUserモデルに設定したリレーションのPostsです。


app.get('/users', async (req: Request, res: Response) => {
  const users = await prisma.user.findMany({
    include: { Posts: true },
  });
  return res.json(users);
});

再度/usersにGETリクエストを送信するとユーザ情報とpost情報を取得することができます。


[
  {
    "id": 1,
    "email": "alice@prisma.io",
    "name": "Alice",
    "Posts": [
      {
        "id": 1,
        "title": "Prismaの基本",
        "content": "リレーションショッピンの設定を確認中",
        "authorId": 1
      },
      {
        "id": 2,
        "title": "Prismaの基本2",
        "content": "Postテーブルへの新規データ作成を確認",
        "authorId": 1
      }
    ]
  }
]
 

リレーションを持つモデルでもSQLで実行するようなjoinなどを気にすることなく簡単に取得できることがわかります。

Supabase(PostgreSQL)への切り替え

SQLiteデータベースで基礎的な動作確認を行なったので次はSupabaseを利用してPostgreSQLでの動作確認を行います。

SupabaseはFirebaseのAlternative(代替)として利用できるオープンソースのクラウドサービスです。データベースはPostgreSQLを利用しています。プランにフリーが提供されています。Supabaseについては下記の記事でも公開しています。

GitHubのアカウントでサインアップすることでできサインアップ後にプロジェクトを作成します。ここではプロジェクトの作成後から確認していきます。

プロジェクトの作成
プロジェクトの作成

プロジェクトの作成ではプロジェクトの名前とデータベースのパスワード、Regionの設定を行います。データベースのパスワードはPrismaから接続する際に必要になります。プロジェクトは任意の名前をつけてください。Regionには東京があるので東京を選択します。

プロジェクトの作成
プロジェクトの作成

データベースへの接続はschema.prismaファイルのdatasourceで行います。接続情報はSupabaseの管理画面から確認することができます。

左側のメニューの一番下にあるSettingを選択して、表示されるDatabaseを選択します。画面を下までスクロールするとConnection Stringの項目があるので”Node.js”のタブを開いてそこに表示されている接続用の文字列をコピーしてください。

Node.jsからの接続情報
Node.jsからの接続情報

コピーしたらプロジェクトフォルダにある.envファイルを開いてDATABASE_URLにペーストしてください。[YOUR_PASSWORD]にはSupabaseのプロジェクトの作成時に入力したパスワードを入れてください。


DATABASE_URL="postgresql://postgres:[YOUR_PASSWOD}@db.yjvfpxhcwbqtpiwhkIzm.supabase.co:5432/postgres"
 

.envファイルの設定が完了したら、schema.prismaファイルを開いてproviderの値をsqliteからpostgresに変更してください。


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

SQLiteの設定は必要ないのでmigrationsフォルダ、db.devファイルを削除してください。ここまでSupabaseを利用するための設定は完了したのでmigrateコマンドを実行します。


 % npx prisma migrate dev --name init  
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "postgres", schema "public" at "db.yjvfpxhcwbqtpiwhkIzm.supabase.co:5432"

Applying migration `20220518073231_init`

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

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

Your database is now in sync with your schema.

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

マイグレーションファイルも作成され、Prisma ClientもPostgreSQL用のものが作成されます。Expressサーバも再起動を行います。

REST Clientを利用してユーザ作成用のPOSTリクエストを送信するとユーザの作成を行うことができます。

追加した内容はSupabaseの管理画面のテーブルからも確認することができます。

Expressサーバを経由して作成したデータの確認
Expressサーバを経由して作成したデータの確認

Prisma Studioを利用してもデータを確認することができます。

schema.prismaファイルのdatasourceの設定を変えるだけでデータベースの切り替えも簡単に行うことができることがわかりました。

まとめ

本文書ではPrismaを利用する上で必要となる基本の操作方法について説明を行いました。実際にアプリケーションを構築するとより複雑なクエリーやモデル間のリレーションを持つことになります。公式ドキュメントにもリエリーに関するいろいろなオプションの設定方法が記述されているので参考にしてみてください。