Next.js AppRouter + tRPC
概要
Next.js(v14)のAppRouterでtRPCの導入を行いたいと思います。
シンプルにNext.js + tRPCの環境を構築したい場合は「T3 Stack」のセットアップを利用するのが最速だと思います。
本内容ではtrpcの公式ドキュメントを元に、Next.jsのAppRouterベースでざっくりかんたんに環境構築を行いたいと思います。(ので個人的なメモです。)
ディレクトリ構成
本実装の最終的なディレクトリ構成です。
基本的に公式ドキュメント元に設置していきますが、PageRouterではなくAppRouterのため適所調整しています。
├── app
│ ├── api
│ │ └── trpc
│ │ └── [trpc]
│ │ └── route.ts // APIエンドポイント(ルートハンドラーでのtRPCルーターを提供)
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── ClientSideComponent.tsx
│ └── ServerSideComponent.tsx
├── server // バックエンド側に関する処理
│ ├── index.ts // tRPCルーターの定義
│ └── trpc.ts // tRPCルーターの初期化
└── trpc // tRPCをアプリケーションに提供する処理
├── client.ts // ReactQueryのセットアップ
└── provider.tsx // tRPCプロバイダー
Next.jsアプリ構築
npx create-next-app@latest
- use TypeScript? -> Yes
- use App Router? -> Yes
tRPCパッケージのインストール
npm install @trpc/server@next @trpc/client@next @trpc/react-query@next @trpc/next@next @tanstack/react-query@latest
バックエンド(サーバー)側の設定
tRPCの初期化の設定とメインルーターのインスタンス構築を行います。
インスタンスを作成し必要なヘルパー関数をエクスポートします。
/**
* Initialization of tRPC backend
* @link https://trpc.io/docs/server/routers
*/
import { initTRPC } from '@trpc/server'
const t = initTRPC.create()
/**
* Create router
* @link https://trpc.io/docs/v11/router
*/
export const router = t.router
/**
* Create public procedure
* @link https://trpc.io/docs/v11/procedures
**/
export const procedure = t.procedure
/**
* Create createCallerFactory
* @link https://trpc.io/docs/v11/server/server-side-calls
*/
export const createCallerFactory = t.createCallerFactory
次にメインルーターを作成します。
ここではhello
という文字列を返却するクエリで定義しています。
今回は利用しませんがtRPCはExpressやFastifyをバックエンドとして利用できるように様々なアダプターが提供されています。
import { procedure, router, createCallerFactory } from './trpc'
/**
* router of tRPC-backend
* @link https://trpc.io/docs/quickstart#1-create-a-router-instance
*/
export const appRouter = router({
hello: procedure.query(() => {
return { msg: 'Hello World' }
}),
})
export type AppRouter = typeof appRouter
export const createCaller = createCallerFactory(appRouter)
Next.jsアダプター作成
Next.jsでtRPCルーターを提供するためのAPIハンドラーを作成します。
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '~/server'
/**
* tRPC's HTTP response handler
* @link https://trpc.io/docs/server/adapters/nextjs
*/
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({}),
})
export { handler as GET, handler as POST }
フロントエンドの設定
フロントエンドでクライアントコンポーネントを利用するため、ReactQueryのセットアップを行います。
AppRouter
を利用するtRPCクライアントを生成します。
import { createTRPCReact } from '@trpc/react-query'
import type { AppRouter } from '~/server'
/**
* React hooks from `AppRouter`
* @link https://trpc.io/docs/client/react/setup#2-import-your-approuter
*/
export const trpc = createTRPCReact<AppRouter>()
tRPCクライアントのプロバイダーを作成し、ReactQueryのセットアップをします。
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import React, { ReactNode, useState } from 'react'
import { trpc } from './client'
/**
* tRPC React-Query Provider
* @link https://trpc.io/docs/client/react/setup#4-add-trpc-providers
*/
export default function TrpcProvider({ children }: { children: ReactNode }) {
const [queryClient] = useState(() => new QueryClient({}))
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: 'http://localhost:3000/api/trpc',
}),
],
}),
)
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
)
}
プロバイダーを設定します。
import type { Metadata } from 'next'
+import TrpcProvider from '~/trpc/provider'
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className={inter.className}>
- {children}
+ <TrpcProvider>{children}</TrpcProvider>
</body>
</html>
)
}
クライアントコンポーネント
ClientComponentを作成し、tRPC ReactQueryを利用します。
'use client'
import { FC } from 'react'
import { trpc } from '~/trpc/client'
export const ClientSideComponent: FC = () => {
const { data } = trpc.hello.useQuery()
return (
<div>
<h1>Client Side Component</h1>
<div>{JSON.stringify(data)}</div>
</div>
)
}
作成したコンポーネントをapp/page.tsx
で読み込み動作を確認します。
import { ClientSideComponent } from '~/components/ClientSideComponent'
export default function Home() {
return (
<div>
<ClientSideComponent />
</div>
);
}
サーバーコンポーネント
ServerComponentを作成し、tRPCルーターの呼び出しを行います。
Next.jsでの同じホストサーバーでの呼び出しのため、createCallerFactory
を利用します。
tRPCのメインルーター(server/index.ts
)でcreateCallerFactory
から生成したcreateCaller
を利用してhelloクエリを呼び出しています。
import { FC } from 'react'
import { createCaller } from '~/server'
export const ServerSideComponent: FC = async () => {
const caller = createCaller({})
const data = await caller.hello()
return (
<div>
<h1>Server Side Component</h1>
<div>{JSON.stringify(data)}</div>
</div>
)
}
作成したコンポーネントをapp/page.tsx
で読み込み動作を確認します。
import { ClientSideComponent } from '~/components/ClientSideComponent'
+import { ServerSideComponent } from '~/components/ServerSideComponent'
export default function Home() {
return (
<div>
<ClientSideComponent />
+ <ServerSideComponent />
</div>
);
}
おわりに
tRPCを利用することでサーバー(バックエンド)の実装を行うだけで、型安全なAPIクライアントの実装を行うことができました。
gRPCと名前が似ているため同じような技術かと思いきや実際は大きく異なり
スキーマ作成やコード生成が不要でクライアントとサーバー間のやり取りが簡単にできてしまいます。
フルスタックなNext.jsを構築する際には非常に相性が良さそうです。
今回はAppRouterでの実装を行いたかったため、Next.jsアプリを作成し組み込みましたが
最近注目を集めている「T3 Stack」を用いて開発する方が最適かつ最速であると考えられます。
tRPCは非常に強力なツールでありますが、柔軟性やアーキテクチャに対応できない部分があります。
- TypeScript以外の言語を利用できない
- フロントエンドとバックエンドの開発環境が密に依存してしまう
- APIクライアントを外部連携に提供できない
また、REST API / GraphQL / gRPCとの比較するものでもなく
プロジェクトの将来を考慮し長所を最適化するような考えが必要だと思われます。
私もこれからtPRCのさらに深い知識に触れれるよう様々な機能の実践に取り組みたいと思います。🪷
Discussion