Prismaをつかってみる

NestJSやら他のフレームワークとの組み合わせや、migrationを前提にした資料ばかりなので、既存のDBに対して使うケースを前提にプレーンな環境で構築してみた。

# 目標
* Prismaを使ってみる
* Prismaで既存のDBに対して操作する
* PrismaでSQL文を投げる

# 前提
* 動作を確認するためにExpressを使う
* [typedSql](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/typedsql)を使う

# 準備

適当な場所に新規ディレクトリを作成して、プロジェクトディレクトリとし、中で下記コマンドを打つ
```shell
npm init -y
npm i -S express
npm i -D typescript ts-node prisma @types/node @types/express
npx tsc --init
```

# prisma初期設定
```shell
npx prisma init
```

上記コマンド実行で、prisma/schema.prisma と .env が生成される。

### mysqlに変更
こちら、接続したいDBにより内容は変わるが、今回ローカルのdockerで立てた mysql に接続するパターンを記す


#### prisma/schema.prisma を修正
```diff_typescript
datasource db {
-  provider = "postgresql"
+  provider = "mysql"
  url      = env("DATABASE_URL")
}
```

#### .env を修正
```config
DATABASE_URL="mysql://root:password@localhost:5306/test-db"
```

### DDL
```sql
CREATE TABLE `users` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ユーザID',
  `name` varchar(100) NOT NULL COMMENT 'ユーザ名',
  `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成日時',
  `updatedAt` datetime DEFAULT NULL COMMENT '更新日時',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```

### DML
```sql
INSERT INTO `test-db`.users (id, name, createdAt, updatedAt) VALUES
(1, 'userA', '2025-01-20 15:15:48', NULL),
(2, 'userB', '2025-01-27 05:15:48', NULL);
```

# PrismaクライアントのインストールとExpress初期設定


```shell
npm install @prisma/client
```

./index.ts
```typescript
import express from "express";
import { PrismaClient } from "@prisma/client";

const app = express();
const PORT = process.env.PORT || 3000;

const prisma = new PrismaClient();

// ルートパスへのリクエスト
app.get("/", (req, res) => {
  res.send("Server is running");
});

// リッスンするポートの設定
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
```

```shell
npx ts-node index.ts
```
で起動して、 ブラウザでlocalhost:3000にアクセスし、「Server is running」が出力していることを確認。

http://localhost:3000

----

# prisma generate

sqlファイルを扱えるように、`prisma/schema.prisma` を修正


```diff_typescript
generator client {
  provider = "prisma-client-js"
+  previewFeatures = ["typedSql"]
}
```

### 全ユーザ情報を取得するためのSQL
prisma/sql/getUsers.sql
```sql
SELECT `id`, `name`, `createdAt`, `updatedAt`
FROM `users` 
ORDER BY id
```

### idを指定してユーザ情報を取得するSQL
prisma/sql/getUser.sql
 ```sql
SELECT `id`, `name`, `createdAt`, `updatedAt`
FROM `users` WHERE `id` = ?
LIMIT 1
```

※ 引数の書き方はDBによって違うらしい [参考](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/typedsql#passing-arguments-to-typedsql-queries)


下記コマンドでクライアントを生成
```shell
npx prisma generate --sql --allow-no-models
```
※ よくわからんけど、node_modules内のファイルが書き換えられる

:::note info
上記までの作業で、用意したSQLを投げることができる。
SQLを修正追加した場合事に `generate` コマンドの実行が必要。
:::

# Expressの処理の中でSQLを投げ結果を得る

./index.ts に下記を追記
```typescript
import { getUser, getUsers } from "@prisma/client/sql";


// id指定でユーザ情報を取得
app.get("/getUser/:id(\\d+)", async (req, res) => {
  const id = req.params.id;

  const users: getUser.Result[] = await prisma.$queryRawTyped(
    getUser(Number(id))
  );

  if (!users || users.length === 0) {
    res.send(`<pre>Not Found.<pre>`);
    return;
  }

  res.send(
    `<pre>User:${JSON.stringify(users[0], (key, value) =>
      typeof value === "bigint" ? Number(value) : value
    )}<pre>`
  );
});

// 全ユーザ情報を取得
app.get("/getUsers", async (req, res) => {
  const users: getUsers.Result[] = await prisma.$queryRawTyped(getUsers());

  const userDisplay = users
    .map(
      (user) =>
        `User ${user.id.toString()}: ${JSON.stringify(user, (key, value) =>
          typeof value === "bigint" ? Number(value) : value
        )}`
    )
    .join("\n");

  res.send(`<pre>${userDisplay}<pre>`);
});

```

```shell
npx ts-node index.ts
```
で起動して、 ユーザ情報を取得できることを確認。

http://localhost:3000/getUsers

http://localhost:3000/getUser/1

### SQLを直接書いてみる

./index.ts に下記を追記
```typescript
// id指定でユーザ情報を取得
app.get("/getUser", async (req, res) => {
  const { id } = req.query;

  const users: any[] = await prisma.$queryRawUnsafe(
    `SELECT id, name, createdAt, updatedAt" FROM users WHERE id = ${Number(id)}`
  );

  if (!users || users.length === 0) {
    res.send(`<pre>Not Found.<pre>`);
    return;
  }

  res.send(
    `<pre>User:${JSON.stringify(users[0], (key, value) =>
      typeof value === "bigint" ? Number(value) : value
    )}<pre>`
  );
});

```

http://localhost:3000/getUser?id=1

# まとめ

generate コマンドを避け得て通りたかったがどうやら避けれ無さそう？
※ 方法あったら教えてください

上記で上げたような簡単なSQLであれば、やはり `prisma/schema.prisma` にmodelを書いてfindメソッド系で呼び出したりするほうが早そう。
```typescript
const users = await prisma.user.findMany();
```
とかね。

javascript, typescriptでのバックエンドの業務をちゃんとやったことがないので、何が正しいのか手探りだけど、数粒の糧にはなったか。。。
※ BEのお仕事ください(切実)

## ソース
https://github.com/rk7fd3s/prisma-express