Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【Supabase×React】サーバレスアプリ開発入門

kei4eva4
July 12, 2023
570

 【Supabase×React】サーバレスアプリ開発入門

2023.07.21 オトナのプログラミング勉強会 https://otona.connpass.com/event/288114/

kei4eva4

July 12, 2023
Tweet

Transcript

  1. 新垣圭祐 (ソーイ株式会社 / YOKUMIRU株式会社) Web/モバイルアプリ開発エンジニア → PdM 大学院で自律分散コンピューティングの研究 インドに渡り現地システム会社にて勤務 帰国してフリーランスエンジニア

    → 会社設立 認定スクラムマスター/プロダクトオーナー 興味 アジャイル開発、プロダクトマネジメント Web3 趣味 格闘技(柔術) 【Supabase×React】サーバレスアプリ開発入門 2
  2. Supabase Supabase is an open source Firebase alternative. サーバーレスのバックエンドサービス(Firebase Alternative:

    Firabaseの代替) 全てがオープンソース PostgreSQL(RDS)(⇔ FirebaseはNoSQL) 2019年より開発 【Supabase×React】サーバレスアプリ開発入門 5
  3. 利用イメージ 従来の開発 1. クライアントがバックエンドにリクエスト を送信 2. バックエンドで認可、バリデーション、ト ランザクション等を必要に応じてハンドリ ング 3.

    バックエンドがデータベースにアクセスし てデータを取得して、フロントエンドに返 却する 【Supabase×React】サーバレスアプリ開発入門 6
  4. Next.jsのプロジェクトの作成 $ npx create-next-app What is your project named? otona-hans-on

    Would you like to use TypeScript with this project? … No Would you like to use ESLint with this project? … No Would you like to use Tailwind CSS with this project? … Yes Would you like to use src/ directory with this project? … No Use App Router (recommended)? … Yes Would you like to customize the default import alias? … No 【Supabase×React】サーバレスアプリ開発入門 11
  5. Next.jsの動作を確認 $ cd otona-hans-on // supabase-js のインストール $ npm install

    @supabase/supabase-js // Next.js の起動 $ npm run dev http://localhost:3000を開く 【Supabase×React】サーバレスアプリ開発入門 12
  6. app/global.css 4行目以降を削除する @tailwind base; @tailwind components; @tailwind utilities; app/page.js 下記内容に置き換える

    export default function Home() { return ( <main className='bg-black h-screen'> <div className='flex h-full items-center justify-center'> <div className='text-white'> ハンズオンのスタート</div> </div> </main> ) } 【Supabase×React】サーバレスアプリ開発入門 13
  7. SupabaseのDashboardから「New Project」 Name: otona hans-on Database Password: (「Generate a passowrd」をクリック)

    Region: Northeast Asia (Tokyo) Pricing Plan: Free 【Supabase×React】サーバレスアプリ開発入門 15
  8. Project Settings > API > API Settingsを 開く 1. Project

    API Keysのanon publicをコピー 2. Project ConfigurationのURLをコピー Next.jsのプロジェクト直下に.env.localフ ァイルを作成 NEXT_PUBLIC_SUPABASE_URL= (1 を貼り付け) NEXT_PUBLIC_SUPABASE_ANON_KEY= (2 を貼り付け) 【Supabase×React】サーバレスアプリ開発入門 16
  9. SQL Editor > + New query > New blank queryをクリ

    ック 以下の内容を貼り付けてRUNをクリック create table todos ( id bigint generated by default as identity primary key, user_id uuid references auth.users not null, task text not null, is_complete boolean default false, inserted_at timestamp with time zone default timezone('utc'::text, now()) not null ); 【Supabase×React】サーバレスアプリ開発入門 17
  10. ログイン画面の表示 app/page.js export default function Home() { return ( <main

    className='bg-black h-screen'> <div className='flex h-full items-center justify-center'> <div className='px-8 w-full md:w-96'> <form className='flex flex-col space-y-4' > <input className='p-2 rounded w-full' type='email' name='email' placeholder=' メールアドレス' /> <button className='bg-green-500 text-white rounded block py-2'> マジックリンクでログイン </button> </form> </div> </div> </main> ) } 【Supabase×React】サーバレスアプリ開発入門 18
  11. ログイン処理を追加 Supabaseクライアントを作成 (use clientでクライアントコンポーネントであることを明示する) 'use client' import {createClient} from '@supabase/supabase-js';

    const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, ) 【Supabase×React】サーバレスアプリ開発入門 19
  12. Home()内にログイン処理を追加 const handleLogin = async (e) => { e.preventDefault() const

    form = e.currentTarget const { email } = Object.fromEntries(new FormData(form)) if (typeof email === 'string') { await supabase.auth.signInWithOtp({ email }) alert('Email に認証リンクが送信されました') } } ボタンを押した時にhandleLogin()が呼ばれるようにする ... <form className='flex flex-col space-y-4' onSubmit={handleLogin}> ... 【Supabase×React】サーバレスアプリ開発入門 20
  13. Home()内にログインユーザを取得する処理を追加 // ユーザーを保持する関数 const [user, setUser] = useState(null) useEffect(() =>

    { // ログイン状態の確認 const getInitialUser = async () => { const { data: { user }, } = await supabase.auth.getUser() console.log(user) setUser(user) } getInitialUser() }, []) 【Supabase×React】サーバレスアプリ開発入門 21
  14. ログインユーザの表示 return ( <main className='bg-black h-screen'> {user ? ( //

    ログイン済みの表示 <div className='p-4 flex flex-col h-full max-w-xl md:mx-auto'> <div className='text-white'>Hello, {user.id}</div> </div> ) : ( // 未ログイン時の表示 <div className='flex h-full items-center justify-center'> <div className='px-8 w-full md:w-96'> (略、フォーム部分) </div> </div> )} </main> ) 【Supabase×React】サーバレスアプリ開発入門 22
  15. データの挿入(insert) // 新しいタスクを`todos` テーブルに挿入 const handleSubmit = async (e) =>

    { e.preventDefault() const form = e.currentTarget const { task } = Object.fromEntries(new FormData(form)) // データのバリデーション if (typeof task === 'string' && task.length !== 0) { form.reset() // `todos` テーブルにデータを格納 await supabase.from('todos').insert({ user_id: user.id, task: task }) } } 【Supabase×React】サーバレスアプリ開発入門 23
  16. 表示部分の作成 ... <div className='text-white'>Hello, {user.id}</div> <form> <input className='p-2 rounded w-full'

    type='text' name='task' placeholder=' 新しいタスクを入力...' /> </form> ... Supabase管理画面のTable Editorでデータが追加され ていることを確認 【Supabase×React】サーバレスアプリ開発入門 24
  17. データの取得(select) ページを表示した時にデータを取得する処理を追加 const [user, setUser] = useState(null) const [todos, setTodos]

    = useState([]) // 追加 useEffect(() => { // (略) const getInitialMessages = async () => { const { data, error } = await supabase .from('todos') .select() .order('inserted_at', { ascending: false }) console.log(data) if (error) { alert(error.message) } else if (data) { setTodos(data) } } getInitialMessages() }, []) 【Supabase×React】サーバレスアプリ開発入門 25
  18. 表示部分 // (略) <div className='text-white'>Hello, {user.id}</div> <div className='flex-grow overflow-y-scroll'> <ul>

    {todos.map((todo) => ( <li className='pb-2 flex border-b-gray-600 border-b' key={todo.id} > <div className='px-2 py-1 rounded flex-grow text-white'> {todo.task} <div className='text-zinc-500 text-sm'> {new Date(todo.inserted_at).toLocaleDateString('ja')} </div> </div> </li> ))} </ul> </div> <form onSubmit={handleSubmit}> // (略) 【Supabase×React】サーバレスアプリ開発入門 26
  19. データの更新(update) // チェックアイコンをクリックした時に`is_comprete` ステータスをアップデート const handleUpdate = async (taskId, newStatus)

    => { await supabase .from('todos') .update({ is_complete: newStatus }) .eq('id', taskId) .select() } 【Supabase×React】サーバレスアプリ開発入門 27
  20. iconのインストール $ npm install @heroicons/react 表示部分 <li className='pb-2 flex border-b-gray-600

    border-b' key={todo.id} > {/* ここから追加*/} <button className='mr-2' onClick={() => handleUpdate(todo.id, !todo.is_complete)} > <CheckCircleIcon className={`h-6 w-6 ${ todo.is_complete ? 'text-green-500' : 'text-gray-600' }`} /> </button> {/* ここまで*/} <div className='px-2 py-1 rounded flex-grow text-white'> {todo.task} <div className='text-zinc-500 text-sm'> {new Date(todo.inserted_at).toLocaleDateString('ja')} </div> </div> </li> 【Supabase×React】サーバレスアプリ開発入門 28
  21. データの削除(delete) const handleDelete = async (taskId) => { await supabase

    .from('todos') .delete() .eq('id', taskId) } 【Supabase×React】サーバレスアプリ開発入門 29
  22. 表示部分 <li className='pb-2 flex border-b-gray-600 border-b' key={todo.id} > <button className='mr-2'

    onClick={() => handleUpdate(todo.id, !todo.is_complete)} > <CheckCircleIcon className={`h-6 w-6 ${ todo.is_complete ? 'text-green-500' : 'text-gray-600' }`} /> </button> <div className='px-2 py-1 rounded flex-grow text-white'> {todo.task} <div className='text-zinc-500 text-sm'> {new Date(todo.inserted_at).toLocaleDateString('ja')} </div> </div> {/* ここから追加*/} <button className='ml-2' onClick={() => handleDelete(todo.id)} > <TrashIcon className='w-6 h-6 text-red-500' /> </button> {/* ここまで*/} </li> 【Supabase×React】サーバレスアプリ開発入門 30
  23. データの追加(insert)をリアルタイムで反映させる useEffect(() => { // 略 supabase.channel('todos-channel') .on( 'postgres_changes', {

    event: 'INSERT', schema: 'public', table: 'todos' }, (payload) => { setTodos((prev) => [payload.new, ...prev]) } ) .subscribe() // 略 }, []) タスクを追加したら、リアルタイムで反映されることを確認する 【Supabase×React】サーバレスアプリ開発入門 33
  24. データの更新(update)、データの削除(delete)をリアルタイムで反映させる supabase.channel('todos-channel') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table:

    'todos' }, (payload) => { setTodos((prev) => [payload.new, ...prev]) } ) .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'todos' }, (payload) => { setTodos((prev) => { const updatedTodo = payload.new const targetIndex = prev.findIndex( (todo) => todo.id === updatedTodo.id ) if (targetIndex >= 0) { prev[targetIndex] = updatedTodo } return [...prev] }) } ) .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'todos' }, (payload) => { setTodos((prev) => prev.filter((todo) => todo.id !== payload.old.id) ) } ).subscribe() 【Supabase×React】サーバレスアプリ開発入門 34
  25. Row Level Security (RLS) について Supabaseでは、PostgreSQLのRLS(行単位セキュリティ)を利用することにより細かい認可ルールを設定 することができる PoliciesはPostgreSQLのルールエンジンで、複雑で柔軟なルール設定を記述することが可能 Policies(ポリシー)の例 自分のTodoのみ表示が可能

    create policy "Individuals can view their own todos." on todos for select using ( auth.uid() = user_id ); データ取得(slect)の度に、次のように変換される select * from todos where auth.uid() = todos.user_id; -- Policy is implicitly added. 【Supabase×React】サーバレスアプリ開発入門 35
  26. ログアウト機能をつける const handleLogout = async () => { await supabase.auth.signOut()

    } 表示部分 <div className='flex'> <div className='flex-auto text-white py-1'>Hello, {user.id}</div> <form onSubmit={handleLogout} className='flex-auto' > <button className='bg-transparent text-red-500 border border-red-500 rounded block py-1 px-2'> ログアウト </button> </form> </div> 【Supabase×React】サーバレスアプリ開発入門 37
  27. 自分のタスクか他の人のタスクかラベルをつけ る <div className='px-2 py-1 rounded flex-grow text-white'> {todo.task} <div

    className='text-zinc-500 text-sm'> {new Date(todo.inserted_at).toLocaleDateString('ja')} </div> {/* ここから追加*/} <div className='text-zinc-500 text-sm'> { todo.user_id === user.id ? ' 私のタスク' : ' 他人のタスク'} </div> {/* ここまで*/} </div> ここまでのpage.jsの内容 https://gist.github.com/kei4eva4/0c32432e4605dfe5b31d 30a68ebed799#file-2-page-js 【Supabase×React】サーバレスアプリ開発入門 38
  28. Row Level Security (RLS) の 利用 Database > todos(テーブルを選んで) Editをクリック

    Enable Row Level Securityにチェックを入 れてSaveをクリック 【Supabase×React】サーバレスアプリ開発入門 39
  29. Plicies(ポリシー)を作成 Authentication > Policies > New Policy > For full

    customizationをクリック 【Supabase×React】サーバレスアプリ開発入門 41
  30. Plicy name: Individuals can view their own todos. Allowed operation:

    SELECT USING expression: (auth.uid() = user_id) Review > Save pliciyをクリック 【Supabase×React】サーバレスアプリ開発入門 42
  31. SQL Editor > + New query > New blank queryをクリック

    挿入(insert)、更新(update)、削除 (delete)についてはSQL Editorから同様 に作成する 以下の内容を貼り付けてRUNをクリック create policy "Individuals can create todos." on todos for insert with check (auth.uid() = user_id); create policy "Individuals can update their own todos." on todos for update using (auth.uid() = user_id); create policy "Individuals can delete their own todos." on todos for delete using (auth.uid() = user_id); 【Supabase×React】サーバレスアプリ開発入門 44
  32. PostgREST (API) PostgreSQLのREST APIが自動生成される APIでテーブル操作が可能 Haskell Realtime (API & multiplayer)

    メッセージのブロードキャスト データベース変更をストリーミング可能にする Websocket Elixir 【Supabase×React】サーバレスアプリ開発入門 49
  33. Storage API (large file storage) AWS S3互換のストレージ 権限制御はGoTrueの発行したToken TypeScript Deno

    (Edge Functions) JavaScript/TypeScriptのランタイム TypeScript/Rust 【Supabase×React】サーバレスアプリ開発入門 50
  34. まとめ Supabaseとは Firebaseの代替 全てOSSを組み合わせて構成されている 使いやすいフロントエンド、モバイルクライアントを提供 ハンズオン データベースの作成 リアルタイム機能(サブスクリプション) Row Level

    Security(RLS) Supabaseのアーキテクチャ 生のPostgreSQLを使いやすくするツールを提供 他のOSSを組み合わせて構成されている Supabase自身で開発しているものもOSSとして公開している 【Supabase×React】サーバレスアプリ開発入門 51
  35. 参考リンク 公式ドキュメント https://supabase.com/docs 開発者を魅了するオープンソースソフトウェア Supabase とは | AWS Dev Day

    2022 Japan https://www.youtube.com/watch?v=XWYkpQRLsFk 【Supabase×React】サーバレスアプリ開発入門 52