【🔰初心者向け】SNS型webアプリを作成しよう 第4回_Supabaseをセットアップして、投稿をデータベースに保存する【中編】

プログラミング

こんにちは。
今回は投稿機能実装の中編ということで、投稿するAPIを作成していきます。
コードについて詳しく解説しているので、疑問点があれば適宜参照して下さい。

投稿するAPIを作成

APIルートを作成

app/api/post/route.ts を作成し、以下のコードを入力:

import { createClient } from "@supabase/supabase-js";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  try {
    const { user_id, content } = await req.json();
    const access_token = req.headers.get("Authorization")?.split(" ")[1]; // トークン取得

    if (!access_token) {
      console.error("認証トークンがありません");
      return NextResponse.json({ error: "認証が必要です" }, { status: 401 });
    }

    // Supabaseクライアント(anon key を使用)
    const supabaseAuth = createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        auth: { persistSession: false },
        global: { headers: { Authorization: `Bearer ${access_token}` } },
      }
    );

    // 開発中だけデバッグ用のデータを表示する
    if (process.env.NODE_ENV !== "production") {
      console.log("受け取ったデータ:", { user_id, content });
    }

    // `users` テーブルから `clerk_id` を取得
    const { data: existingUser, error: userError } = await supabaseAuth
      .from("users")
      .select("clerk_id")
      .eq("clerk_id", user_id)
      .single();

    if (userError && userError.code !== "PGRST116") { // PGRST116 = データなしエラーなので無視
      console.error("ユーザー検索エラー:", userError.message);
      return NextResponse.json({ error: "ユーザー検索エラーが発生しました。" }, { status: 500 });
    }

    // `users` に `clerk_id` が存在しない場合、新しく追加
    if (!existingUser) {
      const { error: insertError } = await supabaseAuth
        .from("users")
        .insert([{ clerk_id: user_id }]);

      if (insertError) {
        console.error("ユーザー作成エラー:", insertError.message);
        return NextResponse.json({ error: "ユーザー登録に失敗しました。" }, { status: 500 });
      }
    }

    // `post` に投稿を追加
    const { error } = await supabaseAuth.from("post").insert([
      { user_id, content, created_at: new Date().toISOString() }
    ]);

    if (error) {
      console.error("Supabaseエラー:", error.message);
      return NextResponse.json({ error: "投稿に失敗しました。" }, { status: 500 });
    }

    return NextResponse.json({ message: "投稿成功!" }, { status: 200 });
  } catch (err) {
    console.error("サーバーエラー:", err);
    return NextResponse.json({ error: "サーバーエラーが発生しました。" }, { status: 500 });
  }
}

このコードがやること

  1. ユーザーの投稿データを受け取る(user_idとcontent)
  2. ユーザーがちゃんとログインしているかチェック
  3. データベース(Supabase)にユーザーが登録されているか確認いなければ新しく登録
  4. 投稿をデータベースに保存
  5. 成功 or 失敗の結果を返す
コードの意味を詳細に解説(クリックして展開)
import { createClient } from "@supabase/supabase-js";
import { NextResponse } from "next/server";
import するもの役割
createClientSupabase に接続するための関数
NextResponseNext.js の API Routes でレスポンスを作るためのオブジェクト

export async function POST(req: Request) {

この部分は、POSTリクエストを受け取る処理 を作っています。

  • export → この関数(コードのまとまり)を外からも使えるようにする
  • async function → 「時間がかかる処理」をスムーズに動かすためのもの
  • POST → データを送るときに使う リクエストの種類
  • req(リクエスト) → ユーザーが送ってくるデータのこと

💡 リクエストって何?

Webサイトでは、「サーバー(データを管理する場所)とやり取り」 をします。
その時に、「リクエスト」というものを送って「データをください!」とか「このデータを保存して!」と言うことができます。

リクエストの種類:

  • GET → データを「もらう」ためのリクエスト
  • POST → データを「送る」ためのリクエスト
  • PUT → データを「更新する」ためのリクエスト
  • DELETE → データを「削除する」ためのリクエスト

このコードでは POST を使って 「データを送る」API を作っています。


const { user_id, content } = await req.json();

この部分では以下のような処理が行われています。

  • ユーザーが送ったデータを受け取る
  • リクエスト(req) には、送られたデータが入っている
  • req.json() を使うと、データを「使いやすい形」に変えられる
  • そのデータを user_id と content に分けて使えるようにする
  • await をつけると「データが届くまで待つ」

const とは?

const の特徴説明
変更不可一度代入した値を変更できない
再宣言不可同じ名前の変数をもう一度作ることはできない
配列・オブジェクトの中身は変更OKただし、変数自体を別のものにするのはNG
エラーを防ぐ予期しない変更を防ぎ、安全なコードになる

const は「変えられない変数」を作るためのもの
{ user_id, content } は「オブジェクトからデータを取り出す」書き方(分割代入)
このコードは「データを取り出して、変更できない変数を作る」処理


アクセストークンの入手

const access_token = req.headers.get("Authorization")?.split(" ")[1];

これは、リクエストのヘッダーからアクセストークン(認証用のパスワードみたいなもの)を取り出す処理です。

1. req.headers.get(“Authorization”) の意味

req.headers.get(“Authorization”) は、リクエスト(req)の ヘッダー(追加情報) の中から “Authorization” という項目を取り出すコードです。

💡 ヘッダーとは?

リクエストには、ユーザーが送るデータ(req.json() で取得)だけでなく、追加情報(ヘッダー)が含まれます。
その中に “Authorization” という項目があり、ここに ログイン情報(トークン)が入っている ことが多いです。

例えば、Webサイトでログインするときに、サーバーにこんなリクエストが送られます:

POST /api/post
Authorization: Bearer abcd1234efgh5678
Content-Type: application/json

{
  "user_id": "123",
  "content": "こんにちは!"
}

この “Bearer abcd1234efgh5678” という文字列を取り出すのが req.headers.get(“Authorization”) です。

2. ?.(オプショナルチェーン)の意味

req.headers.get("Authorization")?.split(" ")[1];

この ?.「値がない場合にエラーを防ぐ」 ためのものです。
?. を使うと、Authorization が null や undefined でもエラーにならず、そのまま undefined を返してくれます。

要するにreq.headers.get(“Authorization”) が 存在しない場合(ログインしてない場合など)、エラーを防ぐために ?. をつけているわけです。

3. .split(” “)[1] の意味

req.headers.get("Authorization")?.split(" ")[1];

これは、Authorization の文字列を 「Bearer」と「トークン」の2つに分ける ためのコードです。

さっきの Authorization の例:

Authorization: Bearer abcd1234efgh5678

この “Bearer abcd1234efgh5678” を スペース(” “)で区切る と:

["Bearer", "abcd1234efgh5678"]

このような 2つの部分 に分かれます。

📌 split(” “) とは?

文字列を スペース(” “)で分割して配列にする ためのものです。
そして [1] を指定することで:

"abcd1234efgh5678"

という トークン部分だけを取得 できます。


Supabase を使うための「セットアップ」

const supabaseAuth = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
  {
    auth: { persistSession: false },
    global: { headers: { Authorization: `Bearer ${access_token}` } },
  }
);

このコードは何をしている?

  • Supabase への接続を作る(データベースにアクセスするための「ツール」を作る)
  • データベースのURL(住所)を設定する
  • 匿名キー(パスワード)を使って接続する
  • ログイン情報を保存しない設定にする
  • すべてのリクエストに「ログイン情報(トークン)」を追加する

1. createClient() の役割

const supabaseAuth = createClient(...)

これは、Supabase にアクセスできるようにする「ツール(クライアント)」を作る ための関数です。
例えば、Webサイトで「ログイン機能」を使うとき、データベースに「このユーザーはログインできるのか?」と問い合わせる必要があります。
そのために、createClient() を使って データベースと通信できるツール を作ります。

2. process.env.NEXT_PUBLIC_SUPABASE_URL! の意味

process.env.NEXT_PUBLIC_SUPABASE_URL!

これは、Supabase の「どこにあるか」を指定するためのURL です。

📌 Supabase のデータベースは「クラウド上(インターネット上)」にあるので、その住所(URL) を指定しないと使えません。
この process.env.NEXT_PUBLIC_SUPABASE_URL には、その住所(URL)が入っています。

✅ process.env とは?
process.env は「環境変数」と呼ばれるもので、プログラムの設定情報を外部から渡す仕組みです。

なぜ ! がついてるの?
! は「この値は必ず存在するよ!」という意味です。
もし process.env.NEXT_PUBLIC_SUPABASE_URL が undefined(値がない)とエラーになるので、「絶対にある」と教えてあげるために ! をつけています。

3. process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! の意味

process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

これは、Supabase にアクセスするための「パスワード(キー)」 です。
Supabase はデータを守るために、アクセスキー(APIキー) を発行します。
これが NEXT_PUBLIC_SUPABASE_ANON_KEY に入っていて、これを使うことでデータベースに接続できます。

💡 「anon」って何?

「anon」は「anonymous(匿名)」の略で、公開されてもOKなキー のことです。
このキーを使うと、誰でもデータを取得したり、制限のある範囲で書き込んだりできます。
⚠️ ただし、このキーを公開しすぎると危険なので、データの権限設定はしっかりする必要があります!

4. auth: { persistSession: false } の意味

{
  auth: { persistSession: false },
}

これは、「ログインの情報を保存するかどうか」を決める設定です。

  • persistSession: true → ログイン情報を保存する(ページを閉じてもログイン状態を維持する)
  • persistSession: false → ログイン情報を保存しない(毎回ログインが必要)

📌 このコードでは false にしているので、ログイン情報は保存されません。
そのため、毎回リクエストを送るたびに access_token を一緒に送る必要があります。

5. global: { headers: { Authorization: Bearer ${access_token} } } の意味

{
  global: { headers: { Authorization: `Bearer ${access_token}` } }
}

これは、「すべてのリクエストに Authorization ヘッダーをつける」という設定です。

💡 なぜ必要?

Supabase のデータを変更したり取得するには、「この人はログインしている?」とチェックしないといけません。
もしこの設定が ない場合

const { data, error } = await supabaseAuth
  .from("users")
  .select("*")
  .headers({ Authorization: `Bearer ${access_token}` });

このように、supabaseを使うときに毎回 .headers({ Authorization: Bearer ${access_token} }) を書かないといけない😥
そこで、この Authorization ヘッダーにログインの「鍵(トークン)」を入れます。
そうすると、

📌 すべてのリクエストに Authorization ヘッダーが自動で付く!
いちいち headers を設定しなくても、ログイン情報が送られる! 🎉

📌 この部分がやっていること

  • Authorization: Bearer ${access_token}
  • 「Bearer(認証の種類)」+「トークン(鍵)」をセットする」
  • 例えば、トークンが “abcd1234” だったら:
{
  "Authorization": "Bearer abcd1234"
}
  • これをすべてのリクエスト に自動でつけるので、毎回トークンを手動で設定しなくて済む

🎯 つまり、このコードは何をしているのか?

  • Supabase との接続を作る(createClient)
  • 「どのデータベースに接続するのか?」を指定(URL + APIキー)
  • 「ログイン状態をどう管理するか?」を設定(毎回トークンを使う)
  • 「認証情報をどう送るか?」を設定(リクエストごとに Authorization を自動追加)

📌 Supabase を使うための「準備(セットアップ)」をしているコード! 🚀


if (!existingUser) {
      const { error: insertError } = await supabaseAuth
        .from("users")
        .insert([{ clerk_id: user_id }]);

      if (insertError) {
        console.error("ユーザー作成エラー:", insertError.message);
        return NextResponse.json({ error: "ユーザー登録に失敗しました。" }, { status: 500 });
      }
}
記述意味
error: insertErrorSupabase の error を insertError という名前に変えて使う
clerk_id: user_iduser_id の値を clerk_id という名前で保存する


このコードは users テーブルに clerk_id を新しく追加するときにエラーが出た場合、それを表示して処理
している。


const { error } = await supabaseAuth.from("post").insert([
      { user_id, content, created_at: new Date().toISOString() }
]);

{ error } は「オブジェクトの中から error だけを取り出す」書き方
error には、処理が成功すれば null、失敗すればエラーの内容が入る
エラーがあるかどうかを if (error) でチェックして、エラー時は適切に処理する
エラーを「固定している」のではなく、「実行の結果」に応じて error が変わる

APIルートの作成完了

こんな感じで今回は前回のセットアップ内容を反映させるようにコードを書きました。
次回は投稿機能のフロントエンド側を作成して、投稿機能の実装は完了です!
引き続き頑張りましょう!

コメント

タイトルとURLをコピーしました