使用 Svelte 构建用户管理应用
本教程演示如何构建一个基本的用户管理应用。该应用对用户进行身份验证和识别,将他们的个人资料信息存储在数据库中,并允许用户登录、更新他们的个人资料详细信息以及上传个人资料照片。该应用使用
- Supabase 数据库 - 一个用于存储用户数据的 Postgres 数据库,以及 行级别安全,以保护数据并确保用户只能访问他们自己的信息。
- Supabase Auth - 允许用户注册和登录。
- Supabase Storage - 允许用户上传个人资料照片。

如果在阅读本指南时遇到问题,请参考 GitHub 上的完整示例。
项目设置#
在开始构建之前,您需要设置数据库和 API。您可以通过在 Supabase 中启动一个新项目,然后在数据库中创建一个“schema”来完成此操作。
创建项目#
- 在 Supabase 控制面板中创建一个新项目。
- 输入您的项目详细信息。
- 等待新的数据库启动。
设置数据库 schema#
现在设置数据库 schema。您可以使用 SQL 编辑器中的“用户管理 Starter”快速入门,或者您可以复制/粘贴下面的 SQL 并运行它。
获取 API 详细信息#
现在您已经创建了一些数据库表,您可以使用自动生成的 API 插入数据。
为此,您需要从 项目 Connect 对话框中获取项目 URL 和密钥。
API 密钥的更改
Supabase 正在更改密钥的工作方式,以提高项目安全性和开发人员体验。您可以 阅读完整的公告,但在过渡期间,您可以使用当前的 anon 和 service_role 密钥以及新的可发布密钥,格式为 sb_publishable_xxx,它将取代旧的密钥。
在大多数情况下,你可以从 项目的 连接 对话框 获取正确的密钥,但如果你想要特定的密钥,你可以在 项目的设置页面中的 API 密钥部分 找到所有密钥
- 对于旧版密钥,从 旧版 API 密钥 选项卡中复制
anon密钥用于客户端操作,并复制service_role密钥用于服务器端操作。 - 对于新密钥,打开 API 密钥 选项卡,如果您还没有可发布密钥,请单击 创建新的 API 密钥,并复制 可发布密钥 部分中的值。
阅读 API 密钥文档 以全面了解所有密钥类型及其用途。
构建应用#
开始从头构建 Svelte 应用。
初始化 Svelte 应用#
您可以使用 Vite Svelte TypeScript 模板来初始化一个名为 supabase-svelte 的应用
1npm create vite@latest supabase-svelte -- --template svelte-ts2cd supabase-svelte3npm install安装唯一的附加依赖项:supabase-js
1npm install @supabase/supabase-js最后,将环境变量保存在一个 .env 文件中。您只需要之前复制的 API URL 和密钥 即可。
1VITE_SUPABASE_URL=YOUR_SUPABASE_URL2VITE_SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY现在您已经准备好 API 凭据,创建一个辅助文件来初始化 Supabase 客户端。这些变量将在浏览器中公开,这没关系,因为您已经在数据库上启用了 行级别安全。
src/supabaseClient.ts
1import { createClient } from '@supabase/supabase-js'23const supabaseUrl = import.meta.env.VITE_SUPABASE_URL4const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY56export const supabase = createClient(supabaseUrl, supabasePublishableKey)应用样式 (可选)#
可选地,更新 CSS 文件 src/app.css 以使应用看起来更美观。您可以在 GitHub 上找到此文件的完整内容。
设置登录组件#
设置一个 Svelte 组件来管理登录和注册。它使用 Magic Links,因此用户无需使用密码即可通过电子邮件登录。
src/lib/Auth.svelte
1<script lang="ts">2 import { supabase } from "../supabaseClient";34 let loading = $state(false);5 let email = $state("");67 const handleLogin = async () => {8 try {9 loading = true;10 const { error } = await supabase.auth.signInWithOtp({ email });11 if (error) throw error;12 alert("Check your email for login link!");13 } catch (error) {14 if (error instanceof Error) {15 alert(error.message);16 }17 } finally {18 loading = false;19 }20 };21</script>2223<div class="row flex-center flex">24 <div class="col-6 form-widget" aria-live="polite">25 <h1 class="header">Supabase + Svelte</h1>26 <p class="description">Sign in via magic link with your email below</p>27 <form class="form-widget" onsubmit={(e) => { e.preventDefault(); handleLogin(); }}>28 <div>29 <label for="email">Email</label>30 <input31 id="email"32 class="inputField"33 type="email"34 placeholder="Your email"35 bind:value={email}36 />37 </div>38 <div>39 <button40 type="submit"41 class="button block"42 aria-live="polite"43 disabled={loading}44 >45 <span>{loading ? "Loading" : "Send magic link"}</span>46 </button>47 </div>48 </form>49 </div>50</div>账户页面#
用户登录后,允许他们编辑个人资料详细信息并管理他们的帐户。为此创建一个名为 Account.svelte 的新组件。
1<script lang="ts">2 import { onMount } from "svelte";3 import type { AuthSession } from "@supabase/supabase-js";4 import { supabase } from "../supabaseClient";56 // ...78 interface Props {9 session: AuthSession;10 }1112 let { session }: Props = $props();1314 // ...1516 let username = $state<string | null>(null);17 let website = $state<string | null>(null);18 let avatarUrl = $state<string | null>(null);1920 onMount(() => {21 getProfile();22 });2324 const getProfile = async () => {25 try {26 loading = true;27 const { user } = session;2829 const { data, error, status } = await supabase30 .from("profiles")31 .select("username, website, avatar_url")32 .eq("id", user.id)33 .single();3435 if (error && status !== 406) throw error;3637// ...383940 if (data) {41 username = data.username;42 website = data.website;43 avatarUrl = data.avatar_url;44 }45 } catch (error) {46 if (error instanceof Error) {47 alert(error.message);48 }49 } finally {50 loading = false;51 }52 };5354 const updateProfile = async () => {55 try {56 loading = true;57 const { user } = session;585960 // ...6162 id: user.id,63 username,64 website,65 avatar_url: avatarUrl,66 updated_at: new Date().toISOString(),67 };6869 const { error } = await supabase.from("profiles").upsert(updates);7071 if (error) {72 throw error;73 }74 } catch (error) {75 if (error instanceof Error) {76 alert(error.message);77 }78 } finally {79 loading = false;80 }8182// ...8384</script>8586<form onsubmit={(e) => { e.preventDefault(); updateProfile(); }} class="form-widget">87 <div>Email: {session.user.email}</div>88 <div>89 <Avatar bind:url={avatarUrl} size={150} onupload={updateProfile} />90 <label for="username">Name</label>91 <input id="username" type="text" bind:value={username} />92 </div>93 <div>94 <label for="website">Website</label>95 <input id="website" type="text" bind:value={website} />96 </div>97 <div>98 <button type="submit" class="button primary block" disabled={loading}>99 {loading ? "Saving ..." : "Update profile"}100 </button>101 </div>102 <button103 type="button"104 class="button block"105 onclick={() => supabase.auth.signOut()}106 >107 Sign Out108 </button>109</form>启动!#
现在您已经准备好所有组件,请更新 App.svelte
src/App.svelte
1<script lang="ts">2 import { onMount } from 'svelte'3 import { supabase } from './supabaseClient'4 import type { AuthSession } from '@supabase/supabase-js'5 import Account from './lib/Account.svelte'6 import Auth from './lib/Auth.svelte'78 let session = $state<AuthSession | null>(null)910 onMount(() => {11 supabase.auth.getSession().then(({ data }) => {12 session = data.session13 })1415 supabase.auth.onAuthStateChange((_event, _session) => {16 session = _session17 })18 })19</script>2021<div class="container" style="padding: 50px 0 100px 0">22 {#if !session}23 <Auth />24 {:else}25 <Account {session} />26 {/if}27</div>完成此操作后,在终端窗口中运行以下命令
1npm run dev然后打开浏览器到 localhost:5173,您应该会看到完成的应用。
Svelte 使用 Vite,默认端口是 5173,Supabase 使用 端口 3000。要更改 Supabase 的重定向端口,请转到:身份验证 > URL 配置 并将 站点 URL 更改为 https://:5173/

奖励:个人资料照片#
每个 Supabase 项目都配置了 存储,用于管理大型文件,如照片和视频。
创建一个上传小部件#
为用户创建一个头像,以便他们可以上传个人资料照片。首先创建一个新组件
src/lib/Avatar.svelte
1<script lang="ts">2 import { supabase } from "../supabaseClient";34 interface Props {5 size: number;6 url?: string | null;7 onupload?: () => void;8 }910 let { size, url = $bindable(null), onupload }: Props = $props();1112 let avatarUrl = $state<string | null>(null);13 let uploading = $state(false);14 let files = $state<FileList>();1516 const downloadImage = async (path: string) => {17 try {18 const { data, error } = await supabase.storage19 .from("avatars")20 .download(path);2122 if (error) {23 throw error;24 }2526 const url = URL.createObjectURL(data);27 avatarUrl = url;28 } catch (error) {29 if (error instanceof Error) {30 console.log("Error downloading image: ", error.message);31 }32 }33 };3435 const uploadAvatar = async () => {36 try {37 uploading = true;3839 if (!files || files.length === 0) {40 throw new Error("You must select an image to upload.");41 }4243 const file = files[0];44 const fileExt = file.name.split(".").pop();45 const filePath = `${Math.random()}.${fileExt}`;4647 const { error } = await supabase.storage48 .from("avatars")49 .upload(filePath, file);5051 if (error) {52 throw error;53 }5455 url = filePath;56 onupload?.();57 } catch (error) {58 if (error instanceof Error) {59 alert(error.message);60 }61 } finally {62 uploading = false;63 }64 };6566 $effect(() => {67 if (url) downloadImage(url);68 });69</script>7071<div style="width: {size}px" aria-live="polite">72 {#if avatarUrl}73 <img74 src={avatarUrl}75 alt={avatarUrl ? "Avatar" : "No image"}76 class="avatar image"77 style="height: {size}px, width: {size}px"78 />79 {:else}80 <div class="avatar no-image" style="height: {size}px, width: {size}px"></div>81 {/if}82 <div style="width: {size}px">83 <label class="button primary block" for="single">84 {uploading ? "Uploading ..." : "Upload avatar"}85 </label>86 <span style="display:none">87 <input88 type="file"89 id="single"90 accept="image/*"91 bind:files92 onchange={uploadAvatar}93 disabled={uploading}94 />95 </span>96 </div>97</div>添加新的小部件#
然后您可以将小部件添加到 Account 页面
1<script lang="ts">23 // ...45 import Avatar from "./Avatar.svelte";67 // ...89 } finally {10 loading = false;11 }1213 // ...1415 };1617 // ...1819 </div>20 <button21 type="button"22 class="button block"23 onclick={() => supabase.auth.signOut()}24 >25 Sign Out26 </button>27</form>此时,您已经拥有一个功能齐全的应用!