Supabase核心技能 supabase

掌握 Supabase 核心技能,包括 CLI 工作流程、数据库迁移、实时数据订阅、边缘函数部署等,实现高效开发和数据管理。

后端开发 0 次安装 0 次浏览 更新于 3/5/2026

name: supabase description: 核心 Supabase CLI,迁移,RLS,边缘函数

Supabase 核心技能

加载与: base.md + [supabase-nextjs.md | supabase-python.md | supabase-node.md]

所有 Supabase 项目的共同概念、CLI 工作流程和模式。

来源: Supabase 文档 | Supabase CLI


核心原则

本地优先,迁移在版本控制中,永远不要直接触碰生产环境。

使用 Supabase CLI 在本地开发,将所有更改作为迁移捕获,并通过 CI/CD 部署。


Supabase 技术栈

服务 目的
数据库 PostgreSQL 及其扩展
认证 用户认证,OAuth 提供者
存储 具有 RLS 的文件存储
边缘函数 无服务器 Deno 函数
实时 WebSocket 订阅
向量 AI 嵌入(pgvector)

CLI 设置

安装 & 登录

# macOS
brew install supabase/tap/supabase

# npm(替代方案)
npm install -g supabase

# 登录
supabase login

初始化项目

# 在你的项目目录
supabase init

# 创建:
# supabase/
# ├── config.toml      # 本地配置
# ├── seed.sql         # 种子数据
# └── migrations/      # SQL 迁移

链接到远程

# 从仪表板 URL 获取项目引用:https://supabase.com/dashboard/project/<ref>
supabase link --project-ref <project-id>

# 拉取现有架构
supabase db pull

启动本地堆栈

supabase start

# 输出:
# API URL: http://localhost:54321
# GraphQL URL: http://localhost:54321/graphql/v1
# DB URL: postgresql://postgres:postgres@localhost:54322/postgres
# 工作室 URL: http://localhost:54323
# 匿名密钥: eyJ...
# 服务角色密钥: eyJ...

迁移工作流

选项 1: 仪表板 + 差异(快速原型设计)

# 1. 在本地工作室(localhost:54323)进行更改
# 2. 从差异生成迁移
supabase db diff -f <migration_name>

# 3. 审查生成的 SQL
cat supabase/migrations/*_<migration_name>.sql

# 4. 重置以测试
supabase db reset

选项 2: 直接编写迁移(推荐)

# 1. 创建空迁移
supabase migration new create_users_table

# 2. 编辑迁移文件
# supabase/migrations/<timestamp>_create_users_table.sql

# 3. 在本地应用
supabase db reset

选项 3: ORM 迁移(最佳 DX)

使用 Drizzle(TypeScript)或 SQLAlchemy(Python)- 见框架特定技能。

部署迁移

# 推送到远程(暂存/生产)
supabase db push

# 检查迁移状态
supabase migration list

数据库模式

在所有表上启用 RLS

-- 始终启用 RLS
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;

-- 默认拒绝 - 必须创建策略
CREATE POLICY "Users can view own profile"
  ON public.profiles
  FOR SELECT
  USING (auth.uid() = id);

常见 RLS 策略

-- 公开读取
CREATE POLICY "Public read access"
  ON public.posts FOR SELECT
  USING (true);

-- 仅限认证用户
CREATE POLICY "Authenticated users can insert"
  ON public.posts FOR INSERT
  WITH CHECK (auth.role() = 'authenticated');

-- 所有者访问
CREATE POLICY "Users can update own records"
  ON public.posts FOR UPDATE
  USING (auth.uid() = user_id);

-- 管理员访问(使用自定义声明)
CREATE POLICY "Admins have full access"
  ON public.posts FOR ALL
  USING (auth.jwt() ->> 'role' = 'admin');

链接到 auth.users

-- 与 auth 链接的配置文件
CREATE TABLE public.profiles (
  id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  username TEXT UNIQUE NOT NULL,
  avatar_url TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 注册时自动创建配置文件
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO public.profiles (id, username)
  VALUES (NEW.id, NEW.email);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER on_auth_user_created
  AFTER INSERT ON auth.users
  FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();

种子数据

supabase/seed.sql

-- 在 `supabase db reset` 上运行
-- 使用 ON CONFLICT 以实现幂等性

INSERT INTO public.profiles (id, username, avatar_url)
VALUES
  ('d0e1f2a3-b4c5-6d7e-8f9a-0b1c2d3e4f5a', 'testuser', null),
  ('a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d', 'admin', null)
ON CONFLICT (id) DO NOTHING;

环境变量

必需变量

# 公共(适用于客户端)
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_ANON_KEY=eyJ...

# 私有(仅限服务器端 - 永不公开)
SUPABASE_SERVICE_ROLE_KEY=eyJ...
DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-region.pooler.supabase.com:6543/postgres

本地与生产

# .env.local(本地开发)
SUPABASE_URL=http://localhost:54321
SUPABASE_ANON_KEY=<从 supabase start 获取>
DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres

# .env.production(远程)
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_ANON_KEY=<从仪表板获取>
DATABASE_URL=<连接池 URL>

连接池

# 事务模式(推荐用于无服务器)
# 在 URL 中添加 ?pgbouncer=true
DATABASE_URL=postgresql://...@pooler.supabase.com:6543/postgres?pgbouncer=true

# 会话模式(用于迁移,长事务)
DATABASE_URL=postgresql://...@pooler.supabase.com:5432/postgres

边缘函数

创建函数

supabase functions new hello-world

基本结构

// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';

serve(async (req) => {
  const { name } = await req.json();

  return new Response(
    JSON.stringify({ message: `Hello ${name}!` }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

带认证上下文

import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? '',
    {
      global: {
        headers: { Authorization: req.headers.get('Authorization')! },
      },
    }
  );

  const { data: { user } } = await supabase.auth.getUser();

  if (!user) {
    return new Response('Unauthorized', { status: 401 });
  }

  return new Response(JSON.stringify({ user_id: user.id }));
});

部署

# 本地服务
supabase functions serve

# 部署单个函数
supabase functions deploy hello-world

# 部署全部
supabase functions deploy

存储

创建存储桶(在迁移中)

INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);

-- 存储策略
CREATE POLICY "Avatar images are publicly accessible"
  ON storage.objects FOR SELECT
  USING (bucket_id = 'avatars');

CREATE POLICY "Users can upload own avatar"
  ON storage.objects FOR INSERT
  WITH CHECK (
    bucket_id = 'avatars' AND
    auth.uid()::text = (storage.foldername(name))[1]
  );

CLI 快速参考

# 生命周期
supabase start                   # 启动本地堆栈
supabase stop                    # 停止本地堆栈
supabase status                  # 显示状态 & 凭据

# 数据库
supabase db reset                # 重置 + 迁移 + 种子
supabase db push                 # 推送到远程
supabase db pull                 # 拉取远程架构
supabase db diff -f <name>       # 从差异生成迁移
supabase db lint                 # 检查问题

# 迁移
supabase migration new <name>    # 创建迁移
supabase migration list          # 列出迁移
supabase migration up            # 应用待处理(远程)

# 函数
supabase functions new <name>    # 创建函数
supabase functions serve         # 本地开发
supabase functions deploy        # 部署全部

# 类型
supabase gen types typescript --local > types/database.ts

# 项目
supabase link --project-ref <id> # 链接到远程
supabase projects list           # 列出项目

CI/CD 模板

# .github/workflows/supabase.yml
name: Supabase CI/CD

on:
  push:
    branches: [main]
  pull_request:

env:
  SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
  SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
  SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: supabase/setup-cli@v1

      - name: Start Supabase
        run: supabase start

      - name: Run migrations
        run: supabase db reset

      - name: Lint database
        run: supabase db lint

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: supabase/setup-cli@v1

      - name: Link project
        run: supabase link --project-ref $SUPABASE_PROJECT_ID

      - name: Push migrations
        run: supabase db push

      - name: Deploy functions
        run: supabase functions deploy

反模式

  • 直接生产更改 - 总是使用迁移
  • 禁用 RLS - 在所有用户数据表上启用
  • 客户端的服务密钥 - 永不公开服务角色密钥
  • 无连接池 - 使用池化器进行无服务器
  • 提交 .env - 添加到 .gitignore
  • 跳过迁移审查 - 始终检查生成的 SQL
  • 无种子数据 - 使用 seed.sql 进行一致的本地开发