Closed11

Remix on Cloudflare Workersでsupabase realtimeを使うときのメモ

Hi MORISHIGEHi MORISHIGE

supabase関連のパッケージをインストール。

$ yarn add @supabase/supabase-js react-supabase
Hi MORISHIGEHi MORISHIGE

型定義にグローバルなWorkerに登録する変数を設定。

types/bindings.d.ts
export {};

declare global {
  const SUPABASE_ANON_KEY: string;
  const SUPABASE_URL: string;
}
Hi MORISHIGEHi MORISHIGE

Wrangler経由でシークレット変数を登録。

$ wrangler secret put SUPABASE_URL
$ wrangler secret put SUPABASE_ANON_KEY
Hi MORISHIGEHi MORISHIGE

dev環境等使い分ける場合は--envを使って登録しておく。

$ wrangler secret put SUPABASE_ANON_KEY --env dev
Hi MORISHIGEHi MORISHIGE

wranglerで環境を分けてテスト。

package.json
  "scripts": {
    "build": "remix build",
    "deploy": "npm run build && wrangler publish",
    "dev:remix": "remix watch",
    "dev:miniflare": "cross-env NODE_ENV=development wrangler dev --env dev",
    "dev": "remix build && run-p dev:*",
    "start": "cross-env NODE_ENV=production wrangler dev"
  },
Hi MORISHIGEHi MORISHIGE

サーバーサイド側で動作するクライアントを用意。

app/libs/supabaseClient.server.ts
import { createClient } from '@supabase/supabase-js';

export const client =
  createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
    fetch: fetch.bind(globalThis),
  });

supabase-jscross-fetchを利用しているのでWokers環境ではglobal fetchを利用するように設定しておく。

https://github.com/supabase/supabase-js#custom-fetch-implementation

Hi MORISHIGEHi MORISHIGE

supabaseへは一旦下記のようなスキーマを用意。

テーブル名 todo

Name Type Default Value Primary
id int8 ---
title text NULL ---
created_at timestamptz now() ---

Remixから接続用関数を用意。

app/models/task.ts
import { client } from '~/libs/supabaseClient.server';

export type Todo = {
  id: number;
  title: string;
  created_at: string;
};

// タスクの一覧を取得
export const getTaskList = async () => {
  const { data: todo } = await client
    .from<Todo>('todo')
    .select('*')
    .order('created_at', { ascending: true });
  return todo;
};

// タスクを作成する
export const createTask = async (title: string) => {
  const { data: todo } = await client.from<Todo>('todo').insert({ title });
  return todo;
};

// タスクを削除する
export const deleteTask = async (id: number) => {
  const { data: todo } = await client.from<Todo>('todo').delete().eq('id', id);
  return todo;
};
Hi MORISHIGEHi MORISHIGE

トップページにサーバーサイドからの取得データとクライアントサイドでrealtimeのデータを取得してみる。supabaseの管理画面からデータの登録や更新をしてクライアントサイド側のデータが反映されることを確認しておく。

app/routes/index.tsx
import type { LoaderFunction } from '@remix-run/cloudflare';
import { json } from '@remix-run/cloudflare';
import { useLoaderData } from '@remix-run/react';
import type { Todo } from 'models/task';
import { getTaskList } from 'models/task';
import { useRealtime, useSubscription } from 'react-supabase';

export const loader: LoaderFunction = async ({ params, context }) => {
  const todo = await getTaskList();
  return json(todo);
};

export default function Index() {
  const todo = useLoaderData<Todo[] | null>();

  const [{ data, error, fetching }, reexecute] = useRealtime('todo');

  useSubscription(
    (payload) => {
      console.log('Change received!', payload);
      reexecute();
    },
    { event: '*', table: 'todo' },
  );

  return (
    <div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
      <h1>Welcome to Remix</h1>
      <ul>
        {todo?.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
      <hr />
      <ul>
        {data?.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
}
このスクラップは2022/07/14にクローズされました