<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>TRPC on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/tags/trpc/</link>
    <description>Recent content in TRPC on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Mon, 23 Feb 2026 12:00:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/tags/trpc/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>GraphQL vs tRPC：2026年のAPI選定基準</title>
      <link>https://www.ai2core.com/posts/2026-02-23-graphql-trpc/</link>
      <pubDate>Mon, 23 Feb 2026 12:00:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-23-graphql-trpc/</guid>
      <description>フルスタックTypeScript開発におけるtRPCの優位性と、GraphQLの使い所。</description>
      <content:encoded><![CDATA[<h1 id="graphql-vs-trpc2026年のapi選定基準">GraphQL vs tRPC：2026年のAPI選定基準</h1>
<h2 id="はじめに">はじめに</h2>
<p>「新しいWebアプリケーションの技術選定、APIはどうしよう？」
「RESTはもう古いと聞くけど、GraphQLを学ぶべきだろうか？」
「最近、界隈でtRPCという技術が絶賛されているけど、GraphQLと何が違うんだろう？」
「そもそも、&ldquo;End-to-Endで型安全&quot;って、具体的にどんなメリットがあるの？」</p>
<p>もしあなたがフルスタックTypeScript開発者で、このような疑問を一度でも抱いたことがあるなら、この記事はまさにあなたのために書かれました。</p>
<p>現代のWeb開発において、APIはアプリケーションの心臓部です。フロントエンドとバックエンドを繋ぐこの重要なコンポーネントの設計は、開発体験、生産性、そしてアプリケーションの品質そのものを大きく左右します。</p>
<p>かつてはRESTがAPIのデファクトスタンダードでしたが、アプリケーションが複雑化するにつれて、その限界も明らかになってきました。そこに登場したのが、柔軟なデータ取得を可能にするGraphQLです。そして今、TypeScriptの普及を背景に、究極のシンプルさと型安全性を追求したtRPCが急速に注目を集めています。</p>
<p>この記事では、2026年を見据えた未来のAPI開発という視点から、GraphQLとtRPCを徹底的に比較・解説します。両者の思想、具体的な実装方法、メリット・デメリットを深く掘り下げ、あなたの次のプロジェクトにどちらの技術が最適なのか、明確な判断基準を提供します。この記事を読み終える頃には、あなたは自信を持って最適なAPI技術を選定できるエンジニアになっているはずです。</p>
<h2 id="api技術の進化と現代の課題">API技術の進化と現代の課題</h2>
<p>なぜ今、GraphQLやtRPCが注目されているのでしょうか？その背景を理解するために、まずはAPI技術が歩んできた道のりと、そこで浮き彫りになった課題を振り返ってみましょう。</p>
<h3 id="restの時代とその功罪">RESTの時代と、その功罪</h3>
<p>長年にわたり、REST (REpresentational State Transfer) はWeb APIの設計原則として広く採用されてきました。HTTPメソッド（GET, POST, PUT, DELETE）とURLでリソースを表現するそのシンプルさと、HTTPとの親和性の高さが支持された理由です。</p>
<p>しかし、フロントエンドがリッチでインタラクティブになるにつれて、RESTのいくつかの課題が顕在化しました。</p>
<ol>
<li><strong>Over-fetching（過剰なデータ取得）</strong>: ある画面ではユーザーの名前だけが必要なのに、<code>/users/:id</code> エンドポイントが常に住所や電話番号まで返してしまう問題。不要なデータを転送するため、特にモバイル環境ではパフォーマンスに影響します。</li>
<li><strong>Under-fetching（不十分なデータ取得）</strong>: ユーザー情報とその最新の投稿一覧を表示したい場合、まず <code>/users/:id</code> を叩き、次に <code>/users/:id/posts</code> を叩く、というように複数のリクエストが必要になる問題。俗に言う「N+1問題」の温床であり、画面表示の遅延に繋がります。</li>
<li><strong>エンドポイントの乱立</strong>: 機能追加のたびに新しいエンドポイントが増え、<code>/v1</code>, <code>/v2</code> のようなバージョン管理や、Swagger/OpenAPIといった仕様書のメンテナンスコストが増大します。</li>
<li><strong>フロントとバックの連携コスト</strong>: APIの仕様変更があった際、フロントエンドとバックエンドで型の不整合が起きやすく、実行時エラーの原因となります。この「口約束」になりがちな連携が、開発のボトルネックになることは少なくありません。</li>
</ol>
<h3 id="救世主としてのgraphql">救世主としてのGraphQL</h3>
<p>これらのRESTの問題を解決するために登場したのがGraphQLです。Facebook（現Meta）によって開発されたGraphQLは、APIのための「クエリ言語」です。</p>
<p>GraphQLは、RESTとは全く異なるアプローチでAPI通信に革命をもたらしました。</p>
<ul>
<li><strong>クライアント主導のデータ取得</strong>: フロントエンドが必要なデータの構造を「クエリ」として送信し、サーバーはまさにその通りの構造でデータを返します。これにより、Over-fetchingとUnder-fetchingが根本的に解決されます。</li>
<li><strong>単一エンドポイント</strong>: <code>/graphql</code> のような単一のエンドポイントに対して、すべてのリクエストをPOSTメソッドで送信します。エンドポイントの乱立やバージョン管理の問題から解放されます。</li>
<li><strong>強力な型システム</strong>: APIの仕様は「スキーマ」として厳密に定義されます。このスキーマは、APIのドキュメントとして機能するだけでなく、開発ツールによる強力なサポート（入力補完やバリデーション）を可能にします。</li>
</ul>
<p>GraphQLは、特に多種多様なクライアント（Web、iOS、Androidなど）を持つ大規模なアプリケーションや、マイクロサービスアーキテクチャにおいて、その真価を発揮しました。</p>
<h3 id="typescriptの台頭と究極の型安全性への渇望">TypeScriptの台頭と「究極の型安全性」への渇望</h3>
<p>そして、もう一つの大きな潮流がTypeScriptの普及です。フロントエンドからバックエンド（Node.js）まで、JavaScriptが使われるあらゆる場所でTypeScriptが採用され、「フルスタックTypeScript」という開発スタイルが現実のものとなりました。</p>
<p>これにより、開発者は新たな高みを求めるようになります。それは、**「アプリケーションの端から端まで（End-to-End）を一つの型システムで貫き、静的解析の恩恵を最大限に享受したい」**という願いです。</p>
<p>GraphQLはスキーマによって型安全性を提供しますが、それを実現するには <code>graphql-codegen</code> のようなツールを使い、スキーマからTypeScriptの型を「生成」するビルドステップが必要でした。この設定は時に複雑で、フロントエンドとバックエンドが密結合しているプロジェクトでは、少々大げさに感じられることもありました。</p>
<p>そこに彗星の如く現れたのがtRPCです。</p>
<p>tRPCは、「そもそもフロントとバックが両方TypeScriptなら、<strong>APIスキーマを別途定義する必要はない。バックエンドのTypeScriptの型定義そのものをスキーマとして共有すれば良いじゃないか</strong>」という、シンプルかつ画期的な発想に基づいています。</p>
<p>コード生成も、ビルドステップも不要。サーバーサイドのルーター定義から、クライアントサイドのAPI呼び出しの型が自動的に推論される。この圧倒的な開発体験が、フルスタックTypeScript開発者の心を鷲掴みにしたのです。</p>
<p>このように、API技術は「より柔軟に、より安全に、より開発体験を良くする」という方向へ進化を続けています。その最前線にいるGraphQLとtRPC、それぞれの具体的な実装と特性を詳しく見ていきましょう。</p>
<h2 id="詳細解説graphqlとtrpcの実装比較">詳細解説：GraphQLとtRPCの実装比較</h2>
<p>百聞は一見に如かず。ここでは、Next.js環境を想定し、ユーザー情報を取得・作成する簡単なAPIをGraphQLとtRPCそれぞれで実装してみます。</p>
<h3 id="graphqlスキーマ駆動開発の世界">GraphQL：スキーマ駆動開発の世界</h3>
<p>GraphQL開発は、まずAPIの仕様である「スキーマ」を定義することから始まります。</p>
<h4 id="1-図解graphqlの処理フロー">1. 図解：GraphQLの処理フロー</h4>
<p><em>上図のように、クライアントからのクエリは単一エンドポイントで受け付けられ、リゾルバが対応する処理を実行し、データを返します。</em></p>
<h4 id="2-サーバーサイド実装-with-apollo-server">2. サーバーサイド実装 (with Apollo Server)</h4>
<p>まずは必要なライブラリをインストールします。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install @apollo/server graphql
</span></span></code></pre></td></tr></table>
</div>
</div><p>次に、APIの仕様をスキーマ定義言語（SDL）で記述します。</p>
<p><strong><code>schema.graphql</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-graphql" data-lang="graphql"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">User</span> {
</span></span><span style="display:flex;"><span>  id: <span style="color:#a6e22e">ID</span>!
</span></span><span style="display:flex;"><span>  name: <span style="color:#a6e22e">String</span>!
</span></span><span style="display:flex;"><span>  email: <span style="color:#a6e22e">String</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Query</span> {
</span></span><span style="display:flex;"><span>  getUser(id: <span style="color:#a6e22e">ID</span>!): <span style="color:#a6e22e">User</span>
</span></span><span style="display:flex;"><span>  listUsers: [<span style="color:#a6e22e">User</span>!]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Mutation</span> {
</span></span><span style="display:flex;"><span>  createUser(name: <span style="color:#a6e22e">String</span>!, email: <span style="color:#a6e22e">String</span>): <span style="color:#a6e22e">User</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>このスキーマに基づいて、実際のデータ操作を行う「リゾルバ」を実装します。</p>
<p><strong><code>server/resolvers.ts</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// サンプルのため、DBの代わりにインメモリ配列を使用
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">users</span> <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Alice&#39;</span>, <span style="color:#a6e22e">email</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;alice@example.com&#39;</span> },
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;2&#39;</span>, <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Bob&#39;</span>, <span style="color:#a6e22e">email</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;bob@example.com&#39;</span> },
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">resolvers</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Query</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">getUser</span><span style="color:#f92672">:</span> (<span style="color:#a6e22e">_</span>: <span style="color:#66d9ef">any</span>, { <span style="color:#a6e22e">id</span> }<span style="color:#f92672">:</span> { <span style="color:#a6e22e">id</span>: <span style="color:#66d9ef">string</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">find</span>(<span style="color:#a6e22e">user</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">id</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">id</span>) <span style="color:#f92672">||</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">listUsers</span><span style="color:#f92672">:</span> () <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">users</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Mutation</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">createUser</span><span style="color:#f92672">:</span> (<span style="color:#a6e22e">_</span>: <span style="color:#66d9ef">any</span>, { <span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">email</span> }<span style="color:#f92672">:</span> { <span style="color:#a6e22e">name</span>: <span style="color:#66d9ef">string</span>; <span style="color:#a6e22e">email?</span>: <span style="color:#66d9ef">string</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newUser</span> <span style="color:#f92672">=</span> { <span style="color:#a6e22e">id</span>: <span style="color:#66d9ef">String</span>(<span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>), <span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">email</span> };
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">newUser</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">newUser</span>;
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></td></tr></table>
</div>
</div><p>最後に、これらを統合してApollo Serverを起動します。Next.jsのAPI Routesを使うと以下のようになります。</p>
<p><strong><code>pages/api/graphql.ts</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">ApolloServer</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@apollo/server&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">startServerAndCreateNextHandler</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@as-integrations/next&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">gql</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;graphql-tag&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">resolvers</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;../../server/resolvers&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">readFileSync</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;fs&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">path</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;path&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// スキーマファイルを読み込む
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">typeDefs</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">gql</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">readFileSync</span>(<span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">join</span>(<span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">cwd</span>(), <span style="color:#e6db74">&#39;schema.graphql&#39;</span>), <span style="color:#e6db74">&#39;utf8&#39;</span>)
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">server</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">ApolloServer</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">typeDefs</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">resolvers</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">startServerAndCreateNextHandler</span>(<span style="color:#a6e22e">server</span>);
</span></span></code></pre></td></tr></table>
</div>
</div><p>これで <code>/api/graphql</code> エンドポイントが完成しました。</p>
<h4 id="3-クライアントサイド実装-with-apollo-client">3. クライアントサイド実装 (with Apollo Client)</h4>
<p>クライアント側では、Apollo Clientを使ってサーバーと通信します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install @apollo/client graphql
</span></span></code></pre></td></tr></table>
</div>
</div><p>まず、Apollo Clientのインスタンスを作成し、Reactアプリケーション全体を<code>&lt;ApolloProvider&gt;</code>でラップします。</p>
<p><strong><code>pages/_app.tsx</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-tsx" data-lang="tsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">ApolloClient</span>, <span style="color:#a6e22e">InMemoryCache</span>, <span style="color:#a6e22e">ApolloProvider</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@apollo/client&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">client</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">ApolloClient</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">uri</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;/api/graphql&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">cache</span>: <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">InMemoryCache</span>(),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">MyApp</span>({ <span style="color:#a6e22e">Component</span>, <span style="color:#a6e22e">pageProps</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">ApolloProvider</span> <span style="color:#a6e22e">client</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">client</span>}&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">Component</span> {<span style="color:#a6e22e">...pageProps</span>} /&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">ApolloProvider</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">MyApp</span>;
</span></span></code></pre></td></tr></table>
</div>
</div><p>コンポーネント内では <code>useQuery</code> フックを使ってデータを取得します。</p>
<p><strong><code>components/UserList.tsx</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-tsx" data-lang="tsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useQuery</span>, <span style="color:#a6e22e">gql</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@apollo/client&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">LIST_USERS_QUERY</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">gql</span><span style="color:#e6db74">`
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  query ListUsers {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    listUsers {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      id
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      name
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    }
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  }
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">UserList() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">loading</span>, <span style="color:#a6e22e">error</span>, <span style="color:#a6e22e">data</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">useQuery</span>(<span style="color:#a6e22e">LIST_USERS_QUERY</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">loading</span>) <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">p</span>&gt;<span style="color:#a6e22e">Loading</span>...&lt;/<span style="color:#f92672">p</span>&gt;;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">error</span>) <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">p</span>&gt;Error<span style="color:#f92672">:</span> {<span style="color:#a6e22e">error</span>.<span style="color:#a6e22e">message</span>}&lt;/<span style="color:#f92672">p</span>&gt;;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#a6e22e">data</span>.<span style="color:#a6e22e">listUsers</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">user</span>: <span style="color:#66d9ef">any</span>) <span style="color:#f92672">=&gt;</span> ( <span style="color:#75715e">// ここ！ dataやuserの型がanyになっている
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        &lt;<span style="color:#f92672">li</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">id</span>}&gt;{<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">name</span>}&lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>      ))}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="4-型安全性の実現-with-graphql-code-generator">4. 型安全性の実現 (with GraphQL Code Generator)</h4>
<p>上記のクライアントコードでは、<code>data</code> や <code>user</code> の型が <code>any</code> になっており、型安全ではありません。これを解決するのが <strong>GraphQL Code Generator</strong> です。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install -D @graphql-codegen/cli @graphql-codegen/client-preset
</span></span></code></pre></td></tr></table>
</div>
</div><p>設定ファイルを作成します。</p>
<p><strong><code>codegen.yml</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">schema</span>: <span style="color:#ae81ff">http://localhost:3000/api/graphql</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">documents</span>: <span style="color:#e6db74">&#34;./**/*.tsx&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">generates</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">./generated/graphql.ts</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">plugins</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">typescript</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">typescript-operations</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">typescript-react-apollo</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>package.json</code> にスクリプトを追加し、実行します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#e6db74">&#34;scripts&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;codegen&#34;</span>: <span style="color:#e6db74">&#34;graphql-codegen&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm run codegen
</span></span></code></pre></td></tr></table>
</div>
</div><p>これにより、クエリに対応した型定義とカスタムフックが <code>generated/graphql.ts</code> に自動生成されます。これを使うと、クライアントコードは以下のようになります。</p>
<p><strong><code>components/UserList.tsx</code> (after codegen)</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-tsx" data-lang="tsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useListUsersQuery</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;../generated/graphql&#39;</span>; <span style="color:#75715e">// 自動生成されたフックを利用
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">UserList() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// useQueryの代わりに、型付けされたカスタムフックを使用
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">loading</span>, <span style="color:#a6e22e">error</span>, <span style="color:#a6e22e">data</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">useListUsersQuery</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">loading</span>) <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">p</span>&gt;<span style="color:#a6e22e">Loading</span>...&lt;/<span style="color:#f92672">p</span>&gt;;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">error</span>) <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">p</span>&gt;Error<span style="color:#f92672">:</span> {<span style="color:#a6e22e">error</span>.<span style="color:#a6e22e">message</span>}&lt;/<span style="color:#f92672">p</span>&gt;;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#75715e">/* data.listUsers や user.name にアクセスすると、型補完が効く！ */</span>}
</span></span><span style="display:flex;"><span>      {<span style="color:#a6e22e">data</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">listUsers</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">user</span>) <span style="color:#f92672">=&gt;</span> (
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">li</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">id</span>}&gt;{<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">name</span>}&lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>      ))}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>これで、フロントエンドでも完全に型安全な開発が可能になりました。しかし、この設定に至るまでには複数のライブラリのインストールと設定ファイルの記述、そして <code>codegen</code> コマンドの実行というステップが必要でした。</p>
<hr>
<h3 id="trpc型推論による究極のdx">tRPC：型推論による究極のDX</h3>
<p>tRPCは、TypeScriptの型推論を最大限に活用し、上記のようなスキーマ定義やコード生成を一切不要にします。</p>
<h4 id="1-図解trpcの処理フロー">1. 図解：tRPCの処理フロー</h4>
<p><em>tRPCの核心は、サーバーのRouter定義からエクスポートされた <code>AppRouter</code> 型を、クライアントが直接インポートして利用する点にあります。これにより、型情報がシームレスに共有されます。</em></p>
<h4 id="2-サーバーサイド実装-routerの定義">2. サーバーサイド実装 (Routerの定義)</h4>
<p>まずは必要なライブラリをインストールします。入力バリデーションのために <code>zod</code> を使うのが一般的です。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
</span></span></code></pre></td></tr></table>
</div>
</div><p>次に、APIのロジックを「ルーター」として定義します。これはGraphQLのリゾルバとスキーマを一緒にしたようなものです。</p>
<p><strong><code>server/trpc.ts</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">initTRPC</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@trpc/server&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">initTRPC</span>.<span style="color:#a6e22e">create</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">router</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">router</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">publicProcedure</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">procedure</span>;
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong><code>server/routers/_app.ts</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;zod&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">router</span>, <span style="color:#a6e22e">publicProcedure</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;../trpc&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// サンプルのため、DBの代わりにインメモリ配列を使用
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">users</span> <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Alice&#39;</span>, <span style="color:#a6e22e">email</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;alice@example.com&#39;</span> },
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;2&#39;</span>, <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Bob&#39;</span>, <span style="color:#a6e22e">email</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;bob@example.com&#39;</span> },
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">appRouter</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">router</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// クエリ (データ取得)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">getUser</span>: <span style="color:#66d9ef">publicProcedure</span>
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">input</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({ <span style="color:#a6e22e">id</span>: <span style="color:#66d9ef">z.string</span>() })) <span style="color:#75715e">// 入力の型をzodで定義
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    .<span style="color:#a6e22e">query</span>(({ <span style="color:#a6e22e">input</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// input は { id: string } 型と推論される
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">find</span>(<span style="color:#a6e22e">user</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">id</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">input</span>.<span style="color:#a6e22e">id</span>) <span style="color:#f92672">||</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">listUsers</span>: <span style="color:#66d9ef">publicProcedure</span>
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">query</span>(() <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">users</span>;
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ミューテーション (データ変更)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">createUser</span>: <span style="color:#66d9ef">publicProcedure</span>
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">input</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({ <span style="color:#a6e22e">name</span>: <span style="color:#66d9ef">z.string</span>(), <span style="color:#a6e22e">email</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">optional</span>() }))
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">mutation</span>(({ <span style="color:#a6e22e">input</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// input は { name: string, email?: string } 型と推論される
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newUser</span> <span style="color:#f92672">=</span> { <span style="color:#a6e22e">id</span>: <span style="color:#66d9ef">String</span>(<span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>), ...<span style="color:#a6e22e">input</span> };
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">newUser</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">newUser</span>;
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// この型をエクスポートするのがtRPCの魔法の鍵
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">type</span> <span style="color:#a6e22e">AppRouter</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">appRouter</span>;
</span></span></code></pre></td></tr></table>
</div>
</div><p>最後に、Next.jsのAPI RoutesでtRPCサーバーを公開します。</p>
<p><strong><code>pages/api/trpc/[trpc].ts</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createNextApiHandler</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@trpc/server/adapters/next&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">appRouter</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;../../../server/routers/_app&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">createNextApiHandler</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">router</span>: <span style="color:#66d9ef">appRouter</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">createContext</span><span style="color:#f92672">:</span> () <span style="color:#f92672">=&gt;</span> ({}), <span style="color:#75715e">// 必要に応じてDB接続などを渡す
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>});
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="3-クライアントサイド実装-with-react-query">3. クライアントサイド実装 (with React Query)</h4>
<p>クライアント側の設定は非常にシンプルです。まず、tRPCクライアントを作成します。</p>
<p><strong><code>utils/trpc.ts</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">httpBatchLink</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@trpc/client&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createTRPCNext</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@trpc/next&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#66d9ef">type</span> { <span style="color:#a6e22e">AppRouter</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;../server/routers/_app&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getBaseUrl() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span> window <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;undefined&#39;</span>) <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;&#39;</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ... (サーバーサイドでのURL解決)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">return</span> <span style="color:#e6db74">`http://localhost:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">PORT</span> <span style="color:#f92672">??</span> <span style="color:#ae81ff">3000</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">trpc</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createTRPCNext</span>&lt;<span style="color:#f92672">AppRouter</span>&gt;({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">config() {</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">links</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">httpBatchLink</span>({
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">url</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">getBaseUrl</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">/api/trpc`</span>,
</span></span><span style="display:flex;"><span>        }),
</span></span><span style="display:flex;"><span>      ],
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ssr</span>: <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>AppRouter</code> 型をサーバーから直接インポートしている点に注目してください。</p>
<p><code>_app.tsx</code> をtRPCでラップします。</p>
<p><strong><code>pages/_app.tsx</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-tsx" data-lang="tsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">trpc</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;../utils/trpc&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">MyApp</span>({ <span style="color:#a6e22e">Component</span>, <span style="color:#a6e22e">pageProps</span> }) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">Component</span> {<span style="color:#a6e22e">...pageProps</span>} /&gt;;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">trpc</span>.<span style="color:#a6e22e">withTRPC</span>(<span style="color:#a6e22e">MyApp</span>);
</span></span></code></pre></td></tr></table>
</div>
</div><p>これで準備完了です。コンポーネントからAPIを呼び出してみましょう。</p>
<p><strong><code>components/UserList.tsx</code></strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-tsx" data-lang="tsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">trpc</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;../utils/trpc&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">UserList() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// `trpc`オブジェクトから直接プロシージャを呼び出す！
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">data</span>: <span style="color:#66d9ef">users</span>, <span style="color:#a6e22e">isLoading</span>, <span style="color:#a6e22e">error</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">trpc</span>.<span style="color:#a6e22e">listUsers</span>.<span style="color:#a6e22e">useQuery</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">isLoading</span>) <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">p</span>&gt;<span style="color:#a6e22e">Loading</span>...&lt;/<span style="color:#f92672">p</span>&gt;;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">error</span>) <span style="color:#66d9ef">return</span> &lt;<span style="color:#f92672">p</span>&gt;Error<span style="color:#f92672">:</span> {<span style="color:#a6e22e">error</span>.<span style="color:#a6e22e">message</span>}&lt;/<span style="color:#f92672">p</span>&gt;;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#75715e">/* 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">        users の型は (typeof users)[] と完全に推論される！
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">        user.id や user.name へのアクセスも型安全で、VSCodeの補完が完璧に機能する。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">      */</span>}
</span></span><span style="display:flex;"><span>      {<span style="color:#a6e22e">users</span><span style="color:#f92672">?</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">user</span>) <span style="color:#f92672">=&gt;</span> (
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">li</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">id</span>}&gt;{<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">name</span>}&lt;/<span style="color:#f92672">li</span>&gt;
</span></span><span style="display:flex;"><span>      ))}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">ul</span>&gt;
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>どうでしょうか。GraphQLで必要だったスキーマ定義、<code>gql</code>タグ、コード生成といったステップが一切ありません。サーバーの <code>appRouter</code> にプロシージャを追加・変更すれば、即座にクライアントの <code>trpc</code> オブジェクトに反映され、TypeScriptコンパイラが型チェックを行います。まるでバックエンドの関数を直接呼び出しているかのような、驚異的な開発体験です。</p>
<h2 id="メリットデメリット徹底比較">メリット・デメリット徹底比較</h2>
<p>実装例から見えてきた両者の特性を、メリット・デメリットとして整理し、比較表にまとめます。</p>
<h3 id="graphql">GraphQL</h3>
<h4 id="メリット">メリット</h4>
<ul>
<li><strong>言語・環境非依存</strong>: サーバーがGo、Python、Javaで、クライアントがTypeScript、Swift、Kotlinであっても連携可能です。これはGraphQLの最大の強みであり、多様な技術スタックが混在する環境では他に代えがたい利点です。</li>
<li><strong>強力なエコシステム</strong>: ApolloやRelayといった高機能なクライアントライブラリ、スキーマ管理ツール、GraphiQLのようなインタラクティブな開発コンソールなど、成熟したエコシステムが存在します。</li>
<li><strong>公開APIとしての実績</strong>: GitHub APIやShopify APIなど、多くの大規模な公開APIで採用されています。不特定多数のサードパーティ開発者にAPIを提供する際に、スキーマが厳密な契約書として機能します。</li>
<li><strong>スキーマフェデレーション</strong>: 複数のマイクロサービスを一つのGraphQL APIゲートウェイに統合する「Apollo Federation」のような仕組みがあり、大規模な分散システムと非常に相性が良いです。</li>
</ul>
<h4 id="デメリット">デメリット</h4>
<ul>
<li><strong>学習コスト</strong>: クエリ言語、スキーマ、リゾルバ、型システム、キャッシュ戦略など、学ぶべき独自の概念が多く、初学者にとってはハードルが高いです。</li>
<li><strong>複雑なセットアップ</strong>: サーバー、クライアント、コードジェネレーターなど、開発を始めるまでに多くの設定が必要です。</li>
<li><strong>パフォーマンス問題の潜在リスク</strong>: クライアントが自由にクエリを組み立てられるため、意図せず重いクエリ（N+1問題など）が実行される可能性があります。サーバーサイドでDataLoaderのような対策を講じる必要があります。</li>
<li><strong>キャッシュの複雑さ</strong>: 単一エンドポイントへのPOSTリクエストが基本となるため、RESTのような単純なHTTPレイヤーでのキャッシュが効きにくく、クライアントサイドでの正規化キャッシュなど高度な戦略が求められます。</li>
</ul>
<h3 id="trpc">tRPC</h3>
<h4 id="メリット-1">メリット</h4>
<ul>
<li><strong>究極の型安全性と開発体験 (DX)</strong>: コード生成が不要で、リアルタイムに型情報がフロントエンドとバックエンドで共有されます。サーバーのコードを変更した瞬間に、クライアントのコードで型エラーが検知されるため、バグを未然に防ぎ、リファクタリングも安全に行えます。オートコンプリートも完璧です。</li>
<li><strong>圧倒的なシンプルさと低学習コスト</strong>: APIの定義は、実質的にTypeScriptの関数をエクスポートする感覚に近いです。RESTやGraphQLのような独自の規約を学ぶ必要がほとんどなく、すぐに開発を始められます。</li>
<li><strong>軽量で高速</strong>: APIスキーマをパースしたり、複雑なクエリを解釈したりする必要がないため、ランタイムのオーバーヘッドが非常に小さいです。</li>
<li><strong>フルスタックTypeScriptとの最高の相性</strong>: Next.js, Remix, SvelteKitなどのフレームワークや、Turborepo, Nxのようなモノレポツールと組み合わせることで、その真価を最大限に発揮します。</li>
</ul>
<h4 id="デメリット-1">デメリット</h4>
<ul>
<li><strong>TypeScriptへの強い依存</strong>: サーバーとクライアントがTypeScript（またはJavaScript）であることが大前提です。ネイティブモバイルアプリや、異なる言語で書かれたバックエンドサービスと直接連携することはできません。これがtRPCの最大の制約です。</li>
<li><strong>公開APIには不向き</strong>: tRPCのプロトコルは内部利用を前提としており、不特定多数の外部開発者にAPIを提供するには適していません。（HTTP経由で呼び出すことは可能ですが、型安全性のメリットは失われます）。</li>
<li><strong>発展途上のエコシステム</strong>: GraphQLに比べると、歴史が浅く、サードパーティ製のツールやライブラリはまだ少ないです。ただし、コミュニティは非常に活発で、急速に成長しています。</li>
</ul>
<h3 id="比較まとめ表">比較まとめ表</h3>
<table>
  <thead>
      <tr>
          <th style="text-align: left">項目</th>
          <th style="text-align: left">GraphQL</th>
          <th style="text-align: left">tRPC</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>主な用途</strong></td>
          <td style="text-align: left">公開API, モバイルアプリ, マイクロサービス連携</td>
          <td style="text-align: left">フルスタックTypeScriptアプリ, 社内ツール</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>型安全性</strong></td>
          <td style="text-align: left">スキーマとコード生成で実現</td>
          <td style="text-align: left">TypeScriptの型推論でネイティブに実現</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>開発体験 (DX)</strong></td>
          <td style="text-align: left">良好（ツールが多い）</td>
          <td style="text-align: left"><strong>最高</strong>（シンプル、即時性、補完）</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>学習コスト</strong></td>
          <td style="text-align: left">高い</td>
          <td style="text-align: left"><strong>低い</strong></td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>セットアップ</strong></td>
          <td style="text-align: left">複雑</td>
          <td style="text-align: left"><strong>シンプル</strong></td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>言語依存性</strong></td>
          <td style="text-align: left"><strong>なし</strong></td>
          <td style="text-align: left">TypeScriptに強く依存</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>エコシステム</strong></td>
          <td style="text-align: left"><strong>非常に成熟</strong></td>
          <td style="text-align: left">急速に成長中</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>コード生成</strong></td>
          <td style="text-align: left">必須</td>
          <td style="text-align: left"><strong>不要</strong></td>
      </tr>
  </tbody>
</table>
<h2 id="現場で使える実践的なtips2026年のapi選定基準">現場で使える実践的なTips：2026年のAPI選定基準</h2>
<p>さて、これまでの比較を踏まえ、この記事の核心である「2026年を見据えたAPI選定基準」を、具体的なシナリオベースで提言します。</p>
<h3 id="あなたのプロジェクトに最適なのはどっち">あなたのプロジェクトに最適なのはどっち？</h3>
<h4 id="ケース1-nextjsでwebアプリケーションを新規開発する">ケース1: Next.jsでWebアプリケーションを新規開発する</h4>
<ul>
<li><strong>チーム</strong>: フロントエンドもバックエンドもTypeScriptで開発する。</li>
<li><strong>ユーザー</strong>: 主に自社のWebフロントエンド。</li>
<li><strong>結論</strong>: <strong>tRPCを第一候補として強く推奨します。</strong></li>
<li><strong>理由</strong>: 開発速度、保守性、そして何より開発体験が段違いです。APIの仕様変更に起因するバグを撲滅でき、チームの生産性を最大化できます。モノレポ構成にすれば、型定義の共有はさらに容易になります。2026年、この領域ではtRPCがデファクトスタンダードになっている可能性が非常に高いでしょう。</li>
</ul>
<h4 id="ケース2-不特定多数の開発者にapiを公開するpublic-api">ケース2: 不特定多数の開発者にAPIを公開する（Public API）</h4>
<ul>
<li><strong>チーム</strong>: バックエンドはGo、クライアントはWeb, Pythonスクリプト, etc&hellip;</li>
<li><strong>ユーザー</strong>: 社外のパートナー企業や一般の開発者。</li>
<li><strong>結論</strong>: <strong>GraphQL一択です。</strong></li>
<li><strong>理由</strong>: 言語非依存という最大の強みが活きます。厳密なスキーマは、それ自体が仕様書（ドキュメント）となり、APIの利用者に安定したインターフェースを提供します。エコシステムが成熟しているため、様々な言語でクライアントライブラリが利用できるのも大きな利点です。</li>
</ul>
<h4 id="ケース3-webiosandroidの3つのクライアントを持つサービスを開発する">ケース3: Web、iOS、Androidの3つのクライアントを持つサービスを開発する</h4>
<ul>
<li><strong>チーム</strong>: バックエンドはNode.js(TS)、フロントはNext.js(TS)、iOSはSwift、AndroidはKotlin。</li>
<li><strong>結論</strong>: <strong>GraphQLが最有力候補です。</strong></li>
<li><strong>理由</strong>: このシナリオも、言語の壁を越える必要があるためGraphQLの独壇場です。単一のGraphQLエンドポイントを用意すれば、各クライアントチームはそれぞれの言語用のコード生成ツールを使い、型安全な開発を進めることができます。</li>
</ul>
<h4 id="ケース4-複数のマイクロサービスを集約するbff-backend-for-frontend-を構築する">ケース4: 複数のマイクロサービスを集約するBFF (Backend for Frontend) を構築する</h4>
<ul>
<li><strong>チーム</strong>: 各マイクロサービスは独立して開発されている（言語もバラバラ）。</li>
<li><strong>ユーザー</strong>: フロントエンドチーム。</li>
<li><strong>結論</strong>: <strong>GraphQL (Apollo Federation) が非常に強力な選択肢です。</strong></li>
<li><strong>理由</strong>: 各マイクロサービスのAPI（RESTやgRPCなど）を、BFF層のGraphQLリゾルバでラッピングし、フロントエンドからは単一のデータグラフとして扱えるようにします。これにより、フロントエンドは複雑なマイクロサービスの構成を意識することなく、効率的にデータを取得できます。</li>
</ul>
<h3 id="ハイブリッドアプローチという選択肢">ハイブリッドアプローチという選択肢</h3>
<p>「うちのサービスは、社内Webアプリがメインだけど、一部の機能だけパートナー企業にAPIとして公開したい…」</p>
<p>このような場合、どちらか一方を選ぶ必要はありません。<strong>tRPCとGraphQLを共存させる</strong>ハイブリッドアプローチが有効です。</p>
<p>例えばNext.jsでは、以下のようにAPI Routesを分けることで簡単に実現できます。</p>
<ul>
<li><code>/api/trpc/[trpc]</code>: 社内Webアプリ用のメインAPI。tRPCで高速開発。</li>
<li><code>/api/graphql</code>: パートナー企業向けの公開API。GraphQLで安定したインターフェースを提供。</li>
</ul>
<p>このアプローチにより、それぞれの技術の「おいしいところ」だけを享受することができます。日々の開発はtRPCの圧倒的なDXの恩恵を受けつつ、疎結合性が求められる部分にはGraphQLの堅牢さを適用する。これは非常に現実的で強力な戦略です。</p>
<h2 id="まとめ">まとめ</h2>
<p>GraphQLとtRPCは、API開発における異なる課題を解決するために生まれた技術です。両者は単なる優劣で語れるものではなく、<strong>適材適所</strong>で使い分けるべき関係にあります。</p>
<ul>
<li><strong>tRPC</strong>は、<strong>フルスタックTypeScript</strong>という特定の、しかし非常に強力なエコシステムの中で、開発体験と型安全性を極限まで高めるための**「特化型」**ソリューションです。</li>
<li><strong>GraphQL</strong>は、言語や環境の壁を越え、多様なクライアントとサーバーを繋ぐための**「汎用型」**ソリューションです。</li>
</ul>
<p>2026年のAPI開発のトレンドを予測するならば、以下のようになるでしょう。</p>
<blockquote>
<p><strong>フルスタックTypeScriptで完結するプロジェクトではtRPCが主流となり、開発の高速化と品質向上を牽引する。一方で、マイクロサービス連携やPublic APIなど、システムの境界を越えるインターフェースとしては、引き続きGraphQLがその堅牢性と汎用性で重要な役割を担い続ける。</strong></p></blockquote>
<p>最終的にあなたが行うべき技術選定は、流行りの技術に飛びつくことではありません。あなたのプロジェクトが直面している課題は何か、APIの利用者は誰か、開発チームの構成はどうなっているか——これらの問いに真摯に向き合い、それぞれの技術が持つ本質的な価値を理解した上で、最適なツールを選択することです。</p>
<p>この記事が、あなたの次なる一歩を力強く後押しできれば幸いです。まずは小さなサイドプロジェクトでtRPCを試してみてください。きっと、そのあまりの快適さに驚かされるはずです。</p>
]]></content:encoded>
      <category>Backend</category>
      <category>GraphQL</category>
      <category>tRPC</category>
      <category>API</category>
    </item>
  </channel>
</rss>
