クイックスタート:Svelte
イントロ
この例では、SupabaseとSvelteを使って、シンプルなユーザー管理アプリを(ゼロから)構築する手順を紹介します。内容は以下のとおりです。
- Supabase Database:ユーザーデータを保存するためのPostgresデータベースです。
- Supabase Auth:ユーザーはマジックリンクでサインインできます(パスワードは不要、メールのみ)。
- Supabase Storage:ユーザーは写真をアップロードできます。
- 行単位セキュリティー:データは保護されており、個人が自分のデータにしかアクセスできないようになっています。
- インスタントAPI:データベースのテーブルを作成すると、APIが自動的に生成されます。
このガイドの最後には、ユーザーがログインして基本的なプロフィール情報を更新できるアプリが完成します。
GitHub
どこかで行き詰ったら、このリポジトリーを見てみましょう。
プロジェクトのセットアップ
ビルドを開始する前に、データベースとAPIをセットアップします。Supabaseで新しいプロジェクトを立ち上げ、データベース内に「スキーマ」を作成するだけです。
プロジェクトの作成
- app.supabase.comにアクセスします。
- 「New Project」をクリックします。
- プロジェクトの詳細を入力します。
- 新しいデータベースが起動するのを待ちます。
データベース・スキーマの設定
これからデータベース・スキーマを設定します。SQLエディターの「User Management Starter」クイック・スターターを使用するか、下記のSQLをコピー/ペーストして自分で実行できます。
- UI
- SQL
1. 「SQL」セクションに移動します。
2. 「User Management Starter」をクリックします。
3. 「Run」をクリックします。
-- パブリックで「profiles」テーブルを作成
create table profiles (
id uuid references auth.users not null,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,
primary key (id),
unique(username),
constraint username_length check (char_length(username) >= 3)
);
alter table profiles enable row level security;
create policy "Public profiles are viewable by everyone."
on profiles for select
using ( true );
create policy "Users can insert their own profile."
on profiles for insert
with check ( auth.uid() = id );
create policy "Users can update own profile."
on profiles for update
using ( auth.uid() = id );
-- リアルタイムをセットアップ
begin;
drop publication if exists supabase_realtime;
create publication supabase_realtime;
commit;
alter publication supabase_realtime add table profiles;
-- ストレージをセットアップ
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
create policy "Avatar images are publicly accessible."
on storage.objects for select
using ( bucket_id = 'avatars' );
create policy "Anyone can upload an avatar."
on storage.objects for insert
with check ( bucket_id = 'avatars' );
APIキーを取得
データベースのテーブルをいくつか作成したので、自動生成されたAPIを使ってデータを挿入する準備ができました。
API設定からURLとanon
キーを取得する必要があります。
- UI
1. 「Settings」セクションに移動します。
2. サイドバーの「API」をリックします。
3. そのページでAPI URLを探します。
4. 「anon」と「service_role」キーを探します。
アプリの構築
それでは早速、Svelteアプリを一から作ってみましょう。
Svelteアプリの初期化
Svelteテンプレートのクイックスタートを使って、supbase-svelte
というアプリを初期化します。
npx degit sveltejs/template supabase-svelte
cd supabase-svelte
そして、唯一追加する必要がある依存関係のsupbase-jsをインストールしましょう。
npm install @supabase/supabase-js
最後に、環境変数を.env
に保存します。
必要なのは、APIのURLと、先ほどコピーしたanon
キーだけです。
SVELTE_APP_SUPABASE_URL=YOUR_SUPABASE_URL
SVELTE_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
このアプリはほぼ機能していますが、svelteをSupabaseや.env
ファイルで動作させるためには、まず、rollup.config.js
ファイルを少し変更する必要があります。
Supabaseはjson
ファイルをインポートしますが、.jsonファイルをES6モジュールに変換するには、@rollup/plugin-json
をインストールして実行する必要があります。
npm install --save-dev @rollup/plugin-json
さらに、.envをsvelteで使用するためには、別のrollupプラグインが必要です。そのプラグインをインストールします。
npm install --save-dev dotenv @rollup/plugin-replace
これらのプラグインをrollup.config.js
ファイルに追加します。
import { config } from 'dotenv';
import replace from '@rollup/plugin-replace';
import json from '@rollup/plugin-json'
export default {
plugins: [
replace({
__api: JSON.stringify({
env: {
isProd: production,
...config().parsed // attached the .env config
}
}),
delimiters: ['', '']
}),
json(),
// ...
],
// ...
}
APIの認証情報が得られたので、次はSupabaseクライアントを初期化するヘルパーファイルを作成しましょう。 これらの変数はブラウザー上で公開されますが、データベースでは行単位のセキュリティが有効になっているので、全く問題ありません。
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = __api.env.SVELTE_APP_SUPABASE_URL
const supabaseAnonKey = __api.env.SVELTE_APP_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
また、オプションとして、CSSファイルのpublic/global.css
を更新して、アプリの外観を整えます。
こちらにあるファイルを参照してください。
ログイン・コンポーネントの設定
ログインとサインアップを管理するSvelteコンポーネントを設定しましょう。マジック・リンクを使用することで、ユーザーはパスワードを使わずにメールでサインインできます。
<script>
import {supabase} from "./supabaseClient"
let loading = false
let email;
const handleLogin = async () => {
try {
loading = true
const { error } = await supabase.auth.signIn({ email })
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
alert(error.error_description || error.message)
} finally {
loading = false
}
}
</script>
<form class="row flex flex-center" on:submit|preventDefault={handleLogin}>
<div class="col-6 form-widget">
<h1 class="header">Supabase + Svelte</h1>
<p class="description">Sign in via magic link with your email below</p>
<div>
<input
class="inputField"
type="email"
placeholder="Your email"
bind:value={email}
/>
</div>
<div>
<input type="submit" class='button block' value={loading ? "Loading" : "Send magic link"} disabled={loading} />
</div>
</div>
</form>
ユーザー・ストア
他の場所にあるユーザー情報へアクセスするため、書き込み可能なストアを使用します。sessionStore.js
という新しいファイルを作成します。
import { writable } from 'svelte/store';
export const user = writable(false);
アカウントページ
ユーザーがサイン・インした後、プロフィールの詳細を編集したり、アカウントを管理できるようにします。
そのための新しいコンポーネント、Profile.svelte
を作りましょう。
<script>
import { supabase } from './supabaseClient'
import { user } from './sessionStore'
let loading = true
let username = null
let website = null
let avatar_url = null
async function getProfile() {
try {
loading = true
const user = supabase.auth.user()
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) throw error
if (data) {
username = data.username
website = data.website
avatar_url = data.avatar_url
}
} catch (error) {
alert(error.message)
} finally {
loading = false
}
}
async function updateProfile() {
try {
loading = true
const user = supabase.auth.user()
const updates = {
id: user.id,
username,
website,
avatar_url,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
if (error) throw error
} catch (error) {
alert(error.message)
} finally {
loading = false
}
}
async function signOut() {
try {
loading = true
let { error } = await supabase.auth.signOut()
if (error) throw error
} catch (error) {
alert(error.message)
} finally {
loading = false
}
}
</script>
<form use:getProfile class="form-widget" on:submit|preventDefault={updateProfile}>
<div>
<label for="email">Email</label>
<input id="email" type="text" value={$user.email} disabled />
</div>
<div>
<label for="username">Name</label>
<input
id="username"
type="text"
bind:value={username}
/>
</div>
<div>
<label for="website">Website</label>
<input
id="website"
type="website"
bind:value={website}
/>
</div>
<div>
<input type="submit" class="button block primary" value={loading ? 'Loading ...' : 'Update'} disabled={loading}/>
</div>
<div>
<button class="button block" on:click={signOut} disabled={loading}>
Sign Out
</button>
</div>
</form>
ローンチ
すべてのコンポーネントがそろったところで、App.svelte
を更新しましょう。
<script>
import {user} from "./sessionStore"
import {supabase} from "./supabaseClient"
import Auth from "./Auth.svelte"
import Profile from "./Profile.svelte"
user.set(supabase.auth.user())
supabase.auth.onAuthStateChange((_, session) => {
user.set(session.user)
})
</script>
<div class="container" style="padding: 50px 0 100px 0;">
{#if $user}
<Profile />
{:else}
<Auth />
{/if}
</div>
更新が完了したら、ターミナル・ウィンドウで次のコマンドを実行します。
npm run dev
そして、ブラウザーでlocalhost:5000を開くと、完成したアプリを見ることができます。
⚠️ 注意:Svelteはデフォルトで
5000番ポート
を使用し、Supabaseは3000番ポート
を使用します。Supabaseのリダイレクトポートを変更するには、次のようにします。Authentication > Settings
でSite Url
をlocalhost:5000
に変更してください。
おまけ:プロフィール写真
すべてのSupabaseプロジェクトには、写真やビデオなどの大容量ファイルを管理するためのStorageが設定されています。
アップロード・ウィジェットの作成
ユーザーがプロフィール写真をアップロードできるように、ユーザーのアバターを作成しましょう。まず、新しいコンポーネントを作成します。
<script>
import { createEventDispatcher } from 'svelte';
import { supabase } from './supabaseClient'
export let path;
export let size = "10em"
let uploading = false
let src;
let files;
const dispatch = createEventDispatcher();
async function downloadImage() {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) throw error
src = URL.createObjectURL(data)
} catch (error) {
console.error('Error downloading image: ', error.message)
}
}
async function uploadAvatar() {
try {
uploading = true
if (!files || files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file)
if (uploadError) throw uploadError
path = filePath
dispatch('upload')
} catch (error) {
alert(error.message)
} finally {
uploading = false
}
}
</script>
<div>
{#if path}
<img use:downloadImage
{src}
alt="Avatar"
class="avatar image"
style="height: {size}; width: {size};"
/>
{:else}
<div class="avatar no-image" style="height: {size}; width: {size};" />
{/if}
<div style="width: {size};">
<label class="button primary block" for="single">
{uploading ? 'Uploading ...' : 'Upload'}
</label>
<input
style="visibility: hidden; position:absolute;"
type="file"
id="single"
accept="image/*"
bind:files
on:change={uploadAvatar}
disabled={uploading}
/>
</div>
</div>
新しいウィジェットの追加
そして、このウィジェットをアカウント・ページに追加します。
<script>
// Import the new component
import Avatar from './Avatar.svelte'
</script>
<form use:getProfile class="form-widget" on:submit|preventDefault={updateProfile}>
<!-- Add to body -->
<Avatar bind:path={avatar_url} on:upload={updateProfile} />
<!-- Other form elements -->
</form>
次のステップ
この段階で、完全に機能するアプリケーションが完成しました。
- 何か疑問がありましたら、こちらで質問してください
- サインイン:app.supabase.com