結局Next.js + prisma + tRPC + Supabaseのわかりやすい初期設定記事ってないような？！だから作る💪Part①

# はじめに
こんにちは！@junya-tadと申します。
普段は開発とは無縁な仕事をしていますが、プログラムを作ることにはまり2021年ころから本格的に勉強を始め、最近は特にWeb3やAI領域に興味があり、個人開発やプロジェクトに参加しています。フロントエンド(主にTypeScript）が得意ですが、Python、Rust、C++、Solidityとも戯れています

Xアカウントもありますので、興味あればフォローお願いします🙏
[JUNYA⚡| thinkactive-design](https://x.com/junya_tad)
# この記事の内容
皆さん、T3スタックという言葉を耳にしたことがありますか？Next.js、TypeScript、tRPCを中心としたこの強力な組み合わせ。そして、最近注目を集めているSupabaseというBaaSプラットフォーム。さらに、Next.jsのappルーター。
これらを全て組み合わせて使おうと思ったとき...
「T3スタックは素晴らしいけど、Supabaseとの連携方法がわからない！😵‍💫」
「Next.jsのappルーターを使いたいけど、T3スタックとの相性は大丈夫？🤔」
「Next.js、Prisma、tRPC、Supabase、appルーター...それぞれの初期設定は見つかるけど、全部まとめた手順がない...😞」
「T3スタックの公式テンプレートはあるけど、Next.jsのappルーターにどう追加すればいいの？😅」
ってなりません？

...ということで自分なりに様々な記事を参考に設定した内容を共有したいなと思いました。
この記事を書くきっかけは、自分自身がT3スタック、Supabase、Next.jsのappルーターを組み合わせる際に苦労した経験です。ネット上には個別の技術に関する情報は豊富にありますが、これらを統合した包括的なガイドが不足していると感じました。そこで、他の開発者の方々の手助けになればと思い、この記事を投稿することにしました。

長い記事になったので3パートに分割します。

# 使用技術の概要
T3スタックとは、TypeScript、tRPC、Tailwind CSSを中心としたモダンなウェブアプリケーション開発のためのテクノロジースタックです。型安全性、開発効率、パフォーマンスを重視したこの組み合わせは、多くの開発者から支持を得ています。本記事では、このT3スタックにSupabaseとNext.jsのappルーターを組み合わせた拡張版を紹介します。

1. Next.js: Reactベースのフルスタックフレームワークで、サーバーサイドレンダリングやルーティングを簡単に実装できます。ファイルシステムベースのルーティングや自動コード分割などの機能により、高速で SEO フレンドリーなアプリケーションを構築できます。

2. Prisma: 型安全なORM（Object-Relational Mapping）ツールで、データベース操作を簡素化し、型の恩恵を最大限に活用できます。自動生成される型定義により、データベース操作に関するエラーを開発時に早期発見できます。

3. tRPC: TypeScriptファーストのRPCフレームワークで、フロントエンドとバックエンド間の型安全な通信を実現します。APIエンドポイントの型定義を自動で共有することで、フロントエンドとバックエンドの一貫性を保ち、開発効率を向上させます。

4. Supabase: オープンソースのFirebase代替サービスで、認証やリアルタイムデータベースなどの機能を提供します。PostgreSQLをベースにしており、高度なデータベース機能と柔軟なスケーリングが可能です。

# 本記事の目的と対象読者
この記事では、テンプレートは使用せずT3スタック（Next.js, TypeScript, tRPC）にSupabaseを組み合わせ、Next.jsのappルーターを使用した環境で、ユーザー認証からユーザー登録情報の更新までの実装方法を解説します。

対象読者：
- 最新のNext.js（app router）を使いたい開発者
- && T3スタック（Next.js, TypeScript, tRPC）に興味がある方
- && Supabaseをデータベースとして使用したい方
- && これらの技術を組み合わせた初期設定方法を知りたい方

# やったこと
とりあえずGithubリンク
[nextjs-prisma-trpc-supabase-starter](https://github.com/junya-thinkactive-d/nextjs-prisma-trpc-supabase-starter)
# 今回の実装手順　Part①
Part①では、Next.jsプロジェクトの作成からSupabaseの設定まで、環境構築の基礎となる部分を詳しく説明します。
## 環境設定
### 1. Next.jsプロジェクトの作成
まずは、Next.jsのプロジェクトを作成します。以下のコマンドを実行し、プロンプトに従って設定を行います：

```bash
pnpm next create-app
```
```bash
What is your project named? your-project-name
Would you like to use TypeScript? Yes (型安全性を確保するため)
Would you like to use ESLint? Yes (コード品質を維持するため)
Would you like to use Tailwind CSS? Yes (効率的なスタイリングのため)
Would you like to use `src/` directory? Yes (コードの整理のため)
Would you like to use App Router? (recommended) Yes (最新の機能を使用するため)
Would you like to customize the default import alias (@/*)? Yes (インポートを簡潔にするため)
```

### 2.Supabaseのセットアップ
Supabaseは強力なBaaSプラットフォームですが、適切に設定することが重要です。以下の手順に従って、プロジェクトの作成から認証の設定まで行います。
1. Supabaseで新しいプロジェクトを作成
    - Supabaseウェブサイトにアクセスし、「Create new project」を選択します。
    - プロジェクト作成時に設定したDatabase Passwordを安全な場所にメモしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566553/baaddf8c-91c4-5e84-93f0-cd47054d4eb8.png)
1. Project Setting -> Database -> Modeを確認
    - プロジェクト作成後、Project Settings → Database → Modeを確認します。
    - Modeが「session」になっていることを確認します。
    - `postgresql://postgres.byratbssekchbxkawmtr:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres`のようになるので、最初にメモしたDatabase Passwordを[YOUR-PASSWORD]の箇所に上書き保存（あとで使う）
![スクリーンショット 2024-07-18 170908.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566553/2abeeaf1-4706-f1af-56c4-4dec3385f1d4.png)
1. Discordの認証の設定
本記事ではDiscord認証を例として使用しています。Discord認証は、開発者コミュニティでよく使われており、設定も比較的簡単です。ただし、Supabaseは他の認証方法（Google、Facebook、メール/パスワードなど）もサポートしているので、プロジェクトの要件に応じて適切な認証方法を選択できます。
    a Disord側の設定：
    - [Discord developer](https://discord.com/developers/applications)にアクセス（アカウントがない人は作成）
    - サインインしたら右上のCreate new Applicationを押し新しいプロジェクトを作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566553/91e4145a-f811-9c74-b38f-b0d9a78e48a8.png)
次に左のタグからOAuth2を選択し、CLIENT IDとCLIENT SEACRETをメモしておく
![スクリーンショット 2024-07-19 134013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566553/d83cea54-21a6-9c36-f5e1-474d2c29355c.png)
※初回CLIENT SEACRETはReset Secretで取得しておく
その下のRedirectsは後ほど設定する

    b Supabase側の設定：
    - Authentication -> Providers -> Discord選択
先ほどのDiscord側のClient IDとClient Secretを入力。
    - Callback URL（for OAuth)をコピーする
![スクリーンショット 2024-07-19 135214.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566553/20b0cf08-1b35-31b4-a36c-271419c25848.png)

    c Discord側の設定（続き）：
    - RedirectsのAdd RedirectからSupabaseからコピーしたCallback URLを入力する
![スクリーンショット 2024-07-19 135507.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566553/2858ca38-0ef4-d38c-d3d9-e8d8b0b56d58.png)
1. Supabase認証の設定
Supabase認証を実装するために、必要なパッケージをインストールし、以下のファイルを作成します：
    ```bash
    pnpm add @supabase/ssr
    ```
    1. utils/client.ts: ブラウザクライアントの作成
        ```javascript
        import { createBrowserClient } from '@supabase/ssr';
        
        export function createClient() {
          return createBrowserClient(
            process.env.NEXT_PUBLIC_SUPABASE_URL!,
            process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
          );
        }
        ```
    1. utils/middleware.ts: セッション更新とルート保護
        ```javascript
        import { createServerClient } from '@supabase/ssr';
        import { type NextRequest, NextResponse } from 'next/server';
        
        export async function updateSession(request: NextRequest) {
          let supabaseResponse = NextResponse.next({
            request,
          });
        
          const supabase = createServerClient(
            process.env.NEXT_PUBLIC_SUPABASE_URL!,
            process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
            {
              cookies: {
                getAll() {
                  return request.cookies.getAll();
                },
                setAll(cookiesToSet) {
                  cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
                  supabaseResponse = NextResponse.next({
                    request,
                  });
                  cookiesToSet.forEach(({ name, value, options }) =>
                    supabaseResponse.cookies.set(name, value, options),
                  );
                },
              },
            },
          );
          const {
            data: { user },
          } = await supabase.auth.getUser();
        
          if (
            !user &&
            request.nextUrl.pathname.startsWith('/customer') &&
            !request.nextUrl.pathname.startsWith('/login') &&
            !request.nextUrl.pathname.startsWith('/auth')
          ) {
            const url = request.nextUrl.clone();
            url.pathname = '/login';
            return NextResponse.redirect(url);
          }
          return supabaseResponse;
        }
        ```
    1. utils/server.ts: サーバーサイドクライアントの作成
        ```javascript
        import { createServerClient } from '@supabase/ssr';
        import { cookies } from 'next/headers';
        
        export function createClient() {
          const cookieStore = cookies();
        
          return createServerClient(
            process.env.NEXT_PUBLIC_SUPABASE_URL!,
            process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
            {
              cookies: {
                getAll() {
                  return cookieStore.getAll();
                },
                setAll(cookiesToSet) {
                  try {
                    cookiesToSet.forEach(({ name, value, options }) =>
                      cookieStore.set(name, value, options),
                    );
                  } catch {
                    // The `setAll` method was called from a Server Component.
                    // This can be ignored if you have middleware refreshing
                    // user sessions.
                  }
                },
              },
            },
          );
        }
        ```
     1. utils/supabase/auth.ts: 認証関連の関数
        ```javascript
        import { createClient } from './client';
        
        const supabase = createClient();
        export async function signInWithDiscord() {
          const { data, error } = await supabase.auth.signInWithOAuth({
            provider: 'discord',
            options: {
              redirectTo: `${window.location.origin}/auth/discord/callback`,
            },
          });
          if (!error) {
            window.location.href = data.url;
          }
        }
        
        export async function signOut() {
          const { error } = await supabase.auth.signOut();
        
          return { error };
        }
        ```
    1. middleware.ts（ルートディレクトリ）: グローバルミドルウェア
        ```javascript
        import { type NextRequest } from 'next/server';
        
        import { updateSession } from '@/utils/supabase/middleware';
        
        export async function middleware(request: NextRequest) {
          return await updateSession(request);
        }
        
        export const config = {
          matcher: [
            '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
          ],
        };
        ```
    1. Supabaseの認証を正常に機能させるには、以下の環境変数を.envファイルに追加する必要があります：
        ```javascript
        NEXT_PUBLIC_SUPABASE_URL=
        NEXT_PUBLIC_SUPABASE_ANON_KEY=
        ```
        これらの値はSupabaseのプロジェクト設定から取得できます
        ![スクリーンショット 2024-07-19 163633.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566553/1eacc980-26e8-117f-4bbe-a3e78d368070.png)
        以上でSupabaseの設定は終わりです。

お疲れ様でした！次回Part②ではいよいよPrismaとtRPCの設定をしていきます🎉

読みやすい記事を目指しています。間違っている箇所やわかりずらい箇所などあればコメントいただけると助かります。