こんにちは、なかにしです。
今回はNode.jsのORM、Prismaをいじってみようと思います!
とりあえずCRUDだけ押さえてみます。
Prismaとは
オープンソースの ORMです。
公式は こちら
謳い文句は 次世代のNode.jsおよびTypeScript ORM 。
TSを使用して記述することができ、型安全にDB操作ができる、といった特徴があります。
対応DB一覧はこちら です。
メジャーなオープンソースDBはすべて押さえているイメージですね。
ORMの導入により、各DBの方言の違いの吸収やメンテナンスの容易さが向上し、
よりスピーディーでハイクオリティな開発ができるようになります。
事前準備
アプリケーションとDBを用意します。
今回の環境は Windows(Windows 11 Home)のWSL2です。
アプリは React×Express で作成します。
(Nodeインストール済の前提)
フロント側
ViteでReactテンプレートを使用します。
npx create vite
バック側
Expressを使用します。
package.jsonを作成して、
npm init
必要なライブラリをインストールします。
npm install typescript ts-node @types/node @types/express --save-dev
package.jsonに、ビルドとサーバー起動、そしてシーダー用のスクリプトを追加します。
"scripts": {
"build": "tsc -p tsconfig.prod.json",
"start": "node dist/index.js",
"seed": "ts-node prisma/seed.ts"
},
TS使用の為、tsconfig.jsonを用意します。
(開発用と本番用で2種類)
{
"compilerOptions": {
"types": ["node"],
"target": "es5",
"module": "commonjs",
"rootDir": ".",
"outDir": "./dist",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*", "prisma/seed.ts"]
}
{
"compilerOptions": {
"types": ["node"],
"target": "es5",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["prisma/seed.ts"]
}
index.tsを用意します。
import express, { Request, Response } from 'express';
const app = express();
const port = 3001;
app.use(express.json());
app.get('/', (req: Request, res: Response) => {
res.send('Hello~');
})
app.listen(port, () => {
console.log(`Server is Running: port ${port}`)
})
DB側
DBはDockerでPostgresSQLを立てます。
FROM postgres:latest
ENV POSTGRES_USER=nakanishi-db
ENV POSTGRES_PASSWORD=Nakanishi
ENV POSTGRES_DB=nakanishi-db
version: "3"
services:
db:
build: .
ports:
- "5432:5432"
volumes:
- /mnt/wsl/ubuntu/home/prisma-practice:/var/lib/postgresql/data
dockerでコンテナを立て、準備完了です。
docker compose up -d
Prismaのインストールと初期設定
バック側でPrismaをインストールします。
npm install @prisma/client
初期設定をします。
npx prisma init
Prismaディレクトリと.envが自動生成されました。
DB接続
PostgresSQLと繋いでいきます。
prisma/schema.prismaにDB接続情報を書きます。
PostgresSQLであれば、テンプレートのままでOKです。
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
続いて、.envの環境変数を修正していきます。
文法としては以下のように書きます。
DATABASE_URL="postgresql://ユーザー名:パスワード@ホスト:ポート/データベース名"
上記の文法に則り、今回は以下にしました。
DATABASE_URL="postgresql://nakanishi-db:Nakanishi@127.0.0.1:5432/nakanishi-db"
Model定義
モデルを定義していきます。
まずは定石、Userモデルを定義します。
定義は「schema.prisma」に追記していきます。
idはInt型で、オートインクリメント & プライマリーキー。
nameとemailはString型で、emailのみユニークにします。
// Userモデルを定義
model User{
id Int @id @default(autoincrement())
name String
email String @unique
}
直感的でいいですね。
Prismaの拡張機能を入れると予測変換も出してくれるので、サクサク書けます。
Migration
マイグレーションします。
npx prisma migrate dev --name init
上記コマンドで、「マイグレーションファイルの作成」と「マイグレーションファイルの実行」をしてくれます。
▽ マイグレーションファイルが追加されました。
▽ Userテーブルが作成されました。
中身はもちろん、空です。
Seeder
まだテーブルにデータが入っていないので、Seederファイルを作成します。
今回はprisma/seed.tsとして作成しました。
// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const alice = await prisma.user.create({
data: {
name: 'Alice',
email: 'alice@example.com',
},
});
console.log({ alice });
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
実行はpackage.jsonに事前に書いておいた、以下コマンドで実行します。
npm run seed
▽ ちゃんと初期データが入りました。
データ取得
Express側に、データを返すエンドポイントを追記します。
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
app.get('/users', async (req: Request, res: Response) => {
const users = await prisma.user.findMany();
res.json(users);
})
サーバーを立てて、Curlを叩いてみます。
npm run start // Express側のサーバーを立てる
curl http://localhost:3001/users // Curlを飛ばす
▽ ちゃんとデータが返ってきています。
アプリと結合
いよいよ React側と結合します。
テーブルとボタンを作って、ボタン押下でCRUDを行います。
データ取得
▽ Express(Node)側
import { PrismaClient } from '@prisma/client';
import cors from 'cors';
import express, { Request, Response } from 'express';
const app = express();
const port = 3001;
app.use(cors());
app.use(express.json());
const prisma = new PrismaClient();
// ユーザー取得
app.get('/users', async (req: Request, res: Response) => {
const users = await prisma.user.findMany();
res.json(users);
})
app.listen(port, () => {
console.log(`Server is Running: port ${port}`)
})
▽ React側
import axios from 'axios';
import { useState } from 'react';
import './App.css';
type User = {
id: number,
name: string,
email: string
}
function App() {
const [users, setUsers] = useState<User[]>([]);
/** Userデータを取得 */
const readUserData = () => {
axios.get("http://localhost:3001/users")
.then(res => setUsers(res.data))
.catch(e => console.error(e))
}
return (
<div>
<h1>Sample Page</h1>
<button onClick={readUserData} >データ取得</button>
<div>
<h2>データ表示</h2>
<table border={1}>
<thead>
<tr>
<th>ID</th>
<th>名前</th>
<th>メール</th>
</tr>
</thead>
<tbody>
{users && users.map((user) => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
export default App
ユーザー作成
▽ Express(Node)側
import { PrismaClient } from '@prisma/client';
import cors from 'cors';
import express, { Request, Response } from 'express';
const app = express();
const port = 3001;
app.use(cors());
app.use(express.json());
const prisma = new PrismaClient();
// ユーザー作成
app.post('/users', async (req: Request, res: Response) => {
const { name, email } = req.body;
const data = {
name: name,
email: email
}
try {
const newUser = await prisma.user.create({
data: data
})
res.json(newUser);
} catch (error: any) {
res.status(400).send(error.message);
}
})
app.listen(port, () => {
console.log(`Server is Running: port ${port}`)
})
▽ React側
import axios from 'axios';
import './App.css';
function App() {
// 登録するデータ
const data = {
name: "Tanaka",
email: "sample@gmail.com",
}
/** Userデータを作成 */
const createUserData = () => {
axios.post("http://localhost:3001/users", data)
.then(res => alert("データ作成完了しました!"))
.catch(e => console.error(e))
}
return (
<div>
<h1>Sample Page</h1>
<button onClick={createUserData} >データ作成</button>
</div>
)
}
export default App
ユーザー更新
▽ Express(Node)側
import { PrismaClient } from '@prisma/client';
import cors from 'cors';
import express, { Request, Response } from 'express';
const app = express();
const port = 3001;
app.use(cors());
app.use(express.json());
const prisma = new PrismaClient();
// ユーザー更新
app.put('/users/:id', async (req: Request, res: Response) => {
const { id } = req.params;
const { name, email } = req.body;
const data = {
name: name,
email: email
}
try {
const updateUser = await prisma.user.update({
where: { id: parseInt(id) },
data: data
})
res.json(updateUser);
} catch (error: any) {
res.status(400).send(error.message);
}
})
app.listen(port, () => {
console.log(`Server is Running: port ${port}`)
})
▽ React側
import axios from 'axios';
import './App.css';
function App() {
// 更新するユーザー
const userNum = 3;
/** Userデータを更新 */
const updateUserData = () => {
axios.put(`http://localhost:3001/users/${userNum}`, data)
.then(res => alert("データ更新完了しました!"))
.catch(e => console.error(e))
}
return (
<div>
<h1>Sample Page</h1>
<button onClick={updateUserData} >データ更新</button>
</div>
)
}
export default App
ユーザー削除
▽ Express(Node)側
import { PrismaClient } from '@prisma/client';
import cors from 'cors';
import express, { Request, Response } from 'express';
const app = express();
const port = 3001;
app.use(cors());
app.use(express.json());
const prisma = new PrismaClient();
// ユーザー削除
app.delete('/users/:id', async (req: Request, res: Response) => {
const { id } = req.params;
try {
const deleteUser = await prisma.user.delete({
where: { id: parseInt(id) }
})
res.json(deleteUser);
} catch (error: any) {
res.status(400).send(error.message);
}
})
app.listen(port, () => {
console.log(`Server is Running: port ${port}`)
})
▽ React側
import axios from 'axios';
import './App.css';
function App() {
/** Userデータを削除 */
const deleteUserData = () => {
axios.delete(`http://localhost:3001/users/${userNum}`)
.then(res => alert("データ削除完了しました!"))
.catch(e => console.error(e))
}
return (
<div>
<h1>Sample Page</h1>
<button onClick={deleteUserData} >データ削除</button>
</div>
)
}
export default App
GUIツール起動
Prismaには、dbの確認や更新ができるGUIツールが付属しています。
以下コマンドで起動できます。
npx prisma studio // ポートは5555で起動
シンプルでいいですね。最近っぽい。
▽ データの挿入や削除もできます。
さいごに
今回は Prisma をいじってみました!
直感的に書ける & TS対応しているという点が人気の理由なのかな?という感想です。
個人的には DB操作ができるGUIが付属しているのが好きです。
わざわざ別ツールでアクセスしたり、コンテナの中に入らなくてもいいのはとても楽です。
ちなみに、今回は簡略化の為にトランザクションを書いていないですが、
業務使用の際は必ず書くようにしてくださいね。
トランザクションの参考は こちら
今回はここまで!
Enjoy Hacking!