DevToolBox免费
博客

Supabase 指南 2026:认证、数据库、实时、存储与边缘函数

20 分钟阅读作者 DevToolBox
TL;DR

Supabase 是基于 PostgreSQL 的开源 Firebase 替代方案。提供托管 Postgres 数据库(含行级安全)、认证(邮箱/OAuth/魔法链接)、实时订阅、文件存储、Edge Functions(基于 Deno 的无服务器函数)和自动生成的 REST/GraphQL API。使用 @supabase/supabase-js 客户端获得完整 TypeScript 支持。

什么是 Supabase?

Supabase 是一个基于 PostgreSQL 构建的开源后端即服务(BaaS)。与使用专有 NoSQL 数据库的 Firebase 不同,Supabase 提供完整的 Postgres 数据库,支持 SQL、JOIN、外键和高级索引。

Supabase 与 Firebase 对比

FeatureSupabaseFirebase
DatabasePostgreSQL (relational, SQL)Firestore (NoSQL, document)
AuthEmail, OAuth 20+, magic links, phoneEmail, OAuth, phone, anonymous
Real-timePostgres Changes, Broadcast, PresenceFirestore listeners, RTDB
StorageS3-compatible, CDN, transformsCloud Storage, CDN
FunctionsEdge Functions (Deno)Cloud Functions (Node.js)
Open SourceYes (fully open-source)No (proprietary)
Self-hostingYes (Docker Compose)No
PricingFree: 500MB DB, Pro: $25/moFree: limited, Blaze: pay-as-you-go
Query LanguageSQL, PostgREST, GraphQLProprietary SDK queries
Vendor Lock-inLow (standard Postgres)High (proprietary formats)

设置 Supabase 项目

可以使用 Supabase 云平台或通过 Docker 自托管。免费层包括 500MB 数据库、1GB 文件存储和 50,000 月活跃用户。

# Install Supabase CLI
npm install -g supabase

# Login to Supabase
supabase login

# Create a new project locally
supabase init

# Start local development (Docker required)
supabase start

# Output:
#   API URL:   http://localhost:54321
#   DB URL:    postgresql://postgres:postgres@localhost:54322/postgres
#   Studio:    http://localhost:54323
#   anon key:  eyJhbG...
#   service_role key: eyJhbG...

# Install the JavaScript client
npm install @supabase/supabase-js
// lib/supabase.ts - Initialize the client
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

// For server-side admin operations (never expose to client)
import { createClient as createAdmin } from '@supabase/supabase-js';

const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
export const supabaseAdmin = createAdmin(supabaseUrl, serviceRoleKey);

数据库:PostgreSQL 与行级安全

Supabase 提供完整的 PostgreSQL 数据库。使用标准 SQL 编写迁移,使用行级安全(RLS)在行级别控制访问。

创建表和迁移

-- supabase/migrations/001_create_tables.sql

-- Profiles table (extends auth.users)
CREATE TABLE public.profiles (
  id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
  username TEXT UNIQUE NOT NULL,
  full_name TEXT,
  avatar_url TEXT,
  bio TEXT,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
);

-- Posts table
CREATE TABLE public.posts (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  author_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  published BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
);

-- Comments table
CREATE TABLE public.comments (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  post_id BIGINT REFERENCES public.posts(id) ON DELETE CASCADE NOT NULL,
  user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
  content TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL
);

-- Indexes for common queries
CREATE INDEX idx_posts_author ON public.posts(author_id);
CREATE INDEX idx_posts_slug ON public.posts(slug);
CREATE INDEX idx_posts_published ON public.posts(published) WHERE published = true;
CREATE INDEX idx_comments_post ON public.comments(post_id);

-- Auto-update updated_at column
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER profiles_updated_at
  BEFORE UPDATE ON public.profiles
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER posts_updated_at
  BEFORE UPDATE ON public.posts
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

行级安全(RLS)

RLS 是 Supabase 的核心安全模型。启用后,每个查询都会被你定义的策略过滤。

-- Enable RLS on all tables
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.comments ENABLE ROW LEVEL SECURITY;

-- Profiles: anyone can read, owners can update their own
CREATE POLICY "Public profiles are viewable by everyone"
  ON public.profiles FOR SELECT
  USING (true);

CREATE POLICY "Users can update their own profile"
  ON public.profiles FOR UPDATE
  USING (auth.uid() = id)
  WITH CHECK (auth.uid() = id);

-- Posts: published posts visible to all, authors manage own posts
CREATE POLICY "Published posts are viewable by everyone"
  ON public.posts FOR SELECT
  USING (published = true OR auth.uid() = author_id);

CREATE POLICY "Users can create posts"
  ON public.posts FOR INSERT
  WITH CHECK (auth.uid() = author_id);

CREATE POLICY "Users can update their own posts"
  ON public.posts FOR UPDATE
  USING (auth.uid() = author_id)
  WITH CHECK (auth.uid() = author_id);

CREATE POLICY "Users can delete their own posts"
  ON public.posts FOR DELETE
  USING (auth.uid() = author_id);

-- Comments: anyone reads, authenticated users create, owners delete
CREATE POLICY "Comments are viewable by everyone"
  ON public.comments FOR SELECT
  USING (true);

CREATE POLICY "Authenticated users can create comments"
  ON public.comments FOR INSERT
  WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Users can delete their own comments"
  ON public.comments FOR DELETE
  USING (auth.uid() = user_id);

使用客户端查询数据

Supabase JavaScript 客户端提供流式查询构建器,底层映射到 PostgREST 查询。

// Fetch published posts with author profile
const { data: posts, error } = await supabase
  .from('posts')
  .select('
    id, title, slug, content, created_at,
    profiles:author_id (username, full_name, avatar_url)
  ')
  .eq('published', true)
  .order('created_at', { ascending: false })
  .limit(10);

// Fetch a single post by slug with comments
const { data: post } = await supabase
  .from('posts')
  .select('
    *, 
    profiles:author_id (username, avatar_url),
    comments (id, content, created_at, profiles:user_id (username))
  ')
  .eq('slug', 'my-first-post')
  .single();

// Insert a new post
const { data, error } = await supabase
  .from('posts')
  .insert({
    author_id: user.id,
    title: 'My New Post',
    content: 'Post content here...',
    slug: 'my-new-post',
    published: true,
  })
  .select()
  .single();

// Update a post
const { data, error } = await supabase
  .from('posts')
  .update({ title: 'Updated Title', published: true })
  .eq('id', postId)
  .select()
  .single();

// Delete a post
const { error } = await supabase
  .from('posts')
  .delete()
  .eq('id', postId);

// Full-text search
const { data } = await supabase
  .from('posts')
  .select('id, title, slug')
  .textSearch('title', 'supabase & guide');

// Pagination with range
const { data, count } = await supabase
  .from('posts')
  .select('*', { count: 'exact' })
  .eq('published', true)
  .range(0, 9); // rows 0-9 (first 10)

认证

Supabase Auth 支持邮箱/密码、魔法链接、手机 OTP 和 20+ OAuth 提供商。使用 JWT 管理用户会话,通过 auth.uid() 函数与 RLS 策略直接集成。

邮箱和密码认证

// Sign up with email and password
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'securePassword123',
  options: {
    data: {
      username: 'johndoe',
      full_name: 'John Doe',
    },
  },
});

// Sign in with email and password
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'securePassword123',
});

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

// Sign out
const { error } = await supabase.auth.signOut();

// Listen to auth state changes
supabase.auth.onAuthStateChange((event, session) => {
  console.log('Auth event:', event);
  // event: SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, etc.
  if (session) {
    console.log('User:', session.user.email);
  }
});

OAuth 提供商(Google、GitHub)

// Sign in with Google
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://yourapp.com/auth/callback',
    queryParams: {
      access_type: 'offline',
      prompt: 'consent',
    },
  },
});

// Sign in with GitHub
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: 'https://yourapp.com/auth/callback',
    scopes: 'read:user user:email',
  },
});

// Handle OAuth callback (in your callback route)
// app/auth/callback/route.ts
import { NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';

export async function GET(request: Request) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');

  if (code) {
    const supabase = createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
    );
    await supabase.auth.exchangeCodeForSession(code);
  }

  return NextResponse.redirect(new URL('/dashboard', request.url));
}

魔法链接认证

// Send magic link email
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://yourapp.com/auth/callback',
  },
});

// Phone OTP
const { data, error } = await supabase.auth.signInWithOtp({
  phone: '+1234567890',
});

// Verify phone OTP
const { data, error } = await supabase.auth.verifyOtp({
  phone: '+1234567890',
  token: '123456',
  type: 'sms',
});

使用认证中间件保护路由

// middleware.ts - Protect routes with Supabase Auth
import { createServerClient } from '@supabase/ssr';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: { headers: request.headers },
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll();
        },
        setAll(cookies) {
          cookies.forEach(({ name, value, options }) => {
            response.cookies.set(name, value, options);
          });
        },
      },
    }
  );

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

  // Redirect unauthenticated users to login
  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return response;
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
};

实时订阅

Supabase Realtime 通过 WebSocket 监听数据库变更(INSERT、UPDATE、DELETE)。支持 Postgres Changes、Broadcast 和 Presence。

监听数据库变更

// Listen to all changes on the posts table
const channel = supabase
  .channel('posts-changes')
  .on(
    'postgres_changes',
    {
      event: '*',       // INSERT, UPDATE, DELETE, or *
      schema: 'public',
      table: 'posts',
    },
    (payload) => {
      console.log('Change received:', payload);
      // payload.eventType: INSERT | UPDATE | DELETE
      // payload.new: new row data (INSERT/UPDATE)
      // payload.old: old row data (UPDATE/DELETE)
    }
  )
  .subscribe();

// Listen to inserts on a specific user's posts
const channel2 = supabase
  .channel('my-posts')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'comments',
      filter: 'post_id=eq.42',
    },
    (payload) => {
      console.log('New comment:', payload.new);
    }
  )
  .subscribe();

// Unsubscribe when done
supabase.removeChannel(channel);

Broadcast:发布/订阅消息

// Broadcast: send messages to all connected clients
// Useful for cursor positions, typing indicators, notifications

const channel = supabase.channel('room-1');

// Listen for broadcast messages
channel
  .on('broadcast', { event: 'cursor-move' }, (payload) => {
    console.log('Cursor:', payload.payload);
  })
  .subscribe();

// Send a broadcast message
channel.send({
  type: 'broadcast',
  event: 'cursor-move',
  payload: { x: 100, y: 200, userId: 'abc123' },
});

Presence:追踪在线用户

// Presence: track who is online in a room
const channel = supabase.channel('room-1');

channel
  .on('presence', { event: 'sync' }, () => {
    const state = channel.presenceState();
    console.log('Online users:', Object.keys(state));
  })
  .on('presence', { event: 'join' }, ({ key, newPresences }) => {
    console.log('User joined:', key, newPresences);
  })
  .on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
    console.log('User left:', key, leftPresences);
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({
        userId: user.id,
        username: user.username,
        onlineAt: new Date().toISOString(),
      });
    }
  });

存储:文件上传

Supabase Storage 在 S3 兼容的存储桶中管理文件,支持 CDN 缓存。存储桶可以是公开或私有的。

上传和下载文件

// Upload a file to a public bucket
const file = event.target.files[0];
const fileName = Date.now() + '-' + file.name;

const { data, error } = await supabase.storage
  .from('avatars')
  .upload('public/' + fileName, file, {
    cacheControl: '3600',
    upsert: false,
    contentType: file.type,
  });

// Get public URL
const { data: urlData } = supabase.storage
  .from('avatars')
  .getPublicUrl('public/' + fileName);

console.log('Public URL:', urlData.publicUrl);

// Download a file
const { data: blob, error } = await supabase.storage
  .from('avatars')
  .download('public/' + fileName);

// Create a signed URL for private files (expires in 1 hour)
const { data: signed, error } = await supabase.storage
  .from('documents')
  .createSignedUrl('private/report.pdf', 3600);

// List files in a directory
const { data: files, error } = await supabase.storage
  .from('avatars')
  .list('public', {
    limit: 100,
    offset: 0,
    sortBy: { column: 'created_at', order: 'desc' },
  });

// Delete a file
const { error } = await supabase.storage
  .from('avatars')
  .remove(['public/' + fileName]);

// Image transforms (resize on the fly)
const { data: transformed } = supabase.storage
  .from('avatars')
  .getPublicUrl('public/photo.jpg', {
    transform: {
      width: 200,
      height: 200,
      resize: 'cover',
      quality: 80,
    },
  });

存储安全策略

-- Storage policies for the avatars bucket

-- Allow public read access
CREATE POLICY "Avatar images are publicly accessible"
  ON storage.objects FOR SELECT
  USING (bucket_id = 'avatars');

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

-- Allow users to update their own avatar
CREATE POLICY "Users can update their own avatar"
  ON storage.objects FOR UPDATE
  USING (
    bucket_id = 'avatars'
    AND auth.uid()::text = (storage.foldername(name))[1]
  );

-- Allow users to delete their own avatar
CREATE POLICY "Users can delete their own avatar"
  ON storage.objects FOR DELETE
  USING (
    bucket_id = 'avatars'
    AND auth.uid()::text = (storage.foldername(name))[1]
  );

Edge Functions

Edge Functions 是基于 Deno 的服务端 TypeScript 函数,在边缘网络运行,适合 webhook、第三方 API 集成和自定义服务器逻辑。

创建和部署 Edge Functions

# Create a new Edge Function
supabase functions new send-email

# Project structure:
# supabase/functions/send-email/index.ts

# Deploy the function
supabase functions deploy send-email

# Deploy all functions
supabase functions deploy

# Test locally
supabase functions serve send-email --no-verify-jwt
// supabase/functions/send-email/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

serve(async (req) => {
  // Handle CORS preflight
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders });
  }

  try {
    const { to, subject, body } = await req.json();

    // Call an email API (e.g., Resend)
    const res = await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer ' + Deno.env.get('RESEND_API_KEY'),
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        from: 'noreply@yourapp.com',
        to: to,
        subject: subject,
        html: body,
      }),
    });

    const data = await res.json();

    return new Response(JSON.stringify(data), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 200,
    });
  } catch (err) {
    return new Response(JSON.stringify({ error: err.message }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 500,
    });
  }
});

在 Edge Functions 中使用数据库和认证

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

serve(async (req) => {
  // Create Supabase client with service role (admin access)
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
  );

  // Get user from auth header
  const authHeader = req.headers.get('Authorization')!;
  const token = authHeader.replace('Bearer ', '');
  const { data: { user } } = await supabase.auth.getUser(token);

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

  const { orderId } = await req.json();

  // Use admin client to bypass RLS for server operations
  const { data: order, error } = await supabase
    .from('orders')
    .update({ status: 'processing', processed_at: new Date().toISOString() })
    .eq('id', orderId)
    .eq('user_id', user.id)
    .select()
    .single();

  return new Response(JSON.stringify({ order }), {
    headers: { 'Content-Type': 'application/json' },
  });
});
// Invoke Edge Function from the client
const { data, error } = await supabase.functions.invoke('send-email', {
  body: {
    to: 'user@example.com',
    subject: 'Welcome!',
    body: '<h1>Welcome to our app</h1>',
  },
});

// Invoke with custom headers
const { data, error } = await supabase.functions.invoke('process-order', {
  body: { orderId: 'order_123' },
  headers: { 'x-custom-header': 'value' },
});

TypeScript 集成与类型生成

Supabase 提供一流的 TypeScript 支持。可以使用 Supabase CLI 直接从数据库 schema 生成类型。

从数据库 Schema 生成类型

# Generate types from your remote database
supabase gen types typescript \
  --project-id your-project-id \
  > src/types/database.ts

# Or from a local database
supabase gen types typescript --local > src/types/database.ts

# Add to package.json scripts
# "gen-types": "supabase gen types typescript --project-id abc123 > src/types/database.ts"
// Generated types look like this (src/types/database.ts)
export type Database = {
  public: {
    Tables: {
      profiles: {
        Row: {
          id: string;
          username: string;
          full_name: string | null;
          avatar_url: string | null;
          bio: string | null;
          created_at: string;
          updated_at: string;
        };
        Insert: {
          id: string;
          username: string;
          full_name?: string | null;
          avatar_url?: string | null;
          bio?: string | null;
        };
        Update: {
          username?: string;
          full_name?: string | null;
          avatar_url?: string | null;
          bio?: string | null;
        };
      };
      posts: {
        Row: {
          id: number;
          author_id: string;
          title: string;
          content: string;
          slug: string;
          published: boolean;
          created_at: string;
          updated_at: string;
        };
        Insert: {
          author_id: string;
          title: string;
          content: string;
          slug: string;
          published?: boolean;
        };
        Update: {
          title?: string;
          content?: string;
          slug?: string;
          published?: boolean;
        };
      };
    };
  };
};

在查询中使用生成的类型

// lib/supabase.ts - Typed client
import { createClient } from '@supabase/supabase-js';
import type { Database } from '@/types/database';

export const supabase = createClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Now all queries are fully typed
// data is typed as Database["public"]["Tables"]["posts"]["Row"][]
const { data: posts } = await supabase
  .from('posts')
  .select('id, title, slug, published');

// TypeScript enforces correct insert shape
const { data } = await supabase
  .from('posts')
  .insert({
    author_id: user.id,    // required: string
    title: 'My Post',      // required: string
    content: 'Content',    // required: string
    slug: 'my-post',       // required: string
    // published is optional (defaults to false)
  })
  .select()
  .single();

// Helper types for reuse
type Post = Database['public']['Tables']['posts']['Row'];
type PostInsert = Database['public']['Tables']['posts']['Insert'];
type PostUpdate = Database['public']['Tables']['posts']['Update'];
type Profile = Database['public']['Tables']['profiles']['Row'];

// Use in React components
function PostCard({ post }: { post: Post }) {
  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      <time>{new Date(post.created_at).toLocaleDateString()}</time>
    </div>
  );
}

生产最佳实践

  • 始终在每个表上启用 RLS
  • 使用数据库迁移(supabase db diff)进行所有 schema 变更
  • 在 WHERE、ORDER BY 和 JOIN 子句中使用的列上创建索引
  • 在无服务器环境中使用连接池(Supavisor)
  • 仅在服务端存储 service_role 密钥,永远不要暴露给客户端
  • 使用 Edge Functions 处理敏感操作
  • 为生产项目设置数据库备份和时间点恢复
  • 使用 pg_stat_statements 和 Supabase 仪表板监控查询性能
  • 使用存储转换进行即时图片缩放
  • 在认证端点上实施速率限制
-- Production-ready database setup checklist

-- 1. Enable RLS on ALL tables
DO $$
DECLARE
  t RECORD;
BEGIN
  FOR t IN SELECT tablename FROM pg_tables WHERE schemaname = 'public'
  LOOP
    EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t.tablename);
  END LOOP;
END $$;

-- 2. Create indexes for performance
CREATE INDEX CONCURRENTLY idx_posts_created
  ON public.posts(created_at DESC);

-- 3. Enable pg_stat_statements for query monitoring
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- 4. Check for missing indexes
SELECT
  relname AS table_name,
  seq_scan - idx_scan AS too_many_seq_scans,
  pg_size_pretty(pg_relation_size(relid)) AS table_size
FROM pg_stat_user_tables
WHERE seq_scan - idx_scan > 0
ORDER BY too_many_seq_scans DESC
LIMIT 10;

常见问题

Supabase 是 Firebase 的好替代品吗?

是的。Supabase 提供等效功能但使用 PostgreSQL 而非 NoSQL。更适合需要关系数据和复杂查询的应用。

Supabase 费用多少?

免费层包括 500MB 数据库、1GB 存储、50K 月活用户。Pro 计划 25 美元/月。

可以自托管 Supabase 吗?

可以。Supabase 完全开源,提供 Docker Compose 文件用于自托管。

行级安全如何工作?

RLS 是 PostgreSQL 功能,根据你定义的策略过滤每个查询。使用 auth.uid() 等 SQL 表达式检查条件。

Supabase 支持实时订阅吗?

支持。包括 Postgres Changes、Broadcast 和 Presence 三种模式,均通过 WebSocket 连接。

什么是 Edge Functions?

Edge Functions 是基于 Deno 的服务端函数,在边缘网络运行,处理 webhook、API 集成等任务。

如何从 Supabase 生成 TypeScript 类型?

运行 supabase gen types typescript 命令,为所有表、视图和函数生成类型。

Supabase 能处理生产工作负载吗?

可以。Supabase 基于 PostgreSQL 运行,Pro 和 Enterprise 计划提供连接池、只读副本和备份。

Key Takeaways
  • Supabase 基于 PostgreSQL:支持完整 SQL、JOIN、索引和 ACID 事务
  • 行级安全是主要的访问控制机制:在每个表上启用
  • 认证支持邮箱、OAuth(20+ 提供商)、魔法链接和手机 OTP
  • 实时订阅支持数据库变更、广播和在线状态追踪
  • Edge Functions(基于 Deno)处理服务端逻辑
  • 从 schema 生成 TypeScript 类型实现端到端类型安全
  • 使用连接池、数据库索引和迁移确保生产就绪
𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON FormatterJWTJWT DecoderIDUUID Generator

相关文章

OAuth 2.0 与认证指南:PKCE、JWT、OpenID Connect、RBAC 与安全最佳实践

完整的 OAuth 2.0 与认证指南,涵盖 PKCE 授权码流程、JWT 令牌、OpenID Connect、会话管理、RBAC、社交登录、MFA、令牌刷新、CSRF 防护、速率限制和安全最佳实践。

Next.js 高级指南:App Router、服务端组件、数据获取、中间件与性能优化

完整的 Next.js 高级指南,涵盖 App Router 架构、React 服务端组件、流式 SSR、数据获取模式、中间件、路由处理器、并行和拦截路由、缓存策略、ISR、图片优化和部署最佳实践。

PostgreSQL完整指南:SQL、索引、JSONB和性能优化

掌握PostgreSQL的完整指南。含核心SQL、索引、Node.js pg、Prisma ORM、Python asyncpg、JSONB、全文搜索、窗口函数和性能调优。