1. 文章/

用 Supabase 打造全栈应用后端

用 Supabase 打造全栈应用后端 #

Supabase 是一个开源的 Firebase 替代方案,提供了 PostgreSQL 数据库、认证、存储、实时订阅等全套后端服务。本文将带你深入了解如何用 Supabase 快速搭建全栈应用后端。

什么是 Supabase #

Supabase 提供了以下核心功能:

  • PostgreSQL 数据库 — 功能强大的关系型数据库
  • 身份认证 — 支持邮箱、社交登录、Magic Link
  • 对象存储 — 存储文件、图片、视频
  • 实时订阅 — 数据变更实时推送
  • Edge Functions — 服务端函数
  • Row Level Security — 行级安全策略

Supabase 让独立开发者也能拥有企业级的后端基础设施。

创建 Supabase 项目 #

步骤一:注册并创建项目 #

  1. 访问 supabase.com 注册账号
  2. 点击 “New Project” 创建新项目
  3. 选择离你最近的数据库区域(推荐东亚地区选择 Singapore)
  4. 设置数据库密码并记住它

步骤二:安装客户端 #

npm install @supabase/supabase-js

步骤三:初始化客户端 #

// lib/supabase.ts
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)

数据库设计 #

创建表结构 #

在 Supabase Dashboard 的 SQL Editor 中执行:

-- 文章表
create table posts (
  id uuid default gen_random_uuid() primary key,
  slug text unique not null,
  title text not null,
  content text,
  excerpt text,
  category text,
  status text default 'draft',
  cover_image_url text,
  word_count integer default 0,
  published_at timestamptz,
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

-- 标签表
create table tags (
  id uuid default gen_random_uuid() primary key,
  name text unique not null
);

-- 文章-标签关联表
create table post_tags (
  post_id uuid references posts(id) on delete cascade,
  tag_id uuid references tags(id) on delete cascade,
  primary key (post_id, tag_id)
);

CRUD 操作 #

查询文章列表 #

async function getPublishedPosts() {
  const { data, error } = await supabase
    .from("posts")
    .select("*")
    .eq("status", "published")
    .order("published_at", { ascending: false })

  if (error) throw error
  return data
}

根据 slug 获取单篇文章 #

async function getPostBySlug(slug: string) {
  const { data, error } = await supabase
    .from("posts")
    .select(`*, post_tags ( tags ( name ) )`)
    .eq("slug", slug)
    .single()

  if (error) throw error
  return data
}

创建新文章 #

async function createPost(post: {
  title: string; slug: string; content: string; excerpt: string
}) {
  const { data, error } = await supabase
    .from("posts").insert(post).select().single()
  if (error) throw error
  return data
}

身份认证 #

实现登录功能 #

// 邮箱密码登录
async function signIn(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({ email, password })
  if (error) throw error
  return data
}

// GitHub OAuth 登录
async function signInWithGitHub() {
  const { data, error } = await supabase.auth.signInWithOAuth({ provider: "github" })
  if (error) throw error
  return data
}

Row Level Security (RLS) #

-- 启用 RLS
alter table posts enable row level security;

-- 所有人可以读取已发布文章
create policy "Published posts are viewable by everyone"
  on posts for select using (status = 'published');

-- 只有认证用户可以创建文章
create policy "Authenticated users can create posts"
  on posts for insert to authenticated with check (true);

注意: RLS 是 Supabase 安全的核心。务必为每个表启用 RLS 并配置合适的策略。

对象存储 #

async function uploadImage(file: File) {
  const fileName = `${Date.now()}-${file.name}`
  const { data, error } = await supabase.storage
    .from("images").upload(fileName, file)
  if (error) throw error

  const { data: urlData } = supabase.storage
    .from("images").getPublicUrl(data.path)
  return urlData.publicUrl
}

实时订阅 #

const channel = supabase
  .channel("posts-changes")
  .on("postgres_changes",
    { event: "*", schema: "public", table: "posts" },
    (payload) => { console.log("Change received:", payload) }
  )
  .subscribe()

总结 #

Supabase 为全栈开发提供了一站式解决方案。相比自己搭建后端服务,Supabase 能让你把更多精力放在产品本身。

推荐资源 #