はじめに
以下のドキュメントに基づいて解説します。
また、データベーステーブルの作成及びRLSポリシーの設定は完了している前提とします。
RLSポリシーについては、以下参照。
環境変数の設定
supabseプロジェクトのページから以下の2つの変数を取得し、.env.local
ファイルに記述する
NEXT_PUBLIC_SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<SUBSTITUTE_SUPABASE_ANON_KEY>
Supabaseパッケージのインストール
以下をターミナルで実行し、パッケージをインストールする
npm install @supabase/supabase-js
Supabaseクライアントの作成
Next.jsプロジェクト内でSupabaseのクライアントを作成していく
- プロジェクトのルートディレクトリに
utils/supabase
ディレクトリを作成 -
utils/supabase
ディレクトリにserver.tsx
,client.tsx
を作成
import { createBrowserClient } from "@supabase/ssr";
import { Database } from "@/types/database.types";
export const createClient = () => {
createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
};
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
import { Database } from "@/types/database.types";
export const createClient = async() => {
const cookieStore = await cookies();
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
try {
cookieStore.set({ name, value, ...options });
} catch (error) {
// The `set` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
remove(name: string, options: CookieOptions) {
try {
cookieStore.set({ name, value: "", ...options });
} catch (error) {
// The `delete` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
},
);
};
使用する
サーバサイドで使用する
import { createClient } from '@/utils/supabase/server';
export default async function Instruments() {
const supabase = await createClient();
const { data: instruments } = await supabase.from("instruments").select();
return <pre>{JSON.stringify(instruments, null, 2)}</pre>
}
クライアントサイドで使用する
'use client'
import { createClient } from '@/utils/supabase/client';
export default async function Instruments() {
const supabase = await createClient();
const { data: instruments } = await supabase.from("instruments").select();
return <pre>{JSON.stringify(instruments, null, 2)}</pre>
}
クエリの書き方
.from(tableName)
対象のテーブルを指定します。
supabase.from('users')
.select(columns)
カラムを指定します。*
で全カラム指定。
supabase.from('users').select('*')
supabase.from('users').select('id, name')
supabase.from('users').select('profile(id, bio)') // リレーションの取得
.insert(data)
新しいレコードを挿入します。
const { data, error } = await supabase
.from('users')
.insert([{ name: 'Taro', age: 30 }])
.update(data)
レコードを更新します。.eq()
などで条件を指定する必要があります。
const { data, error } = await supabase
.from('users')
.update({ age: 31 })
.eq('id', 1)
.delete()
レコードを削除します。.eq()などで条件を指定する必要があります。
const { data, error } = await supabase
.from('users')
.delete()
.eq('id', 1)
比較系フィルター( .eq(column, value) .neq() .gt() .gte() .lt() .lte()
)
フィルター条件を指定します。
.select('*').eq('id', 1) // id = 1
.select('*').neq('age', 20) // age != 20
.select('*').gt('age', 20) // age > 20
.select('*').gte('age', 20) // age >= 20
.select('*').lt('age', 30) // age < 30
.select('*').lte('age', 30) // age <= 30
セット・範囲系フィルタ( .in(column, [values]) .notIn() .is() .not()
)
指定カラムが配列内要素との一致・不一致によってフィルターします
.in()
: 配列に含まれる
.notIn()
: 配列に含まれない
.is()
: nullの比較
.not()
: 否定(特殊系)
.select('*').in('id', [1, 2, 3]) // id == 1 or 2 or 3
.select('*').notIn('id', 'in', '(1, 2)') // id != 1 and id != 2
.select('*').is('deleted_at', null) // deleted_at == null
.select('*').not(id, 'eq', 0) // status != 0
部分一致検索・パターンマッチ( .like(column, pattern) .ilike() .textSearch() .match()
)
部分一致・全部一致で検索します(%をワイルドカードに使用)
// '太郎'に部分一致
.select('*').like('name', '%太郎%')
// '@gmail.com'に大文字小文字無視の部分一致
.select('*').ilike('email', '%@gmail.com')
// patternに全部一致するフルテキスト検索
.select('*').textSearch('description', 'search term')
// name == 'Taro' and age == 30 に一致するデータ
.select('*').match({ name: 'Taro', age: 30 })
.order(column, { ascending: true/false })
ascending: true/false
で(降順・昇順)並び替えを行います
// `created_at`カラムについて昇順で並び替える
.select('*').order('created_at', { ascending: false })
.limit(count)
取得件数を制限します
// 10件のみ取得する
.select('*').limit(10)
.single()
/ .maybeSingle()
.single()
:1件だけ取得することを期待(なければエラー)
.maybeSingle()
:1件取得できれば返す(なくてもエラーにならない)
// id == 1のデータを1件だけ取得する(なければエラー)
.select('*').eq('id', 1).single()
.rpc(function_name, params)
Supabaseのストアドプロシージャ(PostgreSQL関数)を呼び出します
const { data, error } = await supabase
.rpc('get_user_posts', { user_id: 1 })
複数条件の組み合わせ例
const { data, error } = await supabase
.from('users')
.select('*')
.gt('age', 20)
.lt('age', 50)
.like('name', '%太郎%')
.order('created_at', { ascending: false })
.limit(10)
Supabaseはバックエンドで PostgREST を使用しており、クエリチェーンで指定された条件(.eq(), .gt(), .like()など)は、最終的に1つのHTTPクエリパラメータ群に変換されてサーバーに送信されます。
このため、以下のように順番を変えても、SQLに変換されたときには同じ意味のWHERE句になります。
ただし順序が意味を持つケースもある
順不同が前提ですが、以下のような非フィルター系メソッド(副作用があるもの)が絡む場合は、順序に注意が必要です。
❌ 良くない例
// 更新前にフィルターせずに .update() を呼ぶと全件対象になる可能性
supabase
.from('users')
.update({ status: 'active' }) // ← この時点で全件対象になる
.eq('country', 'Japan')
✅ 正しい順序
// フィルターしてから更新
supabase
.from('users')
.eq('country', 'Japan')
.update({ status: 'active' })