← スキル一覧に戻る

supabase-patterns
by TerryXBT
⭐ 0🍴 0📅 2026年1月17日
SKILL.md
name: supabase-patterns description: Supabase database patterns and best practices for Slotify. Use this skill when working with database operations, RLS policies, migrations, or authentication. Covers client setup, queries, and security.
Supabase Patterns Skill
This skill defines Supabase usage patterns for the Slotify booking platform.
Client Setup
Server-Side Client (Preferred for data fetching)
// utils/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
},
},
}
);
}
Browser Client (For client components)
// utils/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
Admin Client (For service role operations)
// utils/supabase/admin.ts
import { createClient } from '@supabase/supabase-js';
export function createAdminClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // Server-only!
{ auth: { persistSession: false } }
);
}
Query Patterns
Basic CRUD
// Read
const { data, error } = await supabase
.from('bookings')
.select('*')
.eq('status', 'confirmed');
// Create
const { data, error } = await supabase
.from('bookings')
.insert({ client_name: 'John', service_id: '123' })
.select()
.single();
// Update
const { error } = await supabase
.from('bookings')
.update({ status: 'cancelled' })
.eq('id', bookingId);
// Delete
const { error } = await supabase
.from('bookings')
.delete()
.eq('id', bookingId);
Related Data (Joins)
// Fetch booking with service and profile
const { data } = await supabase
.from('bookings')
.select(`
*,
service:services(*),
profile:profiles(id, display_name, avatar_url)
`)
.eq('id', bookingId)
.single();
Filtering & Ordering
const { data } = await supabase
.from('bookings')
.select('*')
.eq('provider_id', providerId)
.gte('start_time', startDate)
.lte('start_time', endDate)
.in('status', ['confirmed', 'pending'])
.order('start_time', { ascending: true })
.limit(50);
Row-Level Security (RLS)
Enable RLS on All Tables
ALTER TABLE bookings ENABLE ROW LEVEL SECURITY;
Common RLS Patterns
-- Users can only see their own data
CREATE POLICY "Users can view own bookings"
ON bookings FOR SELECT
USING (auth.uid() = provider_id);
-- Users can insert their own data
CREATE POLICY "Users can create bookings"
ON bookings FOR INSERT
WITH CHECK (auth.uid() = provider_id);
-- Public read for specific conditions
CREATE POLICY "Public can view active services"
ON services FOR SELECT
USING (is_active = true);
Migration Best Practices
File Naming Convention
supabase/migrations/
├── 001_initial_schema.sql
├── 002_add_busy_blocks.sql
├── 003_add_reschedule_proposals.sql
└── 004_add_action_tokens.sql
Migration Template
-- Migration: Add location to profiles
-- Description: Adds location and meeting_link fields for service locations
-- Add columns
ALTER TABLE profiles
ADD COLUMN IF NOT EXISTS location TEXT,
ADD COLUMN IF NOT EXISTS meeting_link TEXT;
-- Add comments
COMMENT ON COLUMN profiles.location IS 'Physical address for in-person services';
COMMENT ON COLUMN profiles.meeting_link IS 'Video call link for online services';
-- No RLS changes needed (existing policies cover new columns)
Authentication Patterns
Get Current User
// Server component
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
redirect('/login');
}
Protected Server Actions
'use server';
export async function updateSettings(formData: FormData) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return { error: 'Unauthorized' };
}
// Safe to proceed with user.id
}
Atomic Operations (Prevent Double-Booking)
-- Use FOR UPDATE to lock rows during check
CREATE OR REPLACE FUNCTION check_slot_availability(
p_provider_id UUID,
p_start_time TIMESTAMPTZ,
p_end_time TIMESTAMPTZ
) RETURNS BOOLEAN AS $$
DECLARE
conflict_count INTEGER;
BEGIN
SELECT COUNT(*) INTO conflict_count
FROM bookings
WHERE provider_id = p_provider_id
AND status IN ('confirmed', 'pending')
AND start_time < p_end_time
AND end_time > p_start_time
FOR UPDATE; -- Lock these rows
RETURN conflict_count = 0;
END;
$$ LANGUAGE plpgsql;
Type Safety
Generate Types
npx supabase gen types typescript --project-id=YOUR_PROJECT_ID > src/types/supabase.ts
Use Generated Types
import { Database } from '@/types/supabase';
type Booking = Database['public']['Tables']['bookings']['Row'];
type NewBooking = Database['public']['Tables']['bookings']['Insert'];
Error Handling
const { data, error } = await supabase
.from('bookings')
.insert(booking)
.select()
.single();
if (error) {
// Handle specific error codes
if (error.code === '23505') {
return { error: 'Booking already exists for this time' };
}
if (error.code === '23503') {
return { error: 'Invalid service or provider' };
}
// Log unexpected errors
console.error('Supabase error:', error);
Sentry.captureException(error);
return { error: 'Failed to create booking' };
}
Performance Tips
- Select only needed columns:
.select('id, name, status') - Use indexes: Create indexes for frequently filtered columns
- Limit results: Always use
.limit()for list queries - Use RPC for complex logic: Move logic to database functions
- Batch operations: Use
.insert([...items])for bulk inserts
スコア
総合スコア
50/100
リポジトリの品質指標に基づく評価
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
レビュー
💬
レビュー機能は近日公開予定です