Nextjs App routerでtrpcを試してみた

# はじめに
NextjsでAPIを公開したいなぁって思ったことはありませんか？
その時に認可処理どうしようとかエラーハンドリングどうしましょうと悩んだことはありませんか？
さらに、それらの処理を共通して定義しておきたいと思ったことはありませんか？
そんな時に、ossを探していたらtrpcが使えるかも？と神に教えてもらったので実験してみようと思いました。
この記事はそんな実験的な記事件備忘録です。

# ゴール
- 本記事ではローカル環境でのHTTPリクエストの疎通を目標としています。
- クライアント側の処理はこの記事では取り扱いません。

# 準備
サンプルプロジェクトを用意してください

```bash
npx create-next-app@latest
cd sample-trpc
```

trpcをinstallしてください。
```bash
npm install @trpc/server@next @trpc/client@next
```
https://trpc.io/docs/quickstart#installation

# 手順
### router instanceを作成します。
（ファイルは公式ドキュメント通りに実施していきます。）
下記はtrpcのbackendの初期化になります

```typescript:src/server/trpc.ts
import { initTRPC } from '@trpc/server';
 
/**
 * Initialization of tRPC backend
 * Should be done only once per backend!
 */
const t = initTRPC.create();
 
/**
 * Export reusable router and procedure helpers
 * that can be used throughout the router
 */
export const router = t.router;
export const publicProcedure = t.procedure;
```

### query procedureを設定します
router instanceを初期化しつつ、query procedureを設定しています。
今回だと、helloが対象となります。
```typescript:src/server/index.ts
import { publicProcedure, router } from "./trpc";

export const appRouter = router({
  hello: publicProcedure.query(() => {
    return "Hello World";
  }),
});

export type AppRouter = typeof appRouter;

```

### fetchRequestHandlerを設定する
Nextjs App Routerを使用している場合、fetchRequestHandlerを使用するとAPIを公開できます。

```typescript:src/app/api/trpc/[trpc]/route.ts
import { appRouter } from "@/server";
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';

function handler(req: Request) {
  return fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext: () => ({ ... })
  });
}
export { handler as GET, handler as POST };
```
https://trpc.io/docs/server/adapters/nextjs#route-handlers

### サーバーを立ち上げて疎通してみます。

```bash
npm run dev
curl http://localhost:3000/api/trpc/hello
# {"result":{"data":"Hello World"}}
```

疎通が完了しました。結構簡単にAPIを用意できますね！


# 実験

## Q. fetchRequestHandlerのendopointって、なんで`/api/trpc`なの？

#### 回答
fetchRequestHandlerのendopointはapp routerのパスと連動しているみたいです。

#### 検証

```typescript:src/app/api/v1/[trpc]/route.ts
import { appRouter } from "@/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
function handler(req: Request) {
  return fetchRequestHandler({
    endpoint: "/api/trpc",
    req,
    router: appRouter,
    createContext: () => ({}),
  });
}
export { handler as GET, handler as POST };
```

ファイルパスを変えて、エンドポイントをそのままでcurlを叩いてみましょう
```bash
curl http://localhost:3001/api/trpc/hello
```
結果は、404のhtmlが返却されると思います。

```diff:src/app/api/v1/[trpc]/route.ts
import { appRouter } from "@/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
function handler(req: Request) {
  return fetchRequestHandler({
-    endpoint: "/api/trpc",
+    endpoint: "/api/v1",  // ファイルパスと連動したエンドポイント
    req,
    router: appRouter,
    createContext: () => ({}),
  });
}
export { handler as GET, handler as POST };
```

ファイルパスとエンドポイント合わせてcurlを叩いてみましょう
```bash
curl http://localhost:3001/api/v1/hello
```
しっかりと結果が返ってくると思います。

#### 蛇足
Q. [trpc]をパスから消してみるとどうなるの？
A. 404になります。


## POSTメソッドの設定はどうするの？

#### 回答
queryではなく、mutationを使います。
```diff:src/server/index.ts
import { publicProcedure, router } from "./trpc";

export const appRouter = router({
  hello: publicProcedure.query(() => {
    return "Hello World";
  }),
+  hello2: publicProcedure.mutation(() => {
+    return "Hello World";
+  }),
});

export type AppRouter = typeof appRouter;
```

https://trpc.io/docs/rpc#methods---type-mapping

```bash
curl http://localhost:3001/api/v1/hello2 -X POST -H "Content-Type: application/json"
# {"result":{"data":"Hello World"}}
```

他にも色々と試した結果を備忘録的に残したいけど、長くなってしまうので別の記事にします。


# 感想
NextjsのApp routerでも安心してtrpcを使える！