PrismaのTypedSQLを使ってみる
はじめに
Prismaでは複雑なSQLクエリを実行したい場合、$queryRawや$executeRawを使用できます。しかしながら、これらの構文はPrisma Clientによる型の恩恵を受けられず、スキーマ変更に追従しづらいというデメリットがあります。Prisma5.19.0で、この問題を解決するTypedSQLという機能がリリースされています。この記事ではTypedSQLを使用し、どのように型安全性が保証されるのか確認します。
Writing Type-safe SQL with TypedSQL and Prisma Client
注意点
この機能は記事執筆時点でプレビュー版です。
前提
バージョン5.19.0以上のPrisma及びPrisma Clientがインストール済みであること。
また、この記事ではPrismaのインストール方法は割愛します。
使ってみる
使用するスキーマ定義とデータ
この記事では、以下のスキーマ定義を使用します。
model User {
id Int @id @default(autoincrement())
name String @db.VarChar(100)
posts Post[]
@@map(name: "users")
}
model Post {
id Int @id @default(autoincrement())
title String
category String
created_by Int
author User @relation(fields: [created_by], references: [id])
@@map(name: "posts")
}
以下のようなデータが入っています。
usersテーブル

postsテーブル

基本的な使い方
schema.prismaにプレビュー機能フラグを追加します。
generator client {
provider = "prisma-client-js"
output = "../generated/prisma"
previewFeatures = ["typedSql"] <--- 追加
}
続いて、prisma/sqlフォルダを作成し、SQLファイルを格納します。この記事では、以下のようなクエリをgetPostsByUsername.sqlとして作成しました。
select p.id, p.title, p.category
from posts p
inner join users u on u.id = p.created_by
where u.name = $1;
以下のコマンドを実行し、クライアントを生成します。
prisma generate --sql
クライアントを生成すると、以下のようにSQLファイルがインポートできるようになります。
import { prisma } from './client'
import { getPostsByUsername } from './generated/prisma/sql';
クエリを実行したい場合は、$queryRawTyped()を使用します。クエリにパラメータを渡したい場合は、以下のように引数として指定します。
const posts = await prisma.$queryRawTyped(getPostsByUsername('加藤 三郎_001'))
実行すると、以下のような結果が返り、クエリが正常に実行されたことがわかります。
[
{ id: 1, title: '応用編 Docker テクニック', category: 'Technology' },
{ id: 2, title: '実践的な React チュートリアル', category: 'Security' },
{ id: 3, title: '最新 TypeScript 設計パターン', category: 'Backend' },
{ id: 4, title: '応用編 テスト ベストプラクティス', category: 'WebDevelopment' },
{ id: 5, title: '完全ガイド Git ベストプラクティス', category: 'Architecture' },
{ id: 6, title: '徹底解説 テスト 活用法', category: 'Frontend' },
{ id: 7, title: '最新 React 設計パターン', category: 'Programming' },
{ id: 8, title: '初心者向け API設計 の基本', category: 'Backend' },
{ id: 9, title: '応用編 Docker 活用法', category: 'Testing' }
]
返ってきたデータの型推論もできています。

SQLファイルを以下のように変更します。
select p.id, p.title, p.category
from posts p
inner join users u on u.id = p.created_by
where u.name = $1
and p.category = $2; <--- 追加
クライアントを再生成します。
prisma generate --sql
すると、以下のようにエラーになります。クエリ内のパラメータとコード上での引数の不一致が、ちゃんと検知されます。

ちなみに、SQLファイルを変更するたびにクライアントの生成が必要となりますが、--watchオプションをつけることで保存のたびに自動的に再生成されます。

これにより、SQLファイルの変更が即座にTypeScriptコードに反映され、開発者体験が向上します。
生成される型
prisma generateコマンドによって以下のような型が自動生成されます。
/**
* @param text
* @param text
*/
export const getPostsByUsername: (text: string, text: string) => $runtime.TypedSql<getPostsByUsername.Parameters, getPostsByUsername.Result>
export namespace getPostsByUsername {
export type Parameters = [text: string, text: string]
export type Result = {
id: number
title: string
category: string
}
}
この型により、パラメータの型と数、および戻り値の型が保証されます。
ちなみに、以下のような記法でコメントを書くことで、使う側にとってよりわかりやすくなります。
-- @param {String} $1:ユーザ名
select p.id, p.title, p.category
from posts p
inner join users u on u.id = p.created_by
where u.full_name = $1;

マイグレーションする場合
name列をfull_nameにリネームしてみます。
schema.prismaでUserテーブルを以下のように変更します。
model User {
id Int @id @default(autoincrement())
full_name String @db.VarChar(100) <--- 変更
posts Post[]
@@map(name: "users")
}
この時点でクライアントの再生成を行います。
prisma generate
prisma generate --sql
特にエラーなくコマンドが実行できました。
以下のマイグレーションファイルを生成し、データベースに適用します。
ALTER TABLE "users" RENAME COLUMN "name" TO "full_name";
再度クライアントを生成します。
prisma generate --sql
今度は以下のエラーが表示されました。
Error: Errors while reading sql files:
In prisma\sql\getPostsByUsername.sql:
Error: column u.name does not exist
マイグレーションをする場合は、スキーマファイルの変更だけでなく、データベースに適用された内容に基づいて型のチェックがされるようです。
おわりに
冒頭で述べた通り、$queryRawや$executeRawの代替として、複雑なSQLクエリを型安全を保ちつつ管理したい場合に適しています。プレビュー版ではありますが、Prismaの公式ドキュメントでも以下のように述べられています。
With Prisma ORM
5.19.0, we have released TypedSQL. TypedSQL is a new way to write SQL queries that are type-safe and even easier to add to your workflow.We strongly recommend using TypedSQL queries over the legacy raw queries described below whenever possible.
また、SQLクエリに対する完全なコントロールが必要なためORMによるクエリ自動生成機能が使えない場合や、既に生クエリで書かれたプロジェクトにPrismaを段階的に導入するといった場合も使える機能かと思います。
今回試してみましたが、型安全を保ちながらSQLの表現力を活用できる強力な機能だと感じました。
この記事がどなたかの参考になれば幸いです。






