本セッションの登壇者
セッション動画
よろしくお願いします。
事前に書いたタイトルが「T3を支えるTypeScript」だったのですが、資料をまとめながら「いや、これなんかTypeScriptというよりエコシステムの話かな」となったので、最後に「のエコシステム」という言葉を補足しつつ、お話しさせていただこうかなと思います。
自己紹介です。@Quramyというこの緑色のアイコンで、TwitterだったりGitHubだったりの活動をしている人です。

Tで始まるライブラリが3つ入ったT3 Stack
今日はTypeScriptのトレンドみたいなのが大きいテーマで、僕のほうからは去年わりと人気がぐっと上がったT3 Stack周りのお話をしていこうかなと思います。
T3 Stackというのは、ご存知の方も多いと思うんですけど、バックエンド/フロントエンドの両方をTypeScriptで記述するという前提の、いわゆるフルTypeScriptなライブラリ構成を持ったスタックです。「Create T3 App」と呼ばれるCLIも合わせて公開されています。

スタックなのでいろいろなライブラリが詰め合わされているんですが、ここに書いてあるようなもの、とくにTで始まるものが3つ入っているので”T3 Stack”と呼ばれます。

今日はTypeScriptの話なので、とくにTSと関係の深い部分としてこのあたり(TypeScript、tRPC、Prisma)を見ていこうかなと思っています。

約束は「Typesafeは必須」 - Typesafe is not optional!
中身を見ていく前に、T3のドキュメントにも書いてあるのですが、Axiom - いわゆる自明の理とか公理、T3が大事にしているポリシーみたいなところがけっこうでかでかと書いてあります。「Typesafety is not optional - Typesafeはオプショナルではなく必須のものです」ということが書いてあります。とくにこのあたりです。「TypeScriptの機能を妥協するような意思決定が必要なものは、Create T3 Appには入れません。それは別のプロジェクトでやってください」といった内容が書いてあります。つまり、それぐらいTypesafeであることを大事にしているということです。

そもそもTypesafeってどんな状態を指すのでしょうか。なんでわざわざこんなスライドを挟んだかというと、この「安全性」という言葉がコンテキストによって定義や意味が異なっていたりするので、要注意かなと思って書いています。
たとえば、仕様書で規定されているすべての型について未定義動作が存在しないという状況を「安全」というのであれば、JavaScriptは型について安全と言えるので、Typesafeになっちゃいます。そういうふうにTypesafeを定義することも場合によってはあるよ、という意味ですね。

TypeScriptはJavaScriptのコードがそのままTypeScriptのコードになるわけで、「じゃあこういうふうに書いたTypeScriptコードはTypesafeですか?」と考えてみると、たぶんここに書いてあるコードをTypesafeだと言うTypeScripterはあんまりいないんじゃないかなと思います。
const product = await fetch(`/api/products/1234`).then(res => res.json())
なぜならば、fetch APIのres.jsonの結果というのはanyだし、このfetchの中身に書いてある文字列だって1個間違ったらバグになっちゃうし…なので、バグを生みやすそうなコードだと思うのが普通だと思います。こういった理由から、僕らにとってのTypesafeとはアプリケーションにバグが混入してしまうような可能性をスタティックに最小化/極小化できている状態 - こういう状態を僕らはTypesafeと呼ぶんじゃないかなと思います。

「unknown」をいかに減らすかがDX向上のカギ
TypeScriptを使っているのにTypesafeが損なわれてしまうのはどういう状況かと考えてみると、やっぱりunknownなんですね。ここで言っているunknownというのはTypeScriptのunknown型というよりは、本当に未知なるものを差しています。図のように真ん中にTypeScriptのコードがあったときに、僕らWebアプリケーションエンジニアにとってはいろんなところからunknownが攻めてくるんですね。バックエンドであればデータベースであったり、APIのリクエストレスポンスであったり、もしかしたらCSSとか、HTML文字列みたいなものもわりと未知なるものとして扱わないといけないかもしれない。なので、こういう自分のコードの周辺から攻めてくる、とくにJavaScriptの外側から攻めてくるunknownというものをいかに減らしていくかが、DX向上のカギなんじゃないかと思うわけです。

翻って、T3がこのunknownたちとどうやって戦ってるのかを考えてみると、フルスタックではブラウザ側とノードにはTypeScriptのコードがあるんですけど、それぞれいろんなものが挟まってるんですね。たとえばインターネットを挟んだところはtRPCが守ります。データベースに関連するところはPrismaが守ってくれます。今日はあんまりここの話をするつもりはないんですけど、レンダリングエンジン側で言うと、ReactとTSXの組み合わせで守っているというところです。それぞれようやくライブラリの名前が出てきたので、これらを見ていきたいと思います。

フルスタックTypeScript開発に特化したtRPC
まず、tRPCです。名前のとおりというか、TypeScriptによるリモートプロシージャコールを実現するためのライブラリです。呼び出し側と呼び出される側、それぞれ両方ともTypeScript開発をする前提のものですね。そういう意味においては、同じインターネットを跨ぐような部分なんだけど、GraphQLやOpen APIというのは両側に出てくる言語が何であってもいい。それに対して、tRPCというのは完全にTypeScript開発に特化しているという意味で性質が異なるんですね。tRPC自体は中心的にZodというものを使っているので、ある意味Zodエコシステムのひとつと言ってもいいと思いますし、T3で使う場合にはランタイムとしてはreact-queryが出てきますので、react-queryを使ったライブラリだという感じです。

細かいコードはさておき、構造としてはNode.jsで書いたなにがしかのこういう関数のコードがクライアント側で推論されていきますので、ここで用意したproductDetailという名前のエンドポイントがproductDetailに入るし、戻り値だったり引数だったりもちゃんと推論されている構造です。

tRPCがどうやってTypesafeを実現しているかというと、基本的にはここにあるようなMapped typesやConditional Typeといったような機能、TypeScriptの機能をフル活用して型推論をゴリゴリやることで、さっきのようなサーバとクライアント間の推論が実現されているといった感じです。

クエリをTypesafeに記述できるPrisma
もうひとつ出てくるのはPrismaですね。こいつはわりともう登場してから時間が経っていて、Node.js用のOR mapperで、とくにクエリ部分、where文とかその手をTypesafeに書けるというのがウリのライブラリで、いろんなRDBに対応しているし、マイグレーション用のCLIが付いているので、OR mapperとしてひととおりの機能を備えているようなやつです。

こいつの場合はデータベースの情報とNode.jsをつなぐための存在なんですけど、さっきはTS to TSだったんですがこっちはschemaと呼ばれる独自の形式のファイルを正として書いておいて、ここからジェネレートCLIを実行することによって、クライアントのソースコードと型定義ファイルが生成されて、それを元にproductというテーブルがあるとか、where文にはIDが詰められるといったことがが守られる、そんな構造ですね。

Prismaは外部DSLから自動生成する戦略でやっています。そういった意味ではtRPCやZodとはちょっと異なっていて、むしろこの戦略だけの意味で言うとGraphQLだったりOpen APIのエコシステムに近いですかね。

まとめ - Typesafeを必須に
時間が差し迫ってきているので、まとめに入りたいと思います。
今日言いたかったことは、T3そのものというよりは、T3のAxiomであるTypesafe、「Typesafety is not optional」というところこそを大事にしましょうということです。これをどうやって実現するのかというと、まず、unknownを倒すことを考えれば必然的にこれが達成できると思っています。そのやり方としては大きく2種類の戦術があるということを言いました。TypeScript型推論のパターンであるのか、.tsコード自動生成のパターンであるのかです。逆に言うとにT3には含まれてないですが、たとえば型推論のすごく強いツールとしてはreact-hook-formがあったりとか、すこしだけ紹介したGraphQLなんかは自動生成にすごい強みがあるツールなので、このあたりからTypeScriptフレンドリーなものを選んでTypesafeを必須にするようなスタックを組んでいきたいと思います。

私からの発表は以上となります。どうもありがとうございました。