跳到主要内容

Supabase

Supabase 被称为 "开源的 Firebase",它基于 PostgreSQL 提供了全套的后端服务,包括数据库、身份认证、实时监控、存储以及边缘函数。

1. 核心特性

  • PostgreSQL 数据库:完整的关系型数据库,具有强大的扩展性和 SQL 支持。
  • 身份认证 (Auth):内置用户注册、登录(支持第三方 OAuth)以及权限管理。
  • Row Level Security (RLS):基于 SQL 指令的精细权限控制,直接在数据库层保护数据。
  • 实时 (Realtime):通过 Websockets 监听数据库变更。
  • 存储 (Storage):用于存储和管理大型文件(图片、视频)。

2. 快速集成

安装 SDK

npm install @supabase/supabase-js

初始化客户端

建议创建一个单独的 lib/supabase.js 文件:

import { createClient } from "@supabase/supabase-js";

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

3. 基础操作事例 (CRUD)

读取数据

const { data, error } = await supabase
.from("posts")
.select("*")
.eq("status", "published");

插入数据

const { data, error } = await supabase
.from("posts")
.insert([{ title: "Hello World", user_id: "..." }]);

更新数据

const { data, error } = await supabase
.from("posts")
.update({ title: "New Title" })
.match({ id: 123 });

4. 身份认证事例

// 登录
const { user, error } = await supabase.auth.signInWithPassword({
email: "example@email.com",
password: "password123",
});

// 监听登录状态
supabase.auth.onAuthStateChange((event, session) => {
console.log(event, session);
});

5. 在 React 中的实践

身份认证 Context

为了在应用全局访问用户信息,建议创建一个 Auth Context:

import { createContext, useContext, useEffect, useState } from "react";
import { supabase } from "./supabaseClient";

const AuthContext = createContext({});

export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);

useEffect(() => {
// 获取当前 Session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
});

// 监听状态变化 (登录/登出)
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});

return () => subscription.unsubscribe();
}, []);

return (
<AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
);
};

export const useAuth = () => useContext(AuthContext);

数据获取与实时订阅

结合 useEffectuseState 实现数据的自动刷新。

import { useEffect, useState } from "react";
import { supabase } from "./supabaseClient";

function ChatRoom() {
const [messages, setMessages] = useState([]);

useEffect(() => {
// 1. 初始获取数据
fetchMessages();

// 2. 开启实时订阅
const channel = supabase
.channel("schema-db-changes")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "messages" },
(payload) => {
setMessages((prev) => [...prev, payload.new]);
}
)
.subscribe();

return () => supabase.removeChannel(channel);
}, []);

const fetchMessages = async () => {
const { data } = await supabase.from("messages").select("*");
if (data) setMessages(data);
};

return (
<ul>
{messages.map((m) => (
<li key={m.id}>{m.content}</li>
))}
</ul>
);
}

6. RLS 权限策略简介

在 Supabase 控制面板中,你可以启用 RLS 并定义 Policy。例如,只允许用户读取自己的数据:

create policy "Individuals can view their own tasks."
on tasks for select
using ( auth.uid() = user_id );

7. Next.js 深度集成

Supabase 推荐使用官方的 @supabase/ssr 包来处理 Next.js 中的认证和数据获取,它支持在不同运行时(Server, Client, Edge)自动同步 Cookie。

安装依赖

npm install @supabase/ssr @supabase/supabase-js

Server Components 中获取数据

在 App Router 中,可以直接在异步函数中调用:

// app/posts/page.tsx
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export default async function Page() {
const cookieStore = cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) { return cookieStore.get(name)?.value },
},
}
)

const { data: posts } = await supabase.from('posts').select('*')

return (
<main>
{posts?.map(post => <div key={post.id}>{post.title}</div>)}
</main>
)
}

在 Middleware 中保护路由

利用 Middleware 检查 Session,未登录时重定向到登录页。

// middleware.ts
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { NextResponse, 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: {
get(name: string) {
return request.cookies.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
response.cookies.set({ name, value, ...options });
},
remove(name: string, options: CookieOptions) {
response.cookies.set({ name, value: "", ...options });
},
},
}
);

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

// 如果访问 /dashboard 且未登录,重定向到 /login
if (!user && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}

return response;
}

Route Handlers (API 路由)

// app/api/comment/route.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
const cookieStore = cookies();
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
},
}
);

const { content } = await request.json();
const { data, error } = await supabase.from("comments").insert({ content });

return NextResponse.json({ data, error });
}

参考资源: