backend-development
npx skills add https://github.com/tomaspozo/skills --skill backend-development
Agent 安装分布
Skill 文档
Supabase Local Dev Workflow
Core Philosophy
- Schema-driven development â all structural changes go to schema files, never direct SQL
- RPC-first architecture â no direct
supabase-jstable calls; all data access through RPCs - DB functions as first-class citizens â business logic lives in the database
Process
Phase 0: Setup Verification (run once per project)
Before starting any backend work, verify the project’s infrastructure is in place.
1. Run the check query â Load assets/check_setup.sql and execute it via execute_sql. It returns a JSON object like:
{
"extensions": { "pg_net": true, "vault": true },
"functions": { "_internal_get_secret": true, "_internal_call_edge_function": true, "_internal_call_edge_function_sync": true },
"secrets": { "SUPABASE_URL": true, "SB_PUBLISHABLE_KEY": true, "SB_SECRET_KEY": true },
"ready": true
}
If "ready": true â skip to Phase 1. Otherwise, fix what’s missing:
2. Missing extensions â Apply via apply_migration:
CREATE EXTENSION IF NOT EXISTS pg_net;
-- vault is typically enabled by default; if not:
CREATE EXTENSION IF NOT EXISTS supabase_vault;
3. Missing internal functions â Copy assets/setup.sql functions into the project’s supabase/schemas/50_functions/_internal/ schema files, then apply via apply_migration.
4. Missing Vault secrets â Two paths depending on agent capabilities:
-
Agent path (preferred): If you have
execute_sql, ask the user for their project URL, publishable key, and secret key. Then for each missing secret run:SELECT vault.create_secret('<value>', '<secret_name>');Use these exact secret names:
SUPABASE_URL,SB_PUBLISHABLE_KEY,SB_SECRET_KEY. -
Manual path (fallback): If you cannot run SQL directly, point the user to the setup script:
./scripts/setup_vault_secrets.sh \ --url "https://your-project.supabase.co" \ --publishable-key "sb_publishable_..." \ --secret-key "sb_secret_..."
â ï¸ Local development (supabase start):
SUPABASE_URLmust use the Docker-internal hostname, nothttp://127.0.0.1:54321. From inside the Postgres container,127.0.0.1refers to the container itself. Usehttp://host.docker.internal:54321instead. This only applies to the Vault secret used by_internal_call_edge_function; the CLI and client-side code still usehttp://127.0.0.1:54321.
5. Re-run the check to confirm "ready": true before proceeding.
ð Load Initial Project Setup for the detailed step-by-step workflow.
Phase 1: Schema Changes
Write structural changes to the appropriate schema file based on the folder structure:
supabase/schemas/
âââ 10_types/ # Enums, composite types, domains
âââ 20_tables/ # Table definitions
âââ 30_constraints/ # Check constraints, foreign keys
âââ 40_indexes/ # Index definitions
âââ 50_functions/ # RPCs, auth functions, internal utils
â âââ _internal/ # Infrastructure utilities
â âââ _auth/ # RLS policy functions
âââ 60_triggers/ # Trigger definitions
âââ 70_policies/ # RLS policies
âââ 80_views/ # View definitions
Files are organized by entity (e.g., charts.sql, readings.sql). Numeric prefixes ensure correct application order.
ð Load Naming Conventions for table, column, and function naming rules.
Phase 2: Apply & Fix
- CLI auto-applies changes (
supabase start) - Monitor logs for errors (constraint violations, dependencies)
- If errors â use
execute_sqlMCP tool for data fixes only (UPDATE, DELETE, INSERT) - Never use
execute_sqlfor schema structure â only schema files
Phase 3: Generate Types
supabase gen types typescript --local > src/types/database.ts
Phase 4: Iterate
Repeat Phases 1-3 until schema is stable and tested.
Phase 5: Migration
- Use
supabase db diffto generate migration - Review migration â patch if manual SQL commands are missing
Reference Files
Load these as needed during development:
Conventions & Patterns
- ð Naming Conventions â Tables, columns, functions, indexes
- ð RPC Patterns â RPC-first architecture, auth functions, RLS policies
- â¡ Edge Functions â Project structure, shared utilities, CORS, error helpers
- ð§ withSupabase Wrapper â Wrapper rules, role selection, client usage patterns
Setup & Infrastructure
- ð Setup Check â Verify extensions, functions, and secrets exist
- âï¸ Setup Guide â Internal utility function definitions
- ð Vault Secrets Script â Store secrets in Vault (manual fallback)
Workflows
- ð Common Workflows â Adding entities, fields, creating RPCs
Entity Tracking
- ð Entity Registry Template â Track entities and schema files
Tools & Dependencies
| Tool | Purpose |
|---|---|
| Supabase CLI | Local development, type generation, migrations |
| Supabase MCP | execute_sql tool for data fixes |
| Edge Functions | See Edge Functions for project structure and withSupabase for wrapper usage |
Quick Reference
Client-side rule â Never direct table access:
// â WRONG
const { data } = await supabase.from("charts").select("*");
// â
CORRECT
const { data } = await supabase.rpc("chart_get_by_user", { p_user_id: userId });
Security context rule â SECURITY INVOKER by default:
-- â WRONG â bypasses RLS then reimplements filtering manually
CREATE FUNCTION chart_get_by_id(p_chart_id uuid)
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$
BEGIN
SELECT ... FROM public.charts WHERE id = p_chart_id AND user_id = auth.uid(); -- manual filter = fragile
END; $$;
-- â
CORRECT â RLS handles access control automatically
CREATE FUNCTION chart_get_by_id(p_chart_id uuid)
RETURNS jsonb LANGUAGE plpgsql SECURITY INVOKER SET search_path = '' AS $$
BEGIN
SELECT ... FROM public.charts WHERE id = p_chart_id; -- RLS enforces permissions
END; $$;
When to use SECURITY DEFINER (rare exceptions):
_auth_*functions called by RLS policies (they run during policy evaluation, need to bypass RLS to query the table they protect)_internal_*utility functions that need elevated access (e.g., reading vault secrets)- Multi-table operations that need cross-table access the user’s role can’t reach
- Always document WHY with a comment:
-- SECURITY DEFINER: required because ...
Function prefixes:
- Business logic:
{entity}_{action}âchart_create(SECURITY INVOKER) - Auth (RLS):
_auth_{entity}_{check}â_auth_chart_can_read(SECURITY DEFINER â needed by RLS) - Internal:
_internal_{name}â_internal_get_secret(SECURITY DEFINER â elevated access)