一、前期准备
1. 注册 Supabase 账号并创建项目
- 访问 Supabase 官网 注册账号(支持 GitHub / 邮箱登录)。
- 登录后点击 New Project,填写项目信息:
- Project name:项目名称(如
vue-supabase-demo) - Database password:数据库密码(记住!后续连接需要)
- Region:区域(选离你最近的,如
Asia Pacific (Singapore))
- Project name:项目名称(如
- 点击 Create Project,等待约 2 分钟项目初始化完成。
2. 获取 Supabase 项目密钥(关键!)
项目创建完成后,进入项目控制台 → 点击左侧 Settings → API,复制两个核心信息:
Project URL:项目接口地址(如https://abc123.supabase.co)anon public:匿名访问密钥(如eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...)
这两个信息用于前端连接 Supabase,后续会用到。
二、搭建 Vue 项目并集成 Supabase
1. 创建 Vue 3 项目(若已有项目可跳过)
打开终端,执行以下命令创建 Vite + Vue 项目:
# 创建项目
npm create vite@latest vue-supabase-demo -- --template vue
# 进入项目目录
cd vue-supabase-demo
# 安装依赖
npm install
2. 安装 Supabase Vue 客户端
Supabase 官方提供了 Vue 专属的客户端库 @supabase/supabase-js(Vue 3 兼容),安装命令:
npm install @supabase/supabase-js
3. 初始化 Supabase 客户端
为了方便全局使用,我们创建一个 Supabase 实例文件:
- 在
src目录下创建utils/supabase.js文件; - 粘贴以下代码(替换为你自己的
Project URL和anon public密钥):
// src/utils/supabase.js
import { createClient } from '@supabase/supabase-js'
// 你的 Supabase 项目配置(替换成自己的!)
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
// 创建 Supabase 客户端实例
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
- 在项目根目录创建
.env文件,存储环境变量(避免密钥硬编码):
# .env
VITE_SUPABASE_URL=你的 Project URL
VITE_SUPABASE_ANON_KEY=你的 anon public 密钥
4. 验证连接
在 src/App.vue 中引入 supabase 实例,测试是否连接成功:
<!-- src/App.vue -->
<script setup>
import { supabase } from './utils/supabase'
// 测试连接
const testConnection = async () => {
const { data, error } = await supabase.from('test').select('*').limit(1)
if (error) {
console.log('连接成功(测试表不存在属于正常):', error.message)
} else {
console.log('连接成功,数据:', data)
}
}
// 组件挂载时执行测试
testConnection()
</script>
<template>
<h1>Vue + Supabase 测试</h1>
</template>
运行项目 npm run dev,打开浏览器控制台,若显示 连接成功 则说明集成完成!
三、核心功能实现(实战)
下面我们实现 3 个最常用的功能:用户认证(邮箱登录)、数据库 CRUD、文件存储,全程结合 Vue 3 的 Composition API。
1. 用户认证(邮箱 / 密码登录)
Supabase 内置了完整的用户认证系统,支持邮箱、OAuth(GitHub/Google)等,无需自己写后端逻辑。
步骤 1:启用 Supabase 认证功能
- 进入 Supabase 控制台 → 左侧 Authentication → Providers;
- 启用 Email 提供商(默认已启用),无需额外配置。
步骤 2:Vue 前端实现登录 / 注册 / 退出
创建 src/views/Auth.vue,实现完整的认证流程:
<!-- src/views/Auth.vue -->
<script setup>
import { ref } from 'vue'
import { supabase } from '@/utils/supabase'
import { useRouter } from 'vue-router'
const router = useRouter()
// 表单数据
const email = ref('')
const password = ref('')
const error = ref('')
const loading = ref(false)
// 注册用户
const signUp = async () => {
try {
loading.value = true
error.value = ''
// 调用 Supabase 注册接口(自动发送验证邮件)
const { data, error: supabaseError } = await supabase.auth.signUp({
email: email.value,
password: password.value,
})
if (supabaseError) throw supabaseError
alert('注册成功!请查收验证邮件~')
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 登录用户
const signIn = async () => {
try {
loading.value = true
error.value = ''
// 调用 Supabase 登录接口
const { data, error: supabaseError } = await supabase.auth.signInWithPassword({
email: email.value,
password: password.value,
})
if (supabaseError) throw supabaseError
alert('登录成功!')
router.push('/dashboard') // 登录成功跳转到仪表盘
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 退出登录
const signOut = async () => {
await supabase.auth.signOut()
alert('退出成功!')
router.push('/')
}
</script>
<template>
<div class="auth-container">
<h2>Vue + Supabase 认证</h2>
<div class="form-group">
<label>邮箱</label>
<input
type="email"
v-model="email"
placeholder="请输入邮箱"
required
/>
</div>
<div class="form-group">
<label>密码</label>
<input
type="password"
v-model="password"
placeholder="请输入密码(至少6位)"
required
/>
</div>
<div class="error" v-if="error">{{ error }}</div>
<button @click="signUp" :disabled="loading">注册</button>
<button @click="signIn" :disabled="loading" style="margin-left: 10px;">登录</button>
<button @click="signOut" style="margin-left: 10px;">退出</button>
</div>
</template>
<style scoped>
.auth-container {
max-width: 400px;
margin: 50px auto;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
}
.form-group {
margin-bottom: 15px;
}
input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
padding: 8px 16px;
background: #2563eb;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #94a3b8;
cursor: not-allowed;
}
.error {
color: #ef4444;
margin: 10px 0;
}
</style>
步骤 3:监听用户登录状态(全局)
在 src/App.vue 中监听用户认证状态,实现 “登录后显示仪表盘,未登录显示登录页”:
<!-- src/App.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { supabase } from './utils/supabase'
import Auth from './views/Auth.vue'
import Dashboard from './views/Dashboard.vue'
const user = ref(null)
// 监听用户状态变化(登录/退出时触发)
onMounted(() => {
// 获取当前登录用户
user.value = supabase.auth.currentUser
// 监听认证状态变化
supabase.auth.onAuthStateChange((_event, session) => {
user.value = session?.user || null
})
})
</script>
<template>
<div id="app">
<h1>Vue + Supabase 全栈Demo</h1>
<!-- 未登录显示登录页,已登录显示仪表盘 -->
<Auth v-if="!user" />
<Dashboard v-else />
</div>
</template>
2. 数据库 CRUD(核心功能)
Supabase 内置 PostgreSQL 数据库,支持通过前端直接操作(无需后端接口),下面实现 “用户列表” 的增删改查。
步骤 1:创建数据库表(Supabase 控制台)
- 进入 Supabase 控制台 → 左侧 Database → Tables → Create a new table;
- 创建
users表(自定义表名),添加以下字段:id:主键(默认已创建,类型uuid,自增);name:字符串(必填,用户姓名);age:整数(可选,用户年龄);created_at:时间戳(默认已创建,记录创建时间);
- 点击 Save 保存表。
步骤 2:Vue 前端实现 CRUD(Dashboard.vue)
创建 src/views/Dashboard.vue,实现用户列表的增删改查:
<!-- src/views/Dashboard.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { supabase } from '@/utils/supabase'
// 数据状态
const users = ref([])
const newUser = ref({ name: '', age: '' }) // 新增用户表单
const editingUser = ref(null) // 编辑中的用户
const loading = ref(false)
const error = ref('')
// 1. 查询所有用户(读取)
const fetchUsers = async () => {
try {
loading.value = true
error.value = ''
// 调用 Supabase 查询接口:from(表名).select(字段)
const { data, error: supabaseError } = await supabase
.from('users')
.select('*') // 查询所有字段
.order('created_at', { ascending: false }) // 按创建时间倒序
if (supabaseError) throw supabaseError
users.value = data
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 2. 新增用户(创建)
const addUser = async () => {
if (!newUser.value.name) {
error.value = '用户名不能为空!'
return
}
try {
loading.value = true
error.value = ''
// 调用 Supabase 插入接口:from(表名).insert(数据)
const { data, error: supabaseError } = await supabase
.from('users')
.insert([newUser.value]) // 插入一条数据(数组形式支持批量插入)
.select() // 插入后返回新增数据
if (supabaseError) throw supabaseError
users.value.unshift(data[0]) // 新增数据添加到列表头部
newUser.value = { name: '', age: '' } // 重置表单
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 3. 编辑用户(更新)
const updateUser = async () => {
if (!editingUser.value.name) {
error.value = '用户名不能为空!'
return
}
try {
loading.value = true
error.value = ''
// 调用 Supabase 更新接口:from(表名).update(数据).eq(条件)
const { error: supabaseError } = await supabase
.from('users')
.update({ name: editingUser.value.name, age: editingUser.value.age })
.eq('id', editingUser.value.id) // 按 id 匹配要更新的记录
if (supabaseError) throw supabaseError
// 更新列表中的数据
const index = users.value.findIndex(u => u.id === editingUser.value.id)
users.value[index] = { ...editingUser.value }
editingUser.value = null // 退出编辑状态
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 4. 删除用户(删除)
const deleteUser = async (userId) => {
if (!confirm('确定要删除吗?')) return
try {
loading.value = true
error.value = ''
// 调用 Supabase 删除接口:from(表名).delete().eq(条件)
const { error: supabaseError } = await supabase
.from('users')
.delete()
.eq('id', userId) // 按 id 匹配要删除的记录
if (supabaseError) throw supabaseError
// 从列表中移除数据
users.value = users.value.filter(u => u.id !== userId)
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 组件挂载时查询所有用户
onMounted(() => {
fetchUsers()
})
</script>
<template>
<div class="dashboard">
<h2>用户管理仪表盘(已登录:{{ user.email }})</h2>
<button @click="supabase.auth.signOut()">退出登录</button>
<!-- 新增用户表单 -->
<div class="add-user">
<h3>新增用户</h3>
<input
type="text"
v-model="newUser.name"
placeholder="输入姓名"
:disabled="loading"
/>
<input
type="number"
v-model="newUser.age"
placeholder="输入年龄"
:disabled="loading"
/>
<button @click="addUser" :disabled="loading">添加</button>
</div>
<!-- 错误提示 -->
<div class="error" v-if="error">{{ error }}</div>
<!-- 用户列表 -->
<div class="user-list" v-if="!loading">
<h3>用户列表(共 {{ users.length }} 人)</h3>
<table border="1" cellpadding="8" cellspacing="0">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="u in users" :key="u.id">
<td>{{ u.id }}</td>
<td>
<input
v-if="editingUser?.id === u.id"
v-model="editingUser.name"
type="text"
/>
<span v-else>{{ u.name }}</span>
</td>
<td>
<input
v-if="editingUser?.id === u.id"
v-model="editingUser.age"
type="number"
/>
<span v-else>{{ u.age || '无' }}</span>
</td>
<td>{{ new Date(u.created_at).toLocaleString() }}</td>
<td>
<button
@click="editingUser = u"
v-if="editingUser?.id !== u.id"
:disabled="loading"
>
编辑
</button>
<button
@click="updateUser()"
v-if="editingUser?.id === u.id"
:disabled="loading"
>
保存
</button>
<button
@click="deleteUser(u.id)"
style="background: #ef4444; margin-left: 5px;"
:disabled="loading"
>
删除
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div v-else>加载中...</div>
</div>
</template>
<style scoped>
.dashboard {
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
.add-user {
margin: 20px 0;
display: flex;
gap: 10px;
align-items: center;
}
.add-user input {
padding: 8px;
flex: 1;
max-width: 200px;
}
.user-list table {
width: 100%;
border-collapse: collapse;
}
.user-list th, .user-list td {
text-align: left;
}
button {
padding: 6px 12px;
background: #2563eb;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #94a3b8;
cursor: not-allowed;
}
.error {
color: #ef4444;
margin: 10px 0;
}
</style>
3. 文件存储(可选,如上传头像)
Supabase 提供文件存储服务,支持上传图片、文档等文件,下面实现 “上传用户头像” 功能。
步骤 1:创建存储桶(Supabase 控制台)
- 进入 Supabase 控制台 → 左侧 Storage → Create a bucket;
- 桶名称:
avatars(存储用户头像); - 权限:勾选 Public(公开访问,方便前端展示头像);
- 点击 Create 保存。
步骤 2:Vue 前端实现文件上传
在 Dashboard.vue 中添加头像上传功能(修改部分代码):
<!-- Dashboard.vue 新增/修改以下代码 -->
<script setup>
// 新增:文件上传相关
const avatarFile = ref(null) // 选中的文件
const avatarUrl = ref('') // 上传后的文件URL
// 上传头像
const uploadAvatar = async () => {
if (!avatarFile.value) {
error.value = '请选择文件!'
return
}
try {
loading.value = true
error.value = ''
// 文件名:使用当前时间戳 + 原文件名(避免重复)
const fileName = `${Date.now()}-${avatarFile.value.name}`
// 调用 Supabase 上传接口:storage.from(桶名).upload(文件路径, 文件)
const { data, error: supabaseError } = await supabase.storage
.from('avatars') // 桶名
.upload(`public/${fileName}`, avatarFile.value, {
cacheControl: '3600', // 缓存1小时
upsert: false // 不覆盖已存在的文件
})
if (supabaseError) throw supabaseError
// 获取上传后的文件公共URL
const { data: urlData } = supabase.storage
.from('avatars')
.getPublicUrl(data.path) // data.path 是文件在桶中的路径
avatarUrl.value = urlData.publicUrl
alert('头像上传成功!')
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
</script>
<template>
<!-- 新增:头像上传区域 -->
<div class="upload-avatar">
<h3>上传头像</h3>
<input type="file" accept="image/*" @change="avatarFile = $event.target.files[0]" />
<button @click="uploadAvatar" :disabled="loading">上传</button>
<div v-if="avatarUrl" style="margin-top: 10px;">
<img :src="avatarUrl" alt="头像" style="width: 100px; height: 100px; object-fit: cover;" />
</div>
</div>
</template>
<style scoped>
/* 新增:上传区域样式 */
.upload-avatar {
margin: 20px 0;
padding: 10px;
border: 1px dashed #ddd;
}
</style>
四、关键注意事项(避坑指南)
-
权限控制:
- 匿名用户默认没有数据库 / 存储的操作权限!如果需要匿名用户操作,需在 Supabase 控制台 → Authentication → Policies 中添加权限策略(如允许匿名用户查询
users表)。 - 生产环境建议使用 “已登录用户” 权限(通过
auth.uid()验证用户身份)。
- 匿名用户默认没有数据库 / 存储的操作权限!如果需要匿名用户操作,需在 Supabase 控制台 → Authentication → Policies 中添加权限策略(如允许匿名用户查询
-
环境变量:
- Vue 3 + Vite 中,环境变量必须以
VITE_开头,否则无法在前端访问。 - 不要将
service_role密钥(管理员密钥)暴露在前端!前端只能用anon public密钥。
- Vue 3 + Vite 中,环境变量必须以
-
错误处理:
- 所有 Supabase 接口都是异步的,必须用
try/catch捕获错误(如网络错误、权限不足、数据格式错误)。
- 所有 Supabase 接口都是异步的,必须用
-
数据验证:
- 前端需做基础数据验证(如必填字段、数据类型),后端可在 Supabase 控制台设置数据库约束(如非空、字段长度)。
五、扩展学习(后续可探索)
- OAuth 登录:Supabase 支持 GitHub、Google、Facebook 等 OAuth 登录,只需在控制台启用对应提供商,前端调用
supabase.auth.signInWithOAuth({ provider: 'github' })即可。 - 实时数据同步:Supabase 支持实时订阅数据库变化,通过
supabase.from('users').on('INSERT', (payload) => { ... }).subscribe()实现 “别人新增用户,你的页面实时更新”。 - 行级安全策略(RLS):生产环境核心!通过 RLS 控制用户只能操作自己的数据(如用户只能修改自己创建的记录)。
- Supabase 函数:类似云函数,可在 Supabase 中编写 PostgreSQL 函数,实现复杂业务逻辑(如数据计算、第三方接口调用)。
总结
Vue + Supabase 是一套 “快速开发全栈应用” 的组合,核心优势是:
- 前端用 Vue 3 写界面,简单高效;
- 后端无需写代码,Supabase 提供数据库、认证、存储等一站式服务;
- 开发速度快,适合原型开发、小型应用、个人项目。
按照上面的步骤,你已经实现了一个完整的全栈应用(认证 + 数据库 CRUD + 文件存储),可以在此基础上扩展更多功能!
1318

被折叠的 条评论
为什么被折叠?



