はじめに
こんにちは!@junya-tadと申します。
普段は開発とは無縁な仕事をしていますが、プログラムを作ることにはまり2021年ころから本格的に勉強を始め、最近は特にWeb3やAI領域に興味があり、個人開発やプロジェクトに参加しています。フロントエンド(主にTypeScript)が得意ですが、Python、Rust、C++、Solidityとも戯れています
Xアカウントもありますので、興味あればフォローお願いします🙏
JUNYA⚡| thinkactive-design
さて前回の記事「結局Next.js + prisma + tRPC + Supabaseのわかりやすい初期設定記事ってないような?!だから作る💪Part①では、Next.jsとSupabaseの設定箇所まで進めました!
今回はPrisma + tRPCの設定をしていきたいと思います。
今回の実装手順 Part②
3. Prismaのセットアップ
- パッケージのインストール&初期化
注意: ルートディレクトリにprismaディレクトリと.envファイルが作成されます。本設定では.envファイルをNext.jsの環境変数と分けるため、prismaディレクトリに移動します。
pnpm add -D prisma pnpm dlx prisma pnpm dlx prisma init
- 環境変数の設定
prismaディレクトリ内の.envファイルを開き、DATABASE_URLにSupabaseの接続情報を入力します:DATABASE_URL="postgresql://postgres.byratbssekchbxkawmtr:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres"
- Prismaスキーマの設定
/prisma/schema.prismaファイルを以下のように編集します:generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id Int @id @default(autoincrement()) auth_id String @unique // Supabaseの認証IDと紐づけるためのフィールド email String @unique name String? }
- migrationの作成
pnpm dlx prisma migrate dev --name ini
簡単でしたね♪ Prismaの設定は以上です!
4. tRPCのセットアップ
tRPCは型安全なAPI通信を実現する強力なツールですが、設定が多岐にわたります。以下に、わかりやすく段階的に説明していきます。
- tRPC及び関連パッケージのインストール
これらのパッケージは、tRPCの基本機能(@trpc/server, @trpc/client)、Next.jsとの統合(@trpc/next)、React Queryとの連携(@trpc/react-query, @tanstack/react-query)、そして入力バリデーション(zod)を提供します。@nextや@latestタグを使用することで、最新バージョンをインストールしています。pnpm add @trpc/server@next @trpc/client@next @trpc/react-query@next @trpc/next@next @tanstack/react-query@latest zod
- APIルートの設定(app/api/trpc/[trpc]route.ts)
このファイルはtRPCのエンドポイントを設定します。fetchRequestHandlerを使用して、HTTPリクエストをtRPCの操作に変換します。'/api/trpc'というエンドポイントを指定することで、全てのtRPC操作がこのパスを通じて行われます。appRouterは後ほど定義する全てのtRPC操作を含むルーターです。createContextは今回は空のオブジェクトを返していますが、ここで認証情報などのコンテキストを追加することができます。このファイルはtRPCのエンドポイントを設定します。fetchRequestHandlerを使用して、HTTPリクエストをtRPCの操作に変換します。import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { appRouter } from '@/server'; const handler = (req: Request) => fetchRequestHandler({ endpoint: '/api/trpc', // tRPCのエンドポイントを指定 req, router: appRouter, // 定義したappRouterを使用 createContext: () => ({}), // コンテキストを作成(認証情報などを追加可能) }); export { handler as GET, handler as POST };
- データベース接続の設定(server/db.ts)
このファイルでPrismaClientのインスタンスを作成し、エクスポートすることで、アプリケーション全体で一貫したデータベース接続を使用できます。これにより、コネクションプールの効率的な管理や、トランザクションの一貫性を保つことができます。PrismaClientのインスタンスを作成し、エクスポートします。これにより、tRPCのルーターからデータベースにアクセスできるようになります。import { PrismaClient } from "@prisma/client" export const db = new PrismaClient();
- tRPCの初期化(server/trpc.ts)
このファイルでtRPCの基本的な設定を行います。initTRPC.create()によってtRPCのインスタンスを作成し、そこからrouterとprocedureを取り出しています。routerは複数のプロシージャをグループ化するために使用され、publicProcedureは認証が不要な公開エンドポイントを定義するために使用されます。後々、認証が必要なプロシージャを追加する際には、ここでそのためのミドルウェアを定義することができます。tRPCの基本的な設定を行います。ルーターとpublicProcedureを定義し、他のファイルで使用できるようエクスポートします。import { initTRPC } from '@trpc/server'; const t = initTRPC.create(); export const router = t.router; // ルーターを作成するための関数 export const publicProcedure = t.procedure; // 公開プロシージャを定義(認証不要のエンドポイント用)
- tRPCルーターの定義(server/index.ts)
このファイルで実際のAPIエンドポイントの処理を定義します。getUsers, getUser, updateUserという3つのエンドポイントを作成しています。それぞれクエリ(データの取得)とミューテーション(データの更新)の例を示しています。zodを使用して入力のバリデーションを行い、型安全性を確保しています。このappRouterをエクスポートすることで、クライアント側でこれらの操作を型安全に呼び出すことができるようになります。ここでAPIエンドポイントの具体的な処理を定義します。ユーザーの取得や更新などの操作を実装します。import { z } from 'zod'; import { db } from './db'; import { publicProcedure, router } from './trpc'; export const appRouter = router({ // 全ユーザーを取得 getUsers: publicProcedure.query(async () => { return db.user.findMany(); }), getUser: publicProcedure.input(z.string()).query(async ({ input }) => { // 特定のユーザーを auth_id で検索 const user = await db.user.findUnique({ where: { auth_id: input }, }); if (!user) { throw new Error('User not found'); } return user; }), updateUser: publicProcedure .input(z.object({ auth_id: z.string(), name: z.string() })) .mutation((req) => { // ユーザー名を更新 return db.user.update({ where: { auth_id: req.input.auth_id }, data: { name: req.input.name }, }); }), }); export type AppRouter = typeof appRouter; // クライアント側で型推論するために型をエクスポート
- クライアントサイドのtRPC設定(utils/trpc/client.ts)
このファイルでは、createTRPCReactを使用してフロントエンドでtRPCを使用するためのクライアントを作成します。型引数にAppRouterを渡すことで、サーバー側で定義したすべての操作の型情報がクライアント側で利用可能になります。これにより、フロントエンドとバックエンドの型の一貫性が保たれ、開発時のエラーを減らすことができます。フロントエンドでtRPCを使用するための設定を行います。createTRPCReactを使用してクライアントを作成します。import { createTRPCReact } from '@trpc/react-query'; import { type AppRouter } from '@/server'; // クライアント側でtRPCを使用するためのフックを作成 export const trpc = createTRPCReact<AppRouter>({});
- tRPCプロバイダーの設定(utils/trpc/Provider.tsx)
このコンポーネントは、アプリケーション全体でtRPCを使用可能にするためのプロバイダーです。QueryClientを作成してReact Queryの設定を行い、tRPCクライアントを初期化します。getBaseUrl関数は、サーバーサイドレンダリング時にも正しくAPIエンドポイントにアクセスできるようにするためのものです。このプロバイダーをアプリケーションのルートで使用することで、どのコンポーネントからでもtRPCクライアントにアクセスできるようになります。アプリケーション全体でtRPCを使用できるようにするためのプロバイダーコンポーネントを作成します。'use client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { httpBatchLink } from '@trpc/client'; import React, { useState } from 'react'; import { trpc } from './client'; export const Provider = ({ children }: { children: React.ReactNode }) => { const getBaseUrl = () => { // サーバーサイドレンダリング時のベースURLを決定 if (typeof window !== 'undefined') { return ''; } if (process.env.VERCEL_URL) { return `https://${process.env.VERCEL_URL}`; } return 'http://localhost:3000'; }; const [queryClient] = useState(() => new QueryClient({})); const [trpcClient] = useState(() => trpc.createClient({ links: [ httpBatchLink({ url: `${getBaseUrl()}/api/trpc`, // tRPCエンドポイントのURLを指定 }), ], }), ); return ( <trpc.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> </trpc.Provider> ); };
- レイアウトへのプロバイダー適用(app/Layout.tsx)
import './globals.css'; import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import React from 'react'; import { Provider as TRPCProvider } from '@/utils/trpc/Provider'; 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}> <TRPCProvider>{children}</TRPCProvider> </body> </html> ); }
作成したtRPCプロバイダーをアプリケーションのルートレイアウトに適用します。これにより、全てのコンポーネントでtRPCが使用可能になります。
お疲れ様でした!次回Part③ではいよいよタイプセーフなNext.js簡単なユーザー情報の編集などが行えるフロントの設定をしていきます🎉
読みやすい記事を目指しています。間違っている箇所やわかりずらい箇所などあればコメントいただけると助かります。