Supabase

BaaSサービスSupabaseを使ってみる

はじめに

前回、フロントエンドのコードをGitHub Pagesにデプロイし無料公開できたので、バックエンド側も無料で公開できるものないかな~と探していたらBaaS(Backend as a Service)としてSupabaseなるものがあることを知ったので触ってみる。

少し前まではHerokuというサービスの無料プランが人気あったみたいだけど、2022年11月に終了してしまったみたい。。。残念

Supabaseとは

公式サイトには、Googleが提供しているFirebaseの代替品ってのがうたわれてた。

なので立ち位置的に、モバイル・Webアプリケーション開発プラットフォームになりFirebaseと同じようなことができるみたい。

なら、Firebaseでいいじゃんって感じだが、無料プランとしてはSupabaseのほうが出来ることが多かったのでこっちを使ってみる。

ざっくりだが以下のような機能を持つ

  1. Database RDMSとしてPostgresデータベースが使える ※NoSQLは使えない
  2. Auth 電子メールとパスワード、OAuthなど複数の認証方法が使える
  3. Storage ファイルのアップロードができる。AWSのS3みたいなもの
  4. Edge Functions AWSのLambdaのようなサーバー側関数を定義できる
  5. Realtime データベースの変更の監視、クライアント間でのユーザ状態のリアルタイム監視などを行える

気になる無料で使える範囲は変更されると思うので、次の公式サイトを確認する

Pricing & fees | Supabase

いまいまだと、個人開発・個人利用なら特段問題はなさそうだし、クレジットカード登録もしていないので急に請求されるといったことはなさそう

手順

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

  1. Supabaseにアクセスする。
  2. Supabaseのアカウント作成する。 自分はGithubアカウントを利用
  3. プロジェクトを新規で作成する。 Name、Database Password、Region(Tokyo)を選択

データベースの作成

まずはじめにデータベースのタイムゾーンを変更しておく

左のタブから[ SQL Editor ] を開いて次のSQLをRUNする

alter database postgres
set timezone to 'Asia/Tokyo';

テーブルの作成はGUIとSQLで可能だが、テーブルを複製しやすいようにSQLで今回は作る。

今回は、行単位でRLSを適用するために user_id カラムを追加している。

-- 1. Create table
create table countiries (
  id bigint generated by default as identity primary key,
  name text,
  user_id uuid references auth.users
);

RLSとは Row Level Securityの略で、テーブルの行レベルでセキュリティをかけることでデータを保護できる仕組みのこと。 https://supabase.com/docs/guides/database/postgres/row-level-security

Supabase Authと組みあわせることで、ユーザは自身が作成したレコードのみ参照、更新、削除ができるといったことができる

RLS用ポリシーの作成

accountsテーブルに対して、RLSを有効化してCRUD時のポリシーを設定する。

ここは少し自信ないけどこれでいけてるはず。。。

-- 2. Enable RLS
alter table accounts enable row level security;

-- 3. Create Policy
create policy "Enable select for users based on user_id"
on accounts for select
to authenticated
using (
  auth.uid() = user_id
);

create policy "Enable insert for authenticated users only"
on accounts for insert
to authenticated
WITH CHECK (true);

create policy "Enable update for users based on user_id"
on accounts for update
to authenticated
using (
  auth.uid() = user_id
);

create policy "Enable delete for users based on user_id"
on accounts for delete
to authenticated
using (
  auth.uid() = user_id
);

これで、insertは認証済みユーザのみ可能で、それ以外はレコードが自身のuser_idと一致するものに対してのみ、参照・更新・削除ができる

Edge Functionsの作成

簡単なテーブル操作であれば、Supabase clientを利用すれば可能だが、AWSのLambdaのようにバックエンド側で特定の処理を組み込みたいときに利用する。

今回はサンプルでhello-world関数を作成する。

supabase cliのインストール

// 1. supabase cliのインストール
npm install supabase

// 2. パスの設定(環境変数) 
// 任意で次のパスを環境変数に追加
C:\\xx\\AssetKeeper-Backend\\node_modules\\supabase\\bin\\

Edge Functionsの作成

// 3. エッジ関数の作成
supabase functions new hello-world

supabase > functions > hello-world > index.tsが作成される。

// Follow this setup guide to integrate the Deno language server with your editor:
// <https://deno.land/manual/getting_started/setup_your_environment>
// This enables autocomplete, go to definition, etc.

import { serve } from "<https://deno.land/std@0.168.0/http/server.ts>"

console.log("Hello from Functions!")

serve(async (req) => {
  const { name } = await req.json()
  const data = {
    message: `Hello ${name}!`,
  }

  return new Response(
    JSON.stringify(data),
    { headers: { "Content-Type": "application/json" } },
  )
})

// To invoke:
// curl -i --location --request POST '<http://localhost:54321/functions/v1/>' \\
//   --header 'Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxx' \\
//   --header 'Content-Type: application/json' \\
//   --data '{"name":"Functions"}'

ここに組み込みたい処理を記述していくのだが、下側のコメント内に認証キーが記載されているので、Githubとかにあげるときには注意する。

--header 'Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxx' \\

次にCORS対応を行う。

supabaseフォルダ内に、’_shared’フォルダを作成し、その中に、 cors.ts ファイルを作成する。

export const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

そのあと、エッジ関数内でCORSヘッダをインポートする

import { serve } from '<https://deno.land/std@0.177.0/http/server.ts>'
import { corsHeaders } from '../_shared/cors.ts'

console.log(`Function "browser-with-cors" up and running!`)

serve(async (req) => {
  // This is needed if you're planning to invoke your function from a browser.
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    const { name } = await req.json()
    const data = {
      message: `Hello ${name}!`,
    }

    return new Response(JSON.stringify(data), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 200,
    })
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 400,
    })
  }
})

コードの記述が完了したら本番環境にデプロイするが、その前に2つの情報を確認しておく。

  1. プロジェクトリファレンスID
    [ Project Settings ] > [ General ] > [ General settings ] にReference IDのこと
  2. Access Token
    ここから新規作成する https://supabase.com/dashboard/account/tokens

2つとも準備完了したらデプロイする

supabase functions deploy hello-world
Enter your project ref: [プロジェクトリファレンスIDを入力]
[Access Tokenを入力]

これでエッジ関数の登録は完了になる

あとは、このエッジ関数をフロントコードから呼び出す処理が必要になる

ユーザ認証関連の設定

Supabaseでは複数のプロバイダーを通じた認証方法があるので必要に応じて有効化する

有効化の設定は[ Authentication ] >[ Providers ] から変更できる

なお、初期設定でEmailとパスワード認証は、有効になっていた

フロントエンドとバックエンド(Supabase)の連携

  1. Supabase クライアントライブラリをインストール
npm install @supabase/supabase-js
  1. Supabase クライアントを作成する src/libにsupabaseClient.jsファイルを作成する。
import { createClient } from '@supabase/supabase-js'

// envファイルから環境情報を取得する
const supabaseUrl = import.meta.env.VITE_APP_SUPABASE_URL
const supabaseApiKey = import.meta.env.VITE_APP_SUPABASE_API_KEY

export const supabase = createClient(supabaseUrl, supabaseApiKey)

あとは、ここを参考にしながらDatabase操作や認証などのコード書いていく

以下はテーブルとビューに対するCRUDクエリのサンプルコード

SELECT

import { supabase } from '../lib/supabaseClient';
const { data, error } = await supabase
  .from('countries')
  .select()

INSERT

const { error } = await supabase
  .from('countries')
  .insert({ id: 1, name: 'Denmark' })

// RLSを設定している場合は、ユーザIDも渡す
const {
  data: { user }
} = await supabase.auth.getUser();

const { error } = await supabase
  .from('countries')
  .insert({ id: 1, name: 'Denmark', user_id: user.id })

UPDATE

const { error } = await supabase
  .from('countries')
  .update({ name: 'Australia' })
  .eq('id', 1)

DELETE

const { error } = await supabase
  .from('countries')
  .delete()
  .eq('id', 1)

Edge Functionをクライアント側で呼び出す場合は次のようにする

ex. hello-world関数の呼び出し

import { supabase } from '../lib/supabaseClient';

const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'Ashitaka' },
})

認証

フロント側の認証については、ここを参考にする

ここでは、パスワード認証におけるサンプルコードだけ乗せておく

Sign Up

const { data, error } = await supabase.auth.signUp({
  email: 'example@email.com',
  password: 'example-password',
})

Sing In

const { data, error } = await supabase.auth.signInWithPassword({
  email: 'example@email.com',
  password: 'example-password',
})

Sign Out

const { error } = await supabase.auth.signOut()

認証イベントをリッスン

supabase.auth.onAuthStateChange((event, session) => {
  console.log(event, session)
})

画面としては、次のような作りにしている

  • セッション情報がないとき
    • ログイン用画面(LoginView) を表示
  • ユーザがサインインしてセッション情報があるとき
    • ログイン後の画面を表示する
// App.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { supabase } from './lib/supabaseClient';

import LoginView from './views/LoginView.vue';
import TheSideMenu from './components/TheSideMenu.vue';

const session = ref();

onMounted(() => {
  supabase.auth.onAuthStateChange((_: any, session: any) => {
    session.value = session;
  });
});
</script>

<template>
  <div class="common-layout">
    <el-container v-if="session" :session="session">
      <el-aside class="side-menu" width="80px"><TheSideMenu /></el-aside>
      <el-main>
        <RouterView />
      </el-main>
    </el-container>
    <el-container v-else>
      <el-main> <LoginView /></el-main>
    </el-container>
  </div>
</template>

<style scoped>

一旦はこれで動いているが他にいいやり方があったら教えていただきたい。。

おわりに

BaaSサービスを使うのは初めてだったので、設定とか手順に手こずるところはあったけど、Supabaseのドキュメントが整備されていたこともあり、フロントとの繋ぎこみまで無事に完了できた。

まだまだ触れていない機能もあるので、そこを使ってみたいという気持ちもありつつどうか無料プランを改悪しないでほしいと切に願うばかり

参考資料

Supabase概要

Supabase Docs

JavaScript Client Library(Supabase client, supabase-js)

Installing | Supabase

Using filters | Supabase

Installing | Supabase

EdgeFunction

Edge Functions | Supabase Docs

Edge Functions Quickstart | Supabase Docs

Integrating With Supabase Auth | Supabase Docs

CORS (Cross-Origin Resource Sharing) support for Invoking from the browser | Supabase Docs

Supabase CLI

Supabase CLI reference - Introduction

Local Development | Supabase Docs

その他

supabaseでさくっとWebアプリを作ってみた|SHIFT Group 技術ブログ

【Supabase入門】認証・DB・リアルタイムリスナーを使ってチャットアプリを作ろう