これはなに
最近会社でNext.js(App Router)でダッシュボードアプリを開発しております。
そのアプリでは、PrismaというTypedScriptのORMツールを用いてDBとのアクセスを行っています。
今回はそのPrismaの機能の1つであるTypedSQLという機能を触ったのでアウトプットしていこうかと思います。
まず、結論からいうとTypedSQLは若干クセがあるものの、めちゃくちゃおすすめなツールです。
この記事では以下を説明しようかと思います。
- TypedSQLとは何か
- TypedSQLの基本的な使い方
- TypedSQLを使う上で詰まったポイント
この記事を読めば、こうなります!!
前:🤪「TypedSQLてなんやね〜〜〜ん。ブフォ!」
↓
後:😎「え?TypedSQL使ってないの?危機感持った方がいいよ。」
では早速いきましょう!
🐶「この記事はPrismaについて最低限の知識がある前提で書いています」
1. TypedSQLとは何か?
概要
TypedSQLはざっくりいうと『生SQLから型安全なデータを取得できるツール』です。
.sql
にSQLを記述することでPrismaクライアント側で使うことができる型定義とクエリ関数を生成してくれます。
SQLから得られる結果を自動的にTypeScriptの型定義に変換してくれるため、「いちいち型の定義をしなくても良い」かつ「型安全にDBのデータを扱うことができる」ようになるのです…!
TypedSQLを初心者でも分かるように解説!
1. TypedSQL とは?
Prisma の TypedSQL は、自分で書いた SQL ファイル(.sql
)をもとに、Prisma Client 側で型定義とクエリ関数を自動生成し、結果とパラメータの両方に型チェックを効かせられる機能です。
これにより、次のメリットがあります:
- 型安全性:SQL の結果を TypeScript の型として扱える
- SQL インジェクション対策:パラメータ化されたクエリを自動生成
- DX 向上:エディタ上で補完や型チェックが効く (Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
2. 導入手順(Getting started)
-
バージョンの確認
-
@prisma/client
とprisma
を少なくとも5.19.0
以上にアップデートします。
npm install @prisma/client@latest npm install -D prisma@latest
(Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
-
-
プレビュー機能の有効化
-
schema.prisma
のgenerator client
ブロックにpreviewFeatures = ["typedSql"]
を追加します。
generator client { provider = "prisma-client-js" previewFeatures = ["typedSql"] }
(Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
-
-
SQL ファイルの配置
- プロジェクトの
prisma
ディレクトリ内にsql
フォルダを作成し、そこにクエリを.sql
として保存します(ファイル名は JS の識別子ルールに従い、$
で始めない)。
mkdir -p prisma/sql
- プロジェクトの
-
SQL ファイルの作成例
- 例:
prisma/sql/getUsersWithPosts.sql
SELECT u.id, u.name, COUNT(p.id) AS "postCount" FROM "User" u LEFT JOIN "Post" p ON u.id = p."authorId" GROUP BY u.id, u.name;
(Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
- 例:
-
クライアントの再生成
-
--sql
フラグをつけて Prisma Client を生成すると、TypedSQL 用の関数と型が作成されます。
prisma generate --sql # 変更を監視しながら生成する場合 prisma generate --sql --watch
(Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
-
-
TypeScript での利用例
import { PrismaClient } from '@prisma/client'; import { getUsersWithPosts } from '@prisma/client/sql'; async function main() { const prisma = new PrismaClient(); // 型安全に SQL を実行 const usersWithPostCounts = await prisma.$queryRawTyped( getUsersWithPosts() ); console.log(usersWithPostCounts); } main();
(Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
3. パラメータ付きクエリの書き方
TypedSQL では、SQL ファイル内にプレースホルダーを記述し、TypeScript 側で引数を渡すことで安全にパラメータ化されたクエリを実行できます。
PostgreSQL の例($1
, $2
…)
-- prisma/sql/getUsersByAge.sql
SELECT
id,
name,
age
FROM
users
WHERE
age > $1
AND age < $2;
import { PrismaClient } from '@prisma/client';
import { getUsersByAge } from '@prisma/client/sql';
async function main() {
const prisma = new PrismaClient();
const minAge = 18;
const maxAge = 30;
// 引数をそのまま渡す
const users = await prisma.$queryRawTyped(
getUsersByAge(minAge, maxAge)
);
console.log(users);
}
main();
- MySQL なら
?
- SQLite なら
?
や:minAge
のような名前付きプレースホルダーも使えます (Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
Point: パラメータ化することで SQL インジェクションを防ぎ、型チェックも効かせられます。
4. 配列を引数に渡す(PostgreSQL のみ)
PostgreSQL の ANY
演算子と組み合わせることで、配列をパラメータとして渡せます。
-- prisma/sql/getUsersByIds.sql
SELECT
id,
name,
email
FROM
users
WHERE
id = ANY($1);
import { PrismaClient } from '@prisma/client';
import { getUsersByIds } from '@prisma/client/sql';
async function main() {
const prisma = new PrismaClient();
const userIds = [1, 2, 3];
const users = await prisma.$queryRawTyped(
getUsersByIds(userIds)
);
console.log(users);
}
main();
TypedSQL が配列の型(number[]
など)も自動で推論してくれます。ただし、大量の要素を一度に送る場合はプレースホルダー数の上限に注意してください (Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
5. SQL ファイル内でのパラメータ型定義
SQL 側でパラメータの型やエイリアスをコメントで定義すると、より詳細な型情報を生成できます。
-- @param {Int} $1:minAge ユーザーの最小年齢
-- @param {Int} $2:maxAge ユーザーの最大年齢
SELECT
id,
name,
age
FROM
users
WHERE
age > $1
AND age < $2;
-
{Type}
はInt
,BigInt
,Float
,Boolean
,String
,DateTime
,Json
,Bytes
,Decimal
のいずれか - エイリアス(
:minAge
)により、TypeScript の型定義もフレンドリーに命名できます - オプショナルにしたい場合は
alias?
と書くとalias?: 型
になります (Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
Note: 配列型の引数については手動での型定義はできず、TypedSQL の型推論に任せます。
6. TypedSQL の制限事項
-
対応データベース
- MySQL(8.0 以上)/PostgreSQL(現行版)は自動推論対応
- MySQL < 8.0/SQLite は手動で
@param
定義が必要 - MongoDB には非対応 (Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
-
データベース接続が必須
-
prisma generate --sql
実行時に、スキーマのdirectUrl
または通常の接続情報を使って実際にデータベースへ接続し、メタデータを元に型情報を取得します (Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
-
-
動的カラム選択には非対応
- クエリ中で動的に列を増やしたい場合は、
$queryRaw
/$queryRawUnsafe
を利用します。その際は SQL インジェクション対策を自前で行う必要があります (Writing Type-safe SQL with TypedSQL and Prisma Client | Prisma Documentation)
- クエリ中で動的に列を増やしたい場合は、
ちなみに、TypedSQLはPrismaのPreview機能となっております。そのためか、公式ドキュメントはかなり少ない…
公式ドキュメント:
🐶「記事を書いている時点(2025/04)
では、TypedSQLはPrismaのPreview機能」
ちなみに以下の記事がめちゃくちゃわかりやすかったのでよかったらご覧ください。
めちゃくちゃわかりやすい記事:
これは余談ですが、Golangにも似たようなsqlcというライブラリが存在します。
こちらも同様にSQLから型安全なコードを生成してくれます。
TypedSQLが生まれた背景
TypedSQLがざっくりとどんなものかは理解してもらえ方と思います。
続いて、TypedSQLが誕生した背景を調べたのでまとめました。
ChatGPTが…!!
TypedSQLが誕生した背景
以下では、TypedSQL が誕生した背景を「なぜ Prisma チームがこの機能を必要としたのか」という観点で解説します。まず要点をまとめ、その後で詳しく掘り下げます。Prisma ORM の TypedSQL は、従来の $queryRaw
を使った生の SQL 実行が「型安全性の欠如」「開発体験(DX)の低さ」といった課題を抱えていたため、これらを解消するために生まれました。Prisma Client API がカバーできない複雑クエリ(およそ 5%)に対して、TypeScript 上で入力と出力の両方に型チェックとオートコンプリートを提供し、手動で型定義を書く手間やメンテナンスコストを大幅に削減します。また、PgTyped や sqlx など他言語/他ツールの「型付き SQL」実装から着想を得ており、Prisma の高レベル抽象 (findMany
など) と生 SQL の自由度をシームレスに両立する狙いがあります。
1. 従来の生 SQL 実行の課題
- Prisma の
$queryRaw
は生の SQL をそのまま実行できるものの、結果型を手動で定義しなければならず、スキーマ変更時に追従しづらい (Announcing TypedSQL: Make your raw SQL queries type-safe with Prisma ORM)。 - エディタでの オートコンプリートが効かない、型ミスマッチが実行時エラーになる、という DX の低さがあった (Announcing TypedSQL: Make your raw SQL queries type-safe with Prisma ORM)。
- チーム内に SQL の得意不得意が分かれると、クエリの可読性・メンテナンス性にもばらつきが生じやすい (Announcing TypedSQL: Make your raw SQL queries type-safe with Prisma ORM)。
2. 逃げ道(Escape Hatch)だけでは不十分
- Prisma Client API で表現できない「複雑な結合」「高度な集計」「パフォーマンスチューニング向けのチューニング」などは全体の約 5% を占めると言われる (Announcing TypedSQL: Make your raw SQL queries type-safe with Prisma ORM)。
- これらのケースで生 SQL を使う開発者は、型安全性を犠牲にしてでも必要最低限のパフォーマンスを確保したい一方で、Prisma の持ち味である開発体験を失いたくないというジレンマがあった (Announcing TypedSQL: Make your raw SQL queries type-safe with Prisma ORM)。
3. 型安全な生 SQL「TypedSQL」の登場
- v5.19.0 リリースで TypedSQL が正式にプレビュー機能として追加され、生の SQL ファイル(
.sql
)から自動的に TypeScript 用の入力型・出力型・実行関数を生成できるように (Announcing TypedSQL: Make your raw SQL queries type-safe with ...)。 - SQL のパラメータ(PostgreSQL の
$1
など)や結果カラムにも型推論が効き、SQL インジェクション対策を兼ねつつ型チェックが可能に (Writing Type-safe SQL with TypedSQL and Prisma Client)。 - エディタ上ではクエリファイル内の補完、TypeScript 側では関数呼び出し時のシグネチャ補完と型チェックが提供され、開発体験が大きく向上 (TypedSQL: Fully type-safe raw SQL in Prisma ORM)。
4. 他プロジェクトからのインスピレーション
- TypedSQL のコンセプトは、TypeScript 向けの PgTyped や、Rust の sqlx など 既存の「型付き SQL ジェネレーター」 にヒントを得ている (Announcing TypedSQL: Make your raw SQL queries type-safe with Prisma ORM)。
- これらのツールは「SQL を生で書く自由度」と「型安全性」を両立しており、その考え方を Prisma のエコシステムに自然に統合しようという狙いがあった (Announcing TypedSQL: Make your raw SQL queries type-safe with Prisma ORM)。
5. コミュニティと開発チームの声
- Prisma Maintainer からは「生 SQL の型安全性と DX をネイティブに提供する方法が欲しかった」というフィードバックが出ており、公式ディスカッションでの要望を反映 (Preview feature feedback: typedSql #25106 - GitHub)。
- Podcast「Syntax.fm」で共同創業者の Søren Bramer Schmidt が TypedSQL を紹介し、Prisma の「ローカルファースト」「高い UX」戦略の一環として位置づけられていることを説明 (Prisma ORM: Local First, Typed SQL Queries and Serverless with ...)。
- 開発者コミュニティ(DEV.to や Reddit)でも「型安全な生 SQL が簡単に使えるのは画期的」「手動で型を書く手間が省ける」といった好意的な声が多く上がっている (TypedSQL in Prisma: Type-Safe SQL Made Simple - DEV Community, What's everyone's opinion of Prisma's TypedSQL update? - Reddit)。
まとめると、TypedSQL は「Prisma Client API が対応しきれない複雑クエリを、手間なく型安全かつ快適に扱いたい」という開発者ニーズと、「ORM と生 SQL の良さを両立したい」という Prisma チームのビジョンから生まれました。今後は GA に向けて安定性を高めつつ、さらなる開発体験の向上が期待されています。
TypedSQLが誕生した背景には大きく以下の2つがあるのかなと思います。
- ORMだと複雑なクエリを描こうとすると難しし、内部で実行されているSQLが思ってたのと違うってなる
$queryRaw
を使えば生のSQLは書けるけど、デフォルトだと返り値の型がunknownなのであんまりイケてない…
って感じかと。
それをTypedSQLはどちらも叶えてくれる最強のツールとして誕生したというわけです…!!
TypedSQLを使えば生SQLを使うので複雑なクエリも書けてしまうし、返り値の型もTypedSQLが自動で定義してくれるので、型安全です。
全部可能にしてくれちゃってるんです。最強なんです。
🐶「これまで大変だったORMでの複雑なクエリの問題や生SQLを書いても型安全を保証できないという課題を全て解決…!!」
さらに詳しく知りたい方はこちらの記事を読んでもらえるといいかと!これまでのPrismaでの開発の課題などがわかりやすく書かれてあります。
ざっくりとTypedSQLについて理解できたかと思います。
次からは、どうやってTypedSQLを使うかを解説していきます!
2. TypedSQLの基本的な使い方
詳しくは以下の公式ドキュメントをご覧ください!
事前準備
schema.prisma
ファイルのpreviewFeatures
という箇所にtypedSql
を追加してください。
generator client {
provider = "prisma-client-js"
previewFeatures = ["typedSql"] # こんな感じ
}
sqlディレクトリにsqlを記述する
prisma
ディレクトリ配下にsql
というディレクトリを作成してください。
このディレクトリにsqlファイルを追加していきます。
mkdir -p prisma/sql
🐶「sqlファイルの名前が実行される関数名になります!例えば、fetchUser.sql
というファイルをprisma/sql
に配置すれば、呼び出す際はfetchUser()
という関数になります!!」
以下のような.sql
ファイルを追加すればOKです。
SELECT u.id, u.name, COUNT(p.id) as "postCount"
FROM "User" u
LEFT JOIN "Post" p ON u.id = p."authorId"
GROUP BY u.id, u.name
ちなみにですが、変数をsqlファイル内に仕込みたい場合は$1
, $2
を用いることで可能(PostgreSQLの場合)です。
SELECT id, name, age
FROM users
WHERE age > $1 AND age < $2
そのほかは以下のリンクを参照ください!
関数として呼び出すために、Prismaクライアントを作成する
以下のコマンドを実行することで、TypeScript関数として呼び出せる関数をPrismaが生成してくれます。
prisma generate --sql
そうすることで、以下のように関数を用いてSQLを実行することができるようになります。
import { PrismaClient } from '@prisma/client'
import { getUsersWithPosts } from '@prisma/client/sql'
const prisma = new PrismaClient()
const usersWithPostCounts = await prisma.$queryRawTyped(getUsersWithPosts())
console.log(usersWithPostCounts)
変数が必要な場合は、以下のように指定することができます。
めっちゃシンプルですね!!
import { PrismaClient } from '@prisma/client'
import { getUsersByAge } from '@prisma/client/sql'
const prisma = new PrismaClient()
const minAge = 18
const maxAge = 30
const users = await prisma.$queryRawTyped(getUsersByAge(minAge, maxAge))
console.log(users)
🐶「ざっくりと流れとしては、
① schema.prisma
ファイルでtypedSQLを使えるようにする
② sqlディレクトリに.sql
ファイルを追加する
③ prisma generate --sql
コマンドを実行し、関数を作成
④ PrismaClient
を経由して関数を呼び出す
ですっ!
」
個人的におすすめな使い方
個人的に好きなTypedSQLの使い方も共有しようかと思います。
それは、以下のように{sqlのファイル名}.Result
とすることで、自動的に生成された型をTypeScript内で柔軟に使えるようにすることです。
import { getUser } from '@prisma/client/sql';
type Result = getUser.Result;
Userの中身は以下の通り。(あくまでサンプルです)
export type Result = {
id: $runtime.Decimal | null
name: string | null
email: string | null
createdAt: Date | null
}
こうすることで
- 型安全性の確保
- IDEの自動保管サポート
- リファクタリングの容易さの向上
- 手動で型定義をする必要がなくなる
などのメリットを傍受できるかと思います!
3. TypedSQLを使う上で詰まったポイント
ここからはTypedSQLを使って実装を進める上で詰まったポイントについて共有していこうかと思います。
ドキュメントが少ない
まず、TypedSQLはPreview機能ということもあり、ドキュメントがめちゃくちゃ少ないです…!😭
私の知る限りだと、公式ドキュメントは以下の記事のみです…
🐶「2025/04時点だと公式ドキュメントはこれしかない…!」
詳しくは後述しますが、TypedSQLはprisma generate --sql
コマンドの実行時に、接続可能なDATABASE_URL
が必要です。
それは公式ドキュメントでは具体的な対策は記述されておらず、ほかの人はどうやってデプロイしているのか調べる時にかなり大変だったことを覚えています。
以下のGitHubのDiscussionsを参照しながら、頑張って開発を進めた記憶があります。
🐶「いろんな人のTipsがDiscussionsには載っていたので一度見てみることをおすすめします!」
デプロイが大変
ちらっと頭出ししましたが、TypedSQLはprisma generate --sql
コマンドの実行時に、接続可能なDATABASE_URL
が必要です。
特に設定をしないのであれば、prisma generate --sql
で生成されるコードはnode_modules
に作成されます。
🐶「バージョン管理するのであれば、DATABASE_URLは不要なので楽だけど、自動生成されるコードをバージョン管理するのあんまりイケてないよね。」
自動生成されるコードをバージョン管理したくないので、それは問題ないのですが、そうなるとビルド時に接続可能なDATABASE_URL
が必要になってきます。
それの対応が大変でした…
実際、TypedSQLに関するDiscussionsでも同じく悩んでいる人が多くいらっしゃいました。
これらの記事を見ながら色々試行錯誤した結果、『ビルド時にPostgreSQLを作成し、接続できるようにする』ことで対応しました。
FROM node:22-bookworm-slim AS base
# 色々な処理
# OpenSSLとその他の必要な依存関係をベースイメージにインストール
# 参考:https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#run
RUN apt-get update && apt-get install -y --no-install-recommends openssl postgresql postgresql-contrib && rm -rf /var/lib/apt/lists/*
# PostgreSQL用ディレクトリの作成と権限付与
# 参考:https://github.com/prisma/prisma/issues/25124#issuecomment-2507850308
RUN mkdir -p /run/postgresql /var/lib/postgresql/data && \
chown -R postgres:postgres /run/postgresql /var/lib/postgresql/data
# PostgreSQLの初期化
RUN su - postgres -c "/usr/lib/postgresql/15/bin/initdb -D /var/lib/postgresql/data"
# 一度起動してpostgresユーザーのパスワードを設定し、すぐ停止
RUN su - postgres -c "/usr/lib/postgresql/15/bin/pg_ctl start -D /var/lib/postgresql/data && \
psql -c \"ALTER USER postgres PASSWORD 'postgres';\" && \
/usr/lib/postgresql/15/bin/pg_ctl stop -D /var/lib/postgresql/data"
# Dockerfile 内で DATABASE_URL を設定
ENV DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres
# PostgreSQLを起動し、マイグレーションおよびprisma generateコマンドを実行
RUN su - postgres -c "/usr/lib/postgresql/15/bin/pg_ctl start -D /var/lib/postgresql/data" && \
pg_isready -h localhost -p 5432 -U postgres && \
npx prisma db push && \
npx prisma generate && \
npx prisma generate --sql && \
su - postgres -c "/usr/lib/postgresql/15/bin/pg_ctl stop -D /var/lib/postgresql/data"
RUN npm run build
# 色々な処理
このようにアクセス可能なダミーのDBをDockerfile内で作成するようにすることでビルド時に接続可能なDATABASE_URLが必要問題は解決しました。
🐶「もし他にいい方法があるならご教示いただけますと幸いだワン」
まとめ
最後までお読みいただきありがとうございました!
TypedSQLはPreview機能ながらも、生のSQLを用いながら型安全に、かつ複雑なクエリも書くことができるイケイケツールです。
ビルドの際に、アクセス可能なDATABASE_URLが必要になるという若干の癖がありつつも、使いこなせるようになれば非常に便利なツールかと思います。
皆様もぜひ、この記事をきっかけにTypedSQLに興味を持ってくださいますと幸いです!
あらためて、最後までお読みいただき本当にありがとうございました!
🐶「ありがとうございました〜!」
🐶「今回紹介したTypedSQLのビルド時の方法よりも良い方法があればぜひコメントで共有いただけますと幸いです!」