Prismaに入門して、MySQLと接続してCRUD処理を実装してみた
概要
TypeScriptで実装されたORMであるPrismaに入門するために公式チュートリアルやりつつ、そこからCRUD処理を書きました。
公式ではPostgreSQLを使っていますが、私はMySQLの方が慣れているという理由でMySQLを使います。また、公式ではnpm/npxを使っていますが、こちらも本記事ではyarnを使います。
下準備(MySQL側の設定)
※MySQL自体のインストールや環境構築については割愛します。
PrismaからアクセスするためのMySQLユーザーを作成します。
mysql -uroot -p
mysql> create user prisma_tester@localhost identified by 'mysql';
mysql> create database prisma_crud_sample;
mysql> grant all on *.* to prisma_tester@localhost;
ここではユーザー名prisma_tester
、パスワードmysql
で作成して、prisma_crud_sample
というDBを作成しています。最後にprisma_tester
ユーザーに対して権限を付与しています。PrismaはシャドウDBというものを自動で作るようでして、そのためにPrismaから接続するユーザーにはDBを新規作成する権限を与える必要があるようです。
まずは公式チュートリアルそのままやってみる
まず最初に公式チュートリアルをそのまま動かして、DBを操作できることを確かめていきます。
適当にディレクトリを作ります
mkdir prisma_test
cd prisma_test
下記の通りpackage.json
を書きます。
{
"dependencies": {
"@prisma/client": "4.8.0",
"@types/node": "^18.11.18",
"prisma": "^4.8.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
}
必要なパッケージをインストールします。
yarn install
下記のコマンドを実行するとprisma
ディレクトリが作成され、その中にPrismaのDBスキームファイルであるschema.prisma
が作成されます
yarn prisma init
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 = "postgresql"
url = env("DATABASE_URL")
}
これを下記のように変更します。データベースにMySQLを用いるのでprovider = "mysql"
に変更し、後は公式チュートリアルと同じようにUser
、Post
、Profile
の3つのテーブルを定義しています。
// 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")
}
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)
authorId Int
author User @relation(fields: [authorId], references: [id])
@@index([authorId], map: "Post_authorId_fkey")
}
model Profile {
id Int @id @default(autoincrement())
bio String?
userId Int @unique
user User @relation(fields: [userId], references: [id])
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
profile Profile?
}
上記のurl = env("DATABASE_URL")
という部分ですが、ここで参照しているDATABASE_URL
という変数を.env
ファイルにて定義しておく必要があります。[DBの種類]://[ユーザー名]:[パスワード]@[ホスト名]:[ポート番号]/[DB名]
という形式で指定します。
DATABASE_URL="mysql://prisma_tester:mysql@localhost:3306/prisma_crud_sample"
ここまできたら下記のコマンドでprisma clientを生成します。
yarn prisma generate
そして、下記のコマンドでschema.prisma
の内容をDBに書き写します(マイグレーション)。
yarn prisma migrate dev --name init
ここでMySQLを確認すると、下記のようにschema.prisma
の内容が反映されていることが確認できます。
mysql -uprisma_tester -p
mysql> use prisma_crud_sample;
mysql> show tables;
+------------------------------+
| Tables_in_prisma_crud_sample |
+------------------------------+
| _prisma_migrations |
| Post |
| Profile |
| User |
+------------------------------+
4 rows in set (0.01 sec)
確認できましたら、チュートリアル通りにindex.ts
を書いていきます。
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// const prisma = new PrismaClient({log: ['query']})
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 })
}
main()
.catch((e) => {
throw e
})
.finally(async () => {
await prisma.$disconnect()
})
下記のコマンドで、index.ts
に記述したDB操作を実行します。
yarn ts-node index.ts
エラーが出ずに実行が完了したら成功です。MySQLから確認すると、上記のコードで追加したAliceのデータが格納されていることが分かります。
mysql> select * from User;
+----+-----------------+-------+
| id | email | name |
+----+-----------------+-------+
| 1 | alice@prisma.io | Alice |
+----+-----------------+-------+
1 row in set (0.00 sec)
CRUD処理を実装
次に、上記のサンプルを参考にCRUD処理を実装していきます。下記の通り、crud.ts
というファイルを作成します。
import { PrismaClient, Prisma } from '@prisma/client'
const prisma = new PrismaClient()
// *******************************************
// CRUD functions
// *******************************************
// [A]ここにCRUD関数を書く
// *******************************************
// sample data
// *******************************************
const user01: Prisma.UserCreateInput = {
name: 'Alice',
email: 'alice@prisma.io',
posts: {
create: { title: 'Hello World' },
},
profile: {
create: {bio: 'I like turtles' },
},
}
const user02: Prisma.UserCreateInput = {
name: 'Viola',
email: 'viola@prisma.io',
posts: {
create: { title: 'Hello World!' },
},
profile: {
create: { bio: 'I like rabbits' },
},
}
// *******************************************
// execute CRUD
// *******************************************
const main = async () => {
// [B]ここにCRUD関数の呼び出し処理を書く
}
main()
Create
最初にデータの登録処理を実装します。crud.ts
の[A]の箇所に下記のコードを書きます。
const create = async (rec: Prisma.UserCreateInput) => {
await prisma.user.create({ data: rec })
}
そして、[B]の箇所に下記のコードを書きます。
console.log('=== Create ===')
await create(user01)
await create(user02)
これで、サンプルデータとして定義したAliceとViolaのデータがMySQLに書き込まれます。
Read
次にデータの読み込み処理を実装します。crud.ts
の[A]の箇所に下記のコードを書き加えます。
const read = async (cond?: object): Promise<object[]> => {
if (cond) { // 検索条件あり
const selectedUsers = await prisma.user.findMany({
where: cond,
include: {
posts: true,
profile: true,
},
})
return selectedUsers
} else { // 検索条件なし
const allUsers = await prisma.user.findMany({
include: {
posts: true,
profile: true,
},
})
return allUsers
}
}
オプション引数cond
のところに条件が指定されていればその条件にあったデータを検索して返し、指定されていなければ全件取得して返します。
そして、[B]の箇所に下記のコードを書き加えます。
console.log('=== Read ===')
let recListAll = await read()
console.dir(recListAll, { depth: null }) // -> AliceとViolaが表示される
console.log('=== Read with condition ===')
const recList: Array<any> = await read({email: 'viola@prisma.io'}) // any型を指定しておかないとrecList[0].idにアクセスできない
console.dir(recList, { depth: null }) // -> 条件を満たしているViolaのみが表示される
この例では、条件を指定せずに全件取得した場合と、メールアドレスで絞り込みをした場合の2通りの処理を書いています。
Update
続いてデータの更新処理を実装します。crud.ts
の[A]の箇所に下記のコードを書き加えます。
const update = async (id: number, data: object) => {
const updateUser = await prisma.user.update({
where: {
id: id,
},
data: data
})
console.dir(updateUser, { depth: null })
}
id
を指定して、該当するIDを持つデータを、data
で渡した内容で書き換えます。data
の中身は書き換えるフィールドだけを含んでいればよく、例えば{name: 'hoge'}
のような形のオブジェクトを渡せば名前だけが更新されます。
そして、[B]の箇所に下記のコードを書き加えます。
console.log('=== Update ===')
const data = {
name: 'Viola the Magnificent',
}
const selectedId = recList[0].id
await update(selectedId, data)
recListAll = await read()
console.dir(recListAll, { depth: null }) // -> Violaの名前が変更されている
recList
は先述のReadの検索条件付きの処理の際に取得したもので、selectedId
にViolaのIDを代入しています。そして、そのIDを指定してupdate
関数を使ってViolaの名前を書き換えるという処理になっています。
Delete
最後にデータの削除処理を実装します。crud.ts
の[A]の箇所に下記のコードを書き加えます。
const delete_ = async (id: number) => {
const deletePosts = prisma.post.deleteMany({
where: {
authorId: id
},
})
const deleteProfiles = prisma.profile.deleteMany({
where: {
userId: id
},
})
const deleteUser = prisma.user.delete({
where: {
id: id
},
})
await prisma.$transaction([deletePosts, deleteProfiles, deleteUser])
}
User
データにはPost
データとProfile
データがリレーションで紐づいていますが、prismaではこうしたリレーションを持つデータについては、User
を削除する前に、そのデータに紐づくPost
及びProfile
を削除してからでないとエラーになります。上記の例ではまずdelateMany
で該当するユーザーの投稿とプロフィールを全て削除してから、最後にユーザーのデータを削除しています。$transaction
メソッドは、リストで渡された処理を順番にトランザクションとして実行します。(この場合ではPost
の削除、Profile
の削除、User
の削除という順番です。)
また、delete
はtypescriptやjavascriptでは予約語なので、関数名はdelete_
としています。
そして、[B]の箇所に下記のコードを書き加えます。
console.log('=== Delete ===')
await delete_(selectedId)
recListAll = await read()
console.dir(recListAll, { depth: null }) // -> Violaが削除され、Aliceのみが表示される
selectedId
は先述のViolaのIDですので、この例ではViolaのデータが削除されます。
CRUD全体のコード
ここまでの処理を書き込んでいくと、crud.ts
全体は下記のようになります。
import { PrismaClient, Prisma } from '@prisma/client'
const prisma = new PrismaClient()
// *******************************************
// CRUD functions
// *******************************************
const create = async (rec: Prisma.UserCreateInput) => {
await prisma.user.create({ data: rec })
}
const read = async (cond?: object): Promise<object[]> => {
if (cond) { // 検索条件あり
const selectedUsers = await prisma.user.findMany({
where: cond,
include: {
posts: true,
profile: true,
},
})
return selectedUsers
} else { // 検索条件なし
const allUsers = await prisma.user.findMany({
include: {
posts: true,
profile: true,
},
})
return allUsers
}
}
const update = async (id: number, data: object) => {
const updateUser = await prisma.user.update({
where: {
id: id,
},
data: data
})
console.dir(updateUser, { depth: null })
}
const delete_ = async (id: number) => {
const deletePosts = prisma.post.deleteMany({
where: {
authorId: id
},
})
const deleteProfiles = prisma.profile.deleteMany({
where: {
userId: id
},
})
const deleteUser = prisma.user.delete({
where: {
id: id
},
})
await prisma.$transaction([deletePosts, deleteProfiles, deleteUser])
}
const deleteAll = async () => {
await prisma.post.deleteMany({})
await prisma.profile.deleteMany({})
await prisma.user.deleteMany({})
}
// *******************************************
// sample data
// *******************************************
const user01: Prisma.UserCreateInput = {
name: 'Alice',
email: 'alice@prisma.io',
posts: {
create: { title: 'Hello World' },
},
profile: {
create: {bio: 'I like turtles' },
},
}
const user02: Prisma.UserCreateInput = {
name: 'Viola',
email: 'viola@prisma.io',
posts: {
create: { title: 'Hello World!' },
},
profile: {
create: { bio: 'I like rabbits' },
},
}
// *******************************************
// execute CRUD
// *******************************************
const main = async () => {
console.log('=== Create & Read ===')
await create(user01)
await create(user02)
let recListAll = await read()
console.dir(recListAll, { depth: null }) // -> AliceとViolaが表示される
console.log('=== Read with condition ===')
const recList: Array<any> = await read({email: 'viola@prisma.io'}) // any型を指定しておかないとrecList[0].idにアクセスできない
const selectedId = recList[0].id
console.dir(recList, { depth: null }) // -> 条件を満たしているViolaのみが表示される
console.log('=== Update ===')
const data = {
name: 'Viola the Magnificent',
}
await update(selectedId, data)
recListAll = await read()
console.dir(recListAll, { depth: null }) // -> Violaの名前が変更されている
console.log('=== Delete ===')
await delete_(selectedId)
recListAll = await read()
console.dir(recListAll, { depth: null }) // -> Violaが削除され、Aliceのみが表示される
// clear tables
await deleteAll()
}
main()
deleteAll
関数はデータベースにあるUser
、Post
、Profile
のデータを全て削除する関数です。crud.ts
を書き換えながら繰り返し実行してPrismaの動作を理解する目的で付け加えました。
下記のコマンドで、作成したCRUD処理を実行します。
yarn ts-node crud.ts
まとめ
PrismaとMySQLとの接続、公式チュートリアルの実装、そして基本的なCRUD処理の書き方を紹介しました。Prismaを使ってみての感想ですが、直感的に書けてコーディングしていてとても気持ちよく感じました。もう少し使ってみて自分の技術スタックに加えていきたいと思います。
Discussion