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

クイックスタート:React

イントロ

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

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

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

Supabaseユーザー管理の例

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

ビルドを開始する前に、データベースと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」キーを探します。

アプリの構築

それでは早速、Reactアプリを一から作ってみましょう。

Reactアプリの初期化

Create React Appを使って、 supabase-reactというアプリを初期化します。

npx create-react-app supabase-react
cd supabase-react

そして、唯一追加する必要がある依存関係のsupbase-jsをインストールしましょう。

npm install @supabase/supabase-js

最後に、環境変数を.envに保存します。 必要なのは、APIのURLと、先ほどコピーしたanonキーだけです。

.env
REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL
REACT_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

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

src/supabaseClient.js
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.REACT_APP_SUPABASE_URL
const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)

また、オプションとして、CSSファイルのsrc/index.cssを更新して、アプリの外観を整えます。 こちらにあるファイルを参照してください。

ログイン・コンポーネントの設定

ログインとサインアップを管理するReactコンポーネントを設定しましょう。マジック・リンクを使用することで、ユーザーはパスワードを使わずに電子メールでサインインできます。

/src/Auth.js
import { useState } from 'react'
import { supabase } from './supabaseClient'

export default function Auth() {
const [loading, setLoading] = useState(false)
const [email, setEmail] = useState('')

const handleLogin = async (e) => {
e.preventDefault()

try {
setLoading(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 {
setLoading(false)
}
}

return (
<div className="row flex flex-center">
<div className="col-6 form-widget" aria-live="polite">
<h1 className="header">Supabase + React</h1>
<p className="description">Sign in via magic link with your email below</p>
{loading ? (
'Sending magic link...'
) : (
<form onSubmit={handleLogin}>
<label htmlFor="email">Email</label>
<input
id="email"
className="inputField"
type="email"
placeholder="Your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button className="button block" aria-live="polite">
Send magic link
</button>
</form>
)}
</div>
</div>
)
}

アカウント・ページ

ユーザーがログインした後、プロフィールの詳細を編集したり、アカウントを管理できるようにします。

そのための新しいコンポーネント、Account.jsを作りましょう。

src/Account.js
import { useState, useEffect } from 'react'
import { supabase } from './supabaseClient'

const Account = ({ session }) => {
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState(null)
const [website, setWebsite] = useState(null)
const [avatar_url, setAvatarUrl] = useState(null)

useEffect(() => {
getProfile()
}, [session])

const getProfile = async () => {
try {
setLoading(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) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}

const updateProfile = async (e) => {
e.preventDefault()

try {
setLoading(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 {
setLoading(false)
}
}

return (
<div aria-live="polite">
{loading ? (
'Saving ...'
) : (
<form onSubmit={updateProfile} className="form-widget">
<div>Email: {session.user.email}</div>
<div>
<label htmlFor="username">Name</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="url"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<button className="button block primary" disabled={loading}>
Update profile
</button>
</div>
</form>
)}
<button type="button" className="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
)
}

export default Account

ローンチ

すべてのコンポーネントがそろったところで、App.jsを更新しましょう。

src/App.js
import './index.css'
import { useState, useEffect } from 'react'
import { supabase } from './supabaseClient'
import Auth from './Auth'
import Account from './Account'

export default function App() {
const [session, setSession] = useState(null)

useEffect(() => {
setSession(supabase.auth.session())

supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
}, [])

return (
<div className="container" style={{ padding: '50px 0 100px 0' }}>
{!session ? <Auth /> : <Account key={session.user.id} session={session} />}
</div>
)
}

更新が完了したら、ターミナル・ウィンドウで次のコマンドを実行します。

npm start

そして、ブラウザーでlocalhost:3000を開くと、完成したアプリを見ることができます。

Supabase React

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

Supabaseのプロジェクトには、写真や動画などの大容量ファイルを管理するためのストレージが用意されています。

@reach/visually-hiddenを追加

アップロード・ウィジェットは、@reach/visually-hiddenというnpmライブラリーを1つ追加して使用します。npm install @reach/visually-hiddenを実行してインストールします。

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

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

src/Avatar.js
import { useEffect, useState } from 'react'
import { supabase } from './supabaseClient'
import VisuallyHidden from '@reach/visually-hidden'

export default function Avatar({ url, size, onUpload }) {
const [avatarUrl, setAvatarUrl] = useState(null)
const [uploading, setUploading] = useState(false)

useEffect(() => {
if (url) downloadImage(url)
}, [url])

const downloadImage = async (path) => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error.message)
}
}

const uploadAvatar = async (event) => {
try {
setUploading(true)

if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
}

const file = event.target.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
}

onUpload(filePath)
} catch (error) {
alert(error.message)
} finally {
setUploading(false)
}
}

return (
<div style={{ width: size }} aria-live="polite">
<img
src={avatarUrl ? avatarUrl : `https://place-hold.it/${size}x${size}`}
alt={avatarUrl ? 'Avatar' : 'No image'}
className="avatar image"
style={{ height: size, width: size }}
/>
{uploading ? "Uploading..." : (
<>
<label className="button primary block" htmlFor="single">
Upload an avatar
</label>
<VisuallyHidden>
<input
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading}
/>
</VisuallyHidden>
</>
)}
</div>
)
}

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

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

src/Account.js
// Import the new component
import Avatar from './Avatar'

// ...

return (
<div className="form-widget">
{/* Add to the body */}
<Avatar
url={avatar_url}
size={150}
onUpload={(url) => {
setAvatarUrl(url)
updateProfile({ username, website, avatar_url: url })
}}
/>
{/* ... */}
</div>
)

次のステップ

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