メインコンテンツまでスキップ

クイックスタート:Svelte

イントロ

この例では、SupabaseとSvelteを使って、シンプルなユーザー管理アプリを(ゼロから)構築する手順を紹介します。内容は以下のとおりです。

  • Supabase Database:ユーザーデータを保存するためのPostgresデータベースです。
  • Supabase Auth:ユーザーはマジックリンクでサインインできます(パスワードは不要、メールのみ)。
  • Supabase Storage:ユーザーは写真をアップロードできます。
  • 行単位セキュリティー:データは保護されており、個人が自分のデータにしかアクセスできないようになっています。
  • インスタントAPI:データベースのテーブルを作成すると、APIが自動的に生成されます。

このガイドの最後には、ユーザーがログインして基本的なプロフィール情報を更新できるアプリが完成します。

Supabaseユーザー管理の例

GitHub

どこかで行き詰ったら、このリポジトリーを見てみましょう。

プロジェクトのセットアップ

ビルドを開始する前に、データベースとAPIをセットアップします。Supabaseで新しいプロジェクトを立ち上げ、データベース内に「スキーマ」を作成するだけです。

プロジェクトの作成

  1. app.supabase.comにアクセスします。
  2. 「New Project」をクリックします。
  3. プロジェクトの詳細を入力します。
  4. 新しいデータベースが起動するのを待ちます。

データベース・スキーマの設定

これからデータベース・スキーマを設定します。SQLエディターの「User Management Starter」クイック・スターターを使用するか、下記のSQLをコピー/ペーストして自分で実行できます。

1. 「SQL」セクションに移動します。
2. 「User Management Starter」をクリックします。
3. 「Run」をクリックします。

APIキーを取得

データベースのテーブルをいくつか作成したので、自動生成されたAPIを使ってデータを挿入する準備ができました。 API設定からURLとanonキーを取得する必要があります。

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キーだけです。

.env
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ファイルに追加します。

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クライアントを初期化するヘルパーファイルを作成しましょう。 これらの変数はブラウザー上で公開されますが、データベースでは行単位のセキュリティが有効になっているので、全く問題ありません。

src/supabaseClient.js
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コンポーネントを設定しましょう。マジック・リンクを使用することで、ユーザーはパスワードを使わずにメールでサインインできます。

/src/Auth.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という新しいファイルを作成します。

src/sessionStore.js
import { writable } from 'svelte/store';

export const user = writable(false);

アカウントページ

ユーザーがサイン・インした後、プロフィールの詳細を編集したり、アカウントを管理できるようにします。 そのための新しいコンポーネント、Profile.svelteを作りましょう。

src/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を更新しましょう。

src/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 > SettingsSite Urllocalhost:5000に変更してください。

Supabase Svelte

おまけ:プロフィール写真

すべてのSupabaseプロジェクトには、写真やビデオなどの大容量ファイルを管理するためのStorageが設定されています。

アップロード・ウィジェットの作成

ユーザーがプロフィール写真をアップロードできるように、ユーザーのアバターを作成しましょう。まず、新しいコンポーネントを作成します。

src/Avatar.svelte
<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>

新しいウィジェットの追加

そして、このウィジェットをアカウント・ページに追加します。

src/Profile.svelte
<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>

次のステップ

この段階で、完全に機能するアプリケーションが完成しました。