【TsKaigi】「TypeScriptとGraphQLで実現する型安全なAPI実装」を聞きました

TypeScriptとGraphQLで実現する型安全なAPI実装

tskaigi.org

Kazuhito Hokamura 様(@hokaccha)

ユビー株式会社 / ソフトウェアエンジニア

スライド

内容メモ

今回の内容はzennにもまとめられている

Kotlin -> node, tsに書き換えなどを行っている

なぜAPIに型をつけたいのか

const response = await fetch("hogehoge")
const user:User = await response.json()

これのuser:Userは祈り。これが通らないと、ぬるぽになる

→ 外部I/Oに対する片付けが不十分だと静的型付けの恩恵を十分に活かせない

ここを起因にして発生するエラーは多い

APIに型をつけるとは?

  1. APIの仕様をスキーマで定義する:そもそもちゃんと界面の型を決めないといけない
  2. スキーマから実装言語の型を生成する
  3. 生成された型を使って実装する

  4. クライアントの実装

  5. サーバーの実装

→ OpenAPI, gRPC, GraphQLなどを用いることで型をつけることができる。

gRPCはツールチェーンで実装まで型をつけてくれる機能がある。GraphQLだと、実装に型をつけるのはオプショナルになっている。

GraphQL Codegen

デファクトになっているコードジェネレーターツール

クライアントの便利機能

Client Presetというクライアント向けのセットがある

重要なポイントとしては、スキーマから型を生成するのではなく、クエリから型を生成する。

GraphQLはクライアントは必要なフィールドを指定できるという機能があるが、スキーマから型を生成すると、すべてのフィールドを含んだ型を生成してしまうので、GraphQLの柔軟性が活かせない

GraphQL CodeGenは、クエリから型を生成することができる!

(だからクライアント側とサーバー側でツールが異なるのか!)

Fragment Colocation

上で取得したデータをコンポーネント内の下層のコンポーネントで情報を使うというような流れ

→ しかし、下層のコンポーネントで、あるフィールドを使わなくなった時に、それに気づけない。どこかで使ってるかもしれないし、安易に消せない。サーバー側から本当は必要ではない情報があるのにいつまでも取得してしまう。

「すべての子が必要とするデータをトップレベルのクエリが知っている必要がある」という設計の歪み

-> Fragment Colocationを用いることで解決できる。

自分に必要な情報だけを定義しておいて、自分と子だけを管理するようにすることで、上のような状況を避けることができる。

→ これをGraphQL CodeGenが対応している!!

Fragment Masking

あるコンポーネントでしか仕様していないフィールドが、他のコンポーネントでも使用しようとしてしまう。大元で情報を取ってきているので、取れてしまうが、Fragment Maskingを用いることによって型エラーを出すことができる。

しかし、型が複雑になるというデメリットもある

サーバーサイド

GraphQL Codegen TypeScript Resolvers

graphql-js, Apollo Server, GraphQL Yogaなどのメジャーなライブラリに対応

NestJSだけちょっと特殊。

サーバーはスキーマから型を生成すれば良い。

例えばスキーマ内の取得しないフィールドがある場合、その取得無駄になることがある。

その時に、フィールドごとにリゾルバを定義することによって、遅延実行をすることができる。

しかし素直にすると、型エラーになってしまうが、打開策もある。

感想

GraphQLを用いて開発していたが、Fragment Colocation(クライアントサイド)や、フィールドごとにリゾルバを設定(サーバーサイド)などについて知らなかった。

今まで課題に感じていた部分の解決になりうる概念だったので、知ることができてとてもよかった。

また、TypeScriptとGraphQLでの web API 開発の親和性の高さを知ることができた。