2024年6月2日現在
現在はSupabaseのクライアント作成に @supabase/supabase-js
を利用しています。
この記事の情報は古く、
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs"
このように @supabase/auth-helpers-nextjs
を利用してSupabaseのクライアントを作成しています。
Supabase公式のリポジトリ
masakinihirota/with-supabase
※オリジナルのリポジトリが更新され、新バージョンになりました、
この記事を書いた時点のリポジトリは保存してあったので、このリポジトリを公開します。
このリポジトリで4つのアクセス方法を確認できます。
Supabaseへの 4種類のアクセス方法
- クライアントから Supabaseのデータを取得する方法
- ルートハンドラーで Supabaseのデータを取得する方法
- Server ActionsでSupabaseのDBを操作する方法
- サーバーコンポーネントで Supabaseのデータを取得する方法
これら4種類のアクセス方法を紹介します。
現時点での Server Actions について (2023年8月31日)
Next.js の Server Actions は先日アルファが取れたようです。
Next.jsの公式ドキュメントで確認。ただし、ベータ版とも書かれていませんでした。
APIの関数として紹介されています。
現在、実験的機能のフラグをtrueにする事で利用できます。
next.config.js
module.exports = {
experimental: {
serverActions: true,
},
}
この記事の主旨
この記事ではNext.jsが公式で用意しているSupabaseのサンプルを使って、
Supabaseのデータを取得する方法を解説します。
Next.js公式examples集を分類(2023年7月版)
https://qiita.com/masakinihirota/items/c4c8931d7067349006ef
Next.jsのサンプル集の中の with-supabase を見ていきます。
このサンプルは Supabase 側でも 推奨されています。
next.js/examples/with-supabase at canary · vercel/next.js
※このサンプルは認証機能付きです。
※2023年12月26日現在リポジトリの内容は変更されてしまいました。
環境
Windows10
VSCode
Supabase
前提知識
Supabaseのアカウント作成済み。
Supabaseの環境変数の取得方法を理解している。
Supabaseのダッシュボードが使えるようになっている。
インストール
npx create-next-app -e with-supabase
※このリポジトリは新バージョンになってしまいました。
環境変数の設定
NEXT_PUBLIC_SUPABASE_URL=https://z************z.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=ey**************************74
これで設定が完了していれば
npm run dev
を実行すると
で起動の確認が出来ます。
ユーザーを作成してログインが出来ることを確認してください。
参考
Supabase ローカル開発環境 + サーバー運用を想定 2023 - Qiita
※今回は Supabaseのローカル環境を必要としません。
データベースの初期化
マイグレーションファイルをサーバーに適用する方法
このリポジトリにはサンプル用のSQLがすでに用意されています。
それをSupabaseに適用します。
Supabaseのダッシュボードを開きます。
SQL Editorを開きます。
supabase\migrations\20230618024722_init.sql
create table if not exists todos (
id uuid default gen_random_uuid() primary key,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
title text,
is_complete boolean default false,
user_id uuid references auth.users default auth.uid()
);
-- Set up Row Level Security (RLS)
-- See https://supabase.com/docs/guides/auth/row-level-security for more details.
alter table todos
enable row level security;
create policy "Authenticated users can select todos" on todos
for select to authenticated using (true);
create policy "Authenticated users can insert their own todos" on todos
for insert to authenticated with check (auth.uid() = user_id);
を貼り付けます。
右下の 「RUN CTRL」ボタンを押して 成功したら メッセージ ↓Successを確認できます。
「Success. No rows returned」
これで todos テーブルが出来ました。
※SupabaseのCLIからサーバーに直接適用できるかどうか調べてみましたが、ローカル環境を作らずに直接サーバーに適用させる方法はわかりませんでした。
ですので上記のようにSQL文をコピペしています。
通常、SupabaseのCLIは、ローカル環境でマイグレーションを作成し、DBでテーブルを作成して、シードファイルを読み込ませて、動作確認ができたらサーバーにPUSHしています。
ローカルからならたくさんのマイグレーションファイルを作ってもPUSH出来ますし、一つにまとめるコマンドもあるようですからローカルからサーバーに反映させるのには問題ありません。
シードファイルをサーバーに適用する方法
supabase\seed.sql
insert into todos(title)
values
('Create Supabase project'),
('Create Next.js app from Supabase Starter template'),
('Keep building cool stuff!');
シードファイルもテーブル作成と同様にSQL文を貼り付けて実行します。
Supabaseのダッシュボードの Table Editor で todosテーブルを選択すると。
データも入っていることが確認できます。
これでSupabase側の下準備は完了です。
これからNext.jsのコードからSupabaseにアクセスして
そのデータを表示するまでを実践します。
Next.jsでのコンポーネント
サンプルには4つのコンポーネントが用意されています。
この4つにはSupabaseにアクセスする4種類のパターンが用意されています。
4種類のアクセス方法
クライアントから Supabaseのデータを取得する方法
useEffectを利用してデータを取得しています。
Supabaseはクライアントから直接データを取得できます。
クライアントコンポーネントにしています。
ルートハンドラーで Supabaseのデータを取得する方法
Next.jsのルーティング機能を使用してHTTPリクエストを送信してデータを取得しています。
Supabaseから直接データを取得しています。
ルートハンドラー
Webアプリケーションにおいて、特定のURLに対するリクエストを処理するための関数です。
ルートハンドラーは、リクエストを受け取り、適切なレスポンスを返すために使用されます。
Server ActionsでSupabaseのDBを操作する方法
このサンプルではインサート関数を作ってタイトルを保存しています。
Supabaseから直接データを挿入しています。
サーバーコンポーネントで Supabaseのデータを取得する方法
このサンプルではサーバーコンポーネントのクライアントを使用しています。
サーバーコンポーネントからデータを取得しているので、データの取得は高速で、セキュリティリスクは少ないです。
サーバーコンポーネント
サーバー側でのみ実行されるコンポーネントです。
4つのコンポーネント
app\_examples\client-component
app\_examples\route-handler
app\_examples\server-action
app\_examples\server-component
クライアントコンポーネント
ルートハンドラー
サーバーアクション
サーバーコンポーネント
の4種類です。
※Next.jsの App router のルールとして フォルダ名の先頭に _[アンダーバー]が
ついているとそのフォルダは無視されます。
app\_examples
このアンダーバーを消して
app\examples
にフォルダ名を変更します。
※VSCode上でフォルダ名を変更すると時間がかなりかかるので、
一旦VSCodeを閉じてエクスプローラー上から変更して、再度VSCodeを起動します。
最初にトップページにそれぞれのコンポーネントにリンクを貼っておきます。
ログイン部分の機能だけ残して不要な部分を削除します。
(見た目を綺麗にするだけです)
app\page.tsx
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs"
import { cookies } from "next/headers"
import Link from "next/link"
import LogoutButton from "../components/LogoutButton"
export default async function Index() {
const supabase = createServerComponentClient({ cookies })
const {
data: { user },
} = await supabase.auth.getUser()
return (
<>
<nav className="flex justify-center w-full h-16 border-b border-b-foreground/10">
<div className="flex items-center justify-between w-full max-w-4xl p-3 text-sm text-foreground">
<div />
<div>
{user ? (
<div className="flex items-center gap-4">
Hey, {user.email}!
<LogoutButton />
</div>
) : (
<Link
href="/login"
className="px-4 py-2 no-underline rounded-md bg-btn-background hover:bg-btn-background-hover"
>
Login
</Link>
)}
</div>
</div>
</nav>
<Link href="./examples/client-component">Client Component Example</Link>
<Link href="./examples/route-handler">Route Handler Example</Link>
<Link href="./examples/server-action">Server Action Example</Link>
<Link href="./examples/server-component">Server Component Example</Link>
</>
)
}
リンクを貼るだけでコードの作業は完了です。
あとは
npm run dev
でローカル環境を立ち上げて
をブラウザで表示します。
ログインをします。
※ログインはそれぞれ各自で設定してください。
そうするとトップページに4つのリンクが表示されます。
Client Component Example
Route Handler Example
Server Action Example
Server Component Example
※ダッシュボードの Table Editor を開き todos テーブルのデータと見比べてください。
※Server Action Exampleはなにか文字列を入力してリターンキーを押すと、データがテーブルに登録されます。リターンキーを押しすぎるとそのまま複数のデータが登録されるので注意してください。
Supabaseのアクセス方法の解説 (おまけ)
Client Component Example
クライアントコンポーネントです。
なので"use client"とディレクティブが書かれています。
const [todos, setTodos] = useState<any[]>([])
Hooks useState の基本的な使いかたなので説明の必要はありません。
const supabase = createClientComponentClient()
createClientComponentClient を使用してクライアントを作っています。
※Supabaseのクライアントつくると Supabaseへアクセスする事ができるようになります。
useEffect(() => {
const getTodos = async () => {
const { data } = await supabase.from("todos").select()
// データがあるのなら
if (data) {
setTodos(data)
}
}
getTodos()
}, [supabase, setTodos])
↑Supabaseにアクセスするために非同期処理 async/await を利用しています。
supabase.from("todos").select()
↑このコードはSupabaseにアクセスしています。
見た目通り todos テーブルから select 関数で 全てのカラムを取得しています。
}, [supabase, setTodos])
↑この行は useEffect の第2引数で 依存配列です。
どちらかが変更された場合に再度実行されます。
これも useEffect の基本的な使い方です。
return
{JSON.stringify(todos, null, 2)}
return文は取得したデータを表示するだけの役割です。
特に難しいところはありませんでした。
Route Handler Example
Next.jsのルーティング機能を使用して、HTTPリクエストを受け取り、適切なルートにルーティングするための関数が定義されたファイルです。このファイルには、GET
HTTPメソッド関数が定義されており、リクエストを受け取り、適切な処理を行うためのコードが記述されています。
GET
関数は、HTTP GETリクエストを受け取り、Supabaseを使用してデータを取得するための処理を行います。
このファイルは、Next.jsのルーティング機能を使用して、サーバーサイドでのAPIエンドポイントを簡単に作成するためのサンプルです。
ルートハンドラーのクライアントは
createRouteHandlerClient
を利用してクライアントを作成しています。
const supabase = createRouteHandlerClient({ cookies })
※クッキーを使用してクライアントを作成しています。
const { data: todos } = await supabase.from('todos').select()
これは前項のデータ取得方法と同じです。
return NextResponse.json(todos)
取得したデータをjsonで返しています。
特に難しいところはありませんでした。
Server Action Example
このサンプルのメイン Server Actions の利用方法です。
このサンプルでは、Supabaseの todos テーブルにデータを登録しています。
このコンポーネントは、createServerActionClient
関数を使用して、Supabaseのクライアントを初期化し、useEffect
フックを使用して、コンポーネントがマウントされたときにデータを取得する処理を行っています。
取得したデータは、setTodos
関数を使用して、todos
ステートに保存され、JSON.stringify
メソッドを使用して、JSON形式で表示されます。
revalidatePath()
この関数は、ページのパスを受け取り、そのパスに関連するデータを再検証するための処理を行います。
Next.js Server Actions と revalidate 周りの挙動を確認する
サーバーアクションのクライアントは
createServerActionClient
を利用してクライアントを作成しています。
ServerAction()関数は非同期関数です
const addTodo = async (formData: FormData) => {
"use server"
const title = formData.get("title")
↑この場所で"use server" ディレクティブが宣言されています。
このファイルはサーバー側です、しかしこのように "use server"とディレクティブを宣言しないと関数をクライアントコンポーネントに渡すことは出来ません。
<form action={addTodo}>
この場所で関数を渡しています。
const title = formData.get("title")
↑この行で 、フォームから送信されたデータからタイトルを取得しています。
formDataという変数は、フォームのデータを表すオブジェクトです。get()メソッドを使用して、titleという名前のフィールドの値を取得します。
タイトルが存在する場合
const supabase = createServerActionClient({ cookies })
クライアントを作成しています。
今回はクライアントからDBへ直接挿入をしています。
await supabase.from("todos").insert({ title })
↑この1行だけでフォームに入力したタイトルを直接DBに登録しています。
コードは非常にシンプルです。
revalidatePath("/server-action-example")
revalidatePathをつかい、データが更新されていたらページのデータが最新のものに更新されます。
return (
<form action={addTodo}>
<input name="title" />
</form>
)
↑return文でフォームに ServerAction関数である addTodo が登録されています。
Server Component Example
このコンポーネントは、createServerComponentClient
関数を使用して、Supabaseのクライアントを初期化し、useEffect
フックを使用して、コンポーネントがマウントされたときにデータを取得する処理を行っています。
サーバーコンポーネントなので、
データへのアクセス速度は早く、
セキュリティリスクは少なく、
安全にデータを扱うことが可能です。
const supabase = createServerComponentClient({ cookies })
Supabaseのクライアントを作っています。
const { data: todos } = await supabase.from('todos').select()
supabase.from('todos').select()
Supabaseの todos テーブルから すべてのデータを取得しています。
return <pre>{JSON.stringify(todos, null, 2)}</pre>
JSON.stringify()
関数は、JavaScriptオブジェクトをJSON文字列に変換するために使用されます。
この関数は、3つの引数を受け取ります。最初の引数は変換するオブジェクトであり、2番目の引数は置換関数であり、3番目の引数はインデントレベルです。この場合、todos
オブジェクトが変換され、インデントレベルが2に設定されています。
App routerの特性を利用して サーバからDBにアクセスをしてもらって
それをブラウザで表示しています。
これも特に難しい処理はありません。
以上が4つのDBへのアクセス方法になります。
ログアウト
ログアウトすると、ブラウザ上にはデータが表示されません。
Server Actionsからもデータを登録できません。
これは RLS が効いているからです。
最初に使用したSQL文で、テーブルを作成した後半に RLS を有効にするコードと
RLSの使用条件を設定したコードが書かれています。