快速入门: SvelteKit
介绍
这个例子提供了建立一个基本用户管理应用程序的步骤。它包括。
- MemFire Cloud Database:一个用于存储用户数据的Postgres数据库。
- MemFire Cloud Auth:用户可以用魔法链接登录(没有密码,只有电子邮件)。
- MemFire Cloud 存储:用户可以上传照片。
- 行级安全:数据受到保护,个人只能访问自己的数据。
- 即时APIs。当你创建你的数据库表时,API将自动生成。
在本指南结束时,你将拥有一个允许用户登录和更新一些基本档案细节的应用程序。
GitHub#
如果你在阅读指南时遇到困难,请参考此版本。
项目设置
在我们开始构建之前,我们要设置我们的数据库和API。这就像在Supabase中启动一个新项目一样简单 然后在数据库中创建一个 "模式"。
创建一个项目
- 进入MemFire Cloud。
- 点击 "新项目"。
- 输入你的项目细节。
- 等待新数据库的启动。
设置数据库模式
现在我们要设置数据库模式。我们可以使用SQL编辑器中的 "用户管理"的模板快速启动。 或者你可以直接复制/粘贴下面的SQL,然后自己运行它。
- 进入仪表版中的SQL编辑器页面。
- 点击 用户管理的模板。
- 点击 运行。
获取API密钥#
现在你已经创建了一些数据库表,你已经准备好使用自动生成的API插入数据。
我们只需要从API设置中获得URL和anon
密钥。
- 进入仪表板中的设置页面。
- 单击侧边栏中的API。
- 在这个页面上找到你的API
URL
、anon
和service_role
键。
构建应用程序
让我们从头开始构建Svelte应用程序。
初始化一个Svelte应用程序#
我们可以使用SvelteKit骨架项目来初始化
一个名为 supabase-sveltekit
的应用程序(本教程中你不需要TypeScript、ESLint、Prettier或Playwright)。
npm init svelte@next supabase-sveltekit cd supabase-sveltekit npm install
然后让我们安装唯一的额外依赖:supabase-js
npm install @supabase/supabase-js
最后,我们要把环境变量保存在.env
中。
我们所需要的是SUPABASE_URL
和你[早些时候]复制的SUPABASE_KEY
键(#get-the-api-keys)。
.envPUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL" PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY"
现在我们已经有了API凭证,让我们创建一个辅助文件来初始化Supabase客户端。这些变量将被暴露在 在浏览器上,这完全没有问题,因为我们的数据库已经启用了行级安全。
src/lib/supabaseClient.ts1import { createClient } from '@supabase/auth-helpers-sveltekit' 2import { env } from '$env/dynamic/public' 3 4export const supabase = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY)
还有一个可选的步骤是更新CSS文件public/global.css
以使应用程序看起来漂亮。
你可以找到这个文件的全部内容这里。
Supabase 认证帮助程序#
SvelteKit是一个高度通用的框架,在构建时提供预渲染(SSG),在请求时提供服务器端渲染(SSR),API路由等。
在所有这些不同的环境中对你的用户进行认证是很有挑战性的,这就是为什么我们创建了Supabase Auth Helpers来使SvelteKit内的用户管理和数据获取尽可能简单。
安装SvelteKit的Auth助手。
npm install @supabase/auth-helpers-sveltekit
更新你的src/routes/+layout.svelte
。
src/routes/+layout.svelte1<script lang="ts"> 2 import { supabase } from '$lib/supabaseClient' 3 import { invalidate } from '$app/navigation' 4 import { onMount } from 'svelte' 5 import './styles.css' 6 7 onMount(() => { 8 const { 9 data: { subscription }, 10 } = supabase.auth.onAuthStateChange(() => { 11 invalidate('supabase:auth') 12 }) 13 14 return () => { 15 subscription.unsubscribe() 16 } 17 }) 18</script> 19 20<div class="container" style="padding: 50px 0 100px 0"> 21 <slot /> 22</div>
创建一个新的src/routes/+layout.ts
文件,在客户端处理会话。
src/routes/+layout.ts1import type { LayoutLoad } from './$types' 2import { getSupabase } from '@supabase/auth-helpers-sveltekit' 3 4export const load: LayoutLoad = async (event) => { 5 const { session } = await getSupabase(event) 6 return { session } 7}
创建一个新的src/routes/+layout.server.ts
文件,在服务器端处理会话。
src/routes/+layout.server.ts1import type { LayoutServerLoad } from './$types'
2import { getServerSession } from '@supabase/auth-helpers-sveltekit'
3
4export const load: LayoutServerLoad = async (event) => {
5 return {
6 session: await getServerSession(event),
7 }
8}
请确保创建src/hooks.client.ts
和src/hooks.server.ts
,以便在客户端和服务器端启动auth帮助器。
src/hooks.client.ts1import '$lib/supabaseClient'
src/hooks.server.ts1import '$lib/supabaseClient'
设置一个登录组件
让我们建立一个Svelte组件来管理登录和注册。我们将使用Magic Links,这样用户就可以用他们的电子邮件登录,而无需使用密码。
src/routes/Auth.svelte1<script lang="ts"> 2 import { supabase } from '$lib/supabaseClient' 3 4 let loading = false 5 let email: string 6 7 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 the login link!') 13 } catch (error) { 14 if (error instanceof Error) { 15 alert(error.message) 16 } 17 } finally { 18 loading = false 19 } 20 } 21</script> 22 23<form class="row flex-center flex" on:submit|preventDefault="{handleLogin}"> 24 <div class="col-6 form-widget"> 25 <h1 class="header">Supabase + SvelteKit</h1> 26 <p class="description">Sign in via magic link with your email below</p> 27 <div> 28 <input class="inputField" type="email" placeholder="Your email" bind:value="{email}" /> 29 </div> 30 <div> 31 <input type="submit" class="button block" value={loading ? 'Loading' : 'Send magic link'} 32 disabled={loading} /> 33 </div> 34 </div> 35</form>
账户组件
在用户登录后,他们需要能够编辑他们的个人资料细节和管理他们的账户。
创建一个新的Account.svelte
组件来处理这个功能。
src/routes/Account.svelte1<script lang="ts"> 2 import { onMount } from 'svelte' 3 import type { AuthSession } from '@supabase/supabase-js' 4 import { supabase } from '$lib/supabaseClient' 5 6 export let session: AuthSession 7 8 let loading = false 9 let username: string | null = null 10 let website: string | null = null 11 let avatarUrl: string | null = null 12 13 onMount(() => { 14 getProfile() 15 }) 16 17 const getProfile = async () => { 18 try { 19 loading = true 20 const { user } = session 21 22 const { data, error, status } = await supabase 23 .from('profiles') 24 .select(`username, website, avatar_url`) 25 .eq('id', user.id) 26 .single() 27 28 if (data) { 29 username = data.username 30 website = data.website 31 avatarUrl = data.avatar_url 32 } 33 34 if (error && status !== 406) throw error 35 } catch (error) { 36 if (error instanceof Error) { 37 alert(error.message) 38 } 39 } finally { 40 loading = false 41 } 42 } 43 44 async function updateProfile() { 45 try { 46 loading = true 47 const { user } = session 48 49 const updates = { 50 id: user.id, 51 username, 52 website, 53 avatar_url: avatarUrl, 54 updated_at: new Date(), 55 } 56 57 let { error } = await supabase.from('profiles').upsert(updates) 58 59 if (error) throw error 60 } catch (error) { 61 if (error instanceof Error) { 62 alert(error.message) 63 } 64 } finally { 65 loading = false 66 } 67 } 68 69 async function signOut() { 70 try { 71 loading = true 72 let { error } = await supabase.auth.signOut() 73 if (error) throw error 74 } catch (error) { 75 if (error instanceof Error) { 76 alert(error.message) 77 } 78 } finally { 79 loading = false 80 } 81 } 82</script> 83 84<form class="form-widget" on:submit|preventDefault="{updateProfile}"> 85 <div> 86 <label for="email">Email</label> 87 <input id="email" type="text" value="{session.user.email}" disabled /> 88 </div> 89 <div> 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="website" bind:value="{website}" /> 96 </div> 97 98 <div> 99 <input type="submit" class="button block primary" value={loading ? 'Loading...' : 'Update'} 100 disabled={loading} /> 101 </div> 102 103 <div> 104 <button class="button block" on:click="{signOut}" disabled="{loading}">Sign Out</button> 105 </div> 106</form>
启动
现在我们有了所有的组件,让我们更新src/routes/+page.svelte
。
src/routes/+page.svelte1<script> 2 import { page } from '$app/stores' 3 import Account from './Account.svelte' 4 import Auth from './Auth.svelte' 5</script> 6 7<svelte:head> 8 <title>Supabase + SvelteKit</title> 9 <meta name="description" content="SvelteKit using supabase-js v2" /> 10</svelte:head> 11 12{#if !$page.data.session} 13<Auth /> 14{:else} 15<Account session="{$page.data.session}" /> 16{/if}
一旦完成,在终端窗口运行这个程序。
npm run dev
然后打开浏览器到localhost:5173,你应该看到完成的应用程序。
个人照片
每个Supabase项目都配置了存储,用于管理照片和视频等大文件。
创建一个上传小组件
让我们为用户创建一个头像,以便他们可以上传个人资料照片。我们可以从创建一个新的组件开始。
src/routes/Avatar.svelte1<script lang="ts"> 2 import { createEventDispatcher } from 'svelte' 3 import { supabase } from '$lib/supabaseClient' 4 5 export let size = 10 6 export let url: string 7 8 let avatarUrl: string | null = null 9 let uploading = false 10 let files: FileList 11 12 const dispatch = createEventDispatcher() 13 14 const downloadImage = async (path: string) => { 15 try { 16 const { data, error } = await supabase.storage.from('avatars').download(path) 17 18 if (error) { 19 throw error 20 } 21 22 const url = URL.createObjectURL(data) 23 avatarUrl = url 24 } catch (error) { 25 if (error instanceof Error) { 26 console.log('Error downloading image: ', error.message) 27 } 28 } 29 } 30 31 const uploadAvatar = async () => { 32 try { 33 uploading = true 34 35 if (!files || files.length === 0) { 36 throw new Error('You must select an image to upload.') 37 } 38 39 const file = files[0] 40 const fileExt = file.name.split('.').pop() 41 const filePath = `${Math.random()}.${fileExt}` 42 43 let { error } = await supabase.storage.from('avatars').upload(filePath, file) 44 45 if (error) { 46 throw error 47 } 48 49 url = filePath 50 dispatch('upload') 51 } catch (error) { 52 if (error instanceof Error) { 53 alert(error.message) 54 } 55 } finally { 56 uploading = false 57 } 58 } 59 60 $: if (url) downloadImage(url) 61</script> 62 63<div> 64 {#if avatarUrl} <img src={avatarUrl} alt={avatarUrl ? 'Avatar' : 'No image'} class="avatar image" 65 style="height: {size}em; width: {size}em;" /> {:else} 66 <div class="avatar no-image" style="height: {size}em; width: {size}em;" /> 67 {/if} 68 69 <div style="width: {size}em;"> 70 <label class="button primary block" for="single"> 71 {uploading ? 'Uploading ...' : 'Upload'} 72 </label> 73 <input 74 style="visibility: hidden; position:absolute;" 75 type="file" 76 id="single" 77 accept="image/*" 78 bind:files 79 on:change="{uploadAvatar}" 80 disabled="{uploading}" 81 /> 82 </div> 83</div>
添加新的小组件
然后我们就可以把这个小部件添加到账号页面:
src/routes/Account.svelte1<script> 2 // Import the new component 3 import Avatar from './Avatar.svelte' 4</script> 5 6<form use:getProfile class="form-widget" on:submit|preventDefault="{updateProfile}"> 7 <!-- Add to body --> 8 <Avatar bind:url="{avatarUrl}" size="{10}" on:upload="{updateProfile}" /> 9 10 <!-- Other form elements --> 11</form>
下一步
在这个阶段,你已经有了一个功能完备的应用程序!
- 有问题吗?在此提问.
- 请登录MemFire Cloud